ActionDispatch::ClosedError when testing Rails 3.1 model creation (RSpec/Cucumber) - cucumber

I am creating a web application with Ruby on Rails 3.1 (RC1). I am using Factory Girl, RSpec and Cucumber (with Capybara) for testing, but I am experiencing unexpected raised ActionDispatch::ClosedErrors some of the times (not every time) when I am creating new users (through the User model's create action). Below is the error message that I get:
Cannot modify cookies because it was closed. This means it was already streamed
back to the client or converted to HTTP headers. (ActionDispatch::ClosedError)
The error is raised when using these ways of creating users:
Creation using Factory Girl
Factory.create( :user )
Factory.build( :user ).save
Basic creation
User.create( { ... } )
User.new( { ... } ).save
What is funny is that they do work during some test, but not in others, and it does not seem random, although I cannot figure out the reason. Below is an excerpt from my code:
users_controller_spec.rb
require 'spec_helper'
def user
#user ||= Factory.create( :user )
end
def valid_attributes
Factory.attributes_for :user
end
describe UsersController do
describe 'GET index' do
it 'assigns all users as #users' do
users = [ user ] # The call to user() raises the error here
get :index
assigns[ :users ].should == users
end
end
describe 'GET show' do
it 'assigns the requested user as #user' do
get :show, id: user.id # The call to user() raises the error here
assigns[ :user ].should == user
end
end
However, the error is not raised in the following code block:
describe 'GET edit' do
it 'assigns the requested user as #user' do
get :edit, id: user.id # This raises no error
assigns[ :user ].should == user
end
end
Any other method below this does not raise the error, even though I am creating users in the exact same way.
Any suggestions to what I might be doing wrong would be greatly appreciated!

Someone posted a workaround here
https://github.com/binarylogic/authlogic/issues/262#issuecomment-1804988

This is due to the way rails 3 streams the response now. They posted a fix in edge for the same issue in flash but not in cookies yet. For now I have turned off my request specs. I am going to look at the problem this weekend if no one gets to it before then.
https://github.com/rails/rails/issues/1452

Just so we don't have to follow links, here's my modified version of the authlogic workaround:
class User < ActiveRecord::Base
acts_as_authentic do |c|
c.maintain_sessions = false if Rails.env == "test"
end
end
Rather than deal with ensuring session management on every .save call, I just turn them off if I'm testing.

Related

Active Admin create additional URL on unique field

I have an object Foo which has fields id and token (both are unique, both have db indexes). I want to be able to get to Foo 1 by going to url "/admin/foos/token-of-foo-1" in addition to being able to use the url "/admin/foos/1". I know that I will need to do something like the below in my routes.rb, but I'm having trouble. Help?
ActiveAdmin.routes(self) # keep this
get 'admin/???', to: 'admin/???' # add ...something
Hmm, some thoughts:
#admin/foo.rb
controller do
def find_resource
if params[:id].length == 16 # it's a token
end_of_association_chain.find_by_token(params[:id])
else
end_of_association_chain.find(params[:id])
end
end
end
For this kind of purposes I use this gem called FriendlyID. You can select which fields should form the URL slug, in your case the field token:
class Foo < ApplicationRecord
extend FriendlyId
friendly_id :token, use: :slugged
end
Let me know if you have more doubts on how to configure. It has a great integration with Active Admin.

Using previous_changes in after_save callback with rails_admin

I have an after_save callback that uses the previous_changes method to test for field change. It works fine when I save a record in rails console, but fails to recognise that the field has changed when I try to trigger it in rails_admin.
def mailers
if previous_changes['pending_approval'] == [false, true]
ProjectMailer.project_owner_new_project(self).deliver_later
Rails.logger.debug("Changed Pending to False!!".green)
end
Rails.logger.debug("hit project mailer callback".red)
end
In this example, when run in console (with a record that has just changed pending_approval from true to false) I get two logged messages; "changed pending to False!!" and "hit project mailer callback"
When I change the state in rails_admin, I only get the second "hit project mailer callback." This leads me to believe that rails_admin is doing something that interferes with the Dirty class handling of my record. Is there a better way to do this?
One way to do this is to change the after callback to before and use self_attribute_changed?(from: true, to: false) like this:
def mailers
if self.pending_approval_changed?(from: true, to: false)
ProjectMailer.project_owner_new_project(self).deliver_later
Rails.logger.debug("Changed Pending to False!!".green)
end
Rails.logger.debug("hit project mailer callback".red)
end
I suppose another way that would utilize the same strategy would be to use the around_save like so
def mailers
changed = self.pending_approval.changed?(from: true, to: false)
yield
if changed
ProjectMailer.project_owner_new_project(self).deliver_later
Rails.logger.debug("Changed Pending to False!!".green)
end
Rails.logger.debug("hit project mailer callback".red)
end
I tried the first way and it worked well for my situation. Haven't tested the second way.

Downloading all records of a paginated resource in ActiveAdmin

I am trying to use the collection action "download_csv" already present in Active Admin to download all the records of a resource. However, the action only downloads the contents of the present page. As I have paginated the resource, the data needs to be downloaded from all the pages. Any suggestions on how I can approach this problem ?
Just for future googlers. My fix (working for the current Master 1.0.0pre) is to add the following to config/initializers/active_admin.rb:
module ActiveAdmin
class ResourceController
module DataAccess
# needed for current active admin master
def max_per_page
30_000
end
def per_page
return 30_000 if %w(text/csv application/xml application/json).include?(request.format)
return max_per_page if active_admin_config.paginate == false
#per_page || active_admin_config.per_page
end
end
end
end
Replace the max as needed. This works for csv, xml and json downloads.
This answer might be a little late but hopefully it can help somebody.
Here is how I have this working in my current app:
csv do
ModelName::ATTR_ADMIN_EXPORT.each do |sym|
column sym
end
end
And in the model I have ATTR_ADMIN_EXPORT defined like this:
ATTR_ADMIN_EXPORT = [:name, :created_at]
Let me know if this works for you!
You could try this
before_filter :only => :index do
#per_page = 10 unless request.format == 'text/csv'
end
This code will also make the pagination dynamic.
before_filter :only => :index do
if params[:pag].blank?
#per_page = AdminSetting.where(:name => 'JobPagination').first.value.to_i unless request.format == 'text/csv'
else
#per_page = 10
end
end

Cakephp get details about security component error

I am using security component in my projects and is there any way to get the detailed description about the error while developing ? For ex:- if any field is added in view without using cakephp's form method, it is returning error as 'auth' in my blackHoleCallback function. Instead I need beacuse of what reason it returned that error. Because it is taking so much time to rectify the problem. Is there any way to get the detailed error description ?
All you have to do is look in the right place
Check your app/tmp/logs/error.log file
If you look in the error log you'll see an entry like this:
2013-03-16 17:24:29 Error: [BadRequestException] The request has been black-holed
#0 root/lib/Cake/Controller/Component/SecurityComponent.php(228): SecurityComponent->blackHole(Object(FacebookUsersController), 'csrf')
#1 [internal function]: SecurityComponent->startup(Object(FacebookUsersController))
#2 root/lib/Cake/Utility/ObjectCollection.php(130): call_user_func_array(Array, Array)
#3 [internal function]: ObjectCollection->trigger(Object(CakeEvent))
#4 root/lib/Cake/Event/CakeEventManager.php(246): call_user_func(Array, Object(CakeEvent))
#5 root/lib/Cake/Controller/Controller.php(670): CakeEventManager->dispatch(Object(CakeEvent))
#6 root/lib/Cake/Routing/Dispatcher.php(183): Controller->startupProcess()
#7 root/lib/Cake/Routing/Dispatcher.php(161): Dispatcher->_invoke(Object(FacebookUsersController), Object(CakeRequest), Object(CakeResponse))
#8 root/app/webroot/index.php(96): Dispatcher->dispatch(Object(CakeRequest), Object(CakeResponse))
#9 {main}
Read the error that is on screen
If you are in debug mode, this error is also shown on screen when the error happens. e.g.:
The request has been black-holed
Error: The requested address '/admin/fooby/edit/1' was not found on this server.
Stack Trace
CORE/Cake/Controller/Component/SecurityComponent.php line 228 → SecurityComponent->blackHole(FacebookUsersController, string)
[internal function] → SecurityComponent->startup(FacebookUsersController)
CORE/Cake/Utility/ObjectCollection.php line 130 → call_user_func_array(array, array)
[internal function] → ObjectCollection->trigger(CakeEvent)
CORE/Cake/Event/CakeEventManager.php line 246 → call_user_func(array, CakeEvent)
CORE/Cake/Controller/Controller.php line 670 → CakeEventManager->dispatch(CakeEvent)
CORE/Cake/Routing/Dispatcher.php line 183 → Controller->startupProcess()
CORE/Cake/Routing/Dispatcher.php line 161 → Dispatcher->_invoke(FacebookUsersController, CakeRequest, CakeResponse)
APP/webroot/index.php line 96 → Dispatcher->dispatch(CakeRequest, CakeResponse)
Handling csrf errors
With the details of a specific error (i.e. the data you are posting, and the exact token data in your session at the time) it would be possible to answer what problem brought you here, in the absense of that:
look at the line throwing the error.
In the stack trace above, the error is coming from CORE/Cake/Controller/Component/SecurityComponent.php line 228 - Open the file and look what that code is:
if ($isPost && $isNotRequestAction && $this->csrfCheck) {
if ($this->_validateCsrf($controller) === false) {
return $this->blackHole($controller, 'csrf');
}
}
What should be obvious from this is that the function _validateCsrf is responsible for the request being blackholed. This should not really be much of a surprise.
Look at the source of that function:
protected function _validateCsrf(Controller $controller) {
$token = $this->Session->read('_Token');
$requestToken = $controller->request->data('_Token.key');
if (isset($token['csrfTokens'][$requestToken]) && $token['csrfTokens'][$requestToken] >= time()) {
if ($this->csrfUseOnce) {
$this->Session->delete('_Token.csrfTokens.' . $requestToken);
}
return true;
}
return false;
}
Depending on why that function returns false, determines how you continue to debug.
Correct configuration of the component
The inevitable consequence of debugging a CSRF error is you'll need to modify the configuration of the Security component.
Do you, for example, want to be reusing tokens, because your app is submitting the same form multiple times between page loads?
Are you self-invalidating the form requests by adding new fields to the form data - You can use the unlockedFields property to exclude these fields from the csrf checks.
You can also simply disable CSRF checks completey. That has obvious security consequences - but if you're struggling to work with the component, it's an easy way to work around and problems you currently face.
In order to see the mechanisms I dug into the code to see how the FormHelper hash is created vs. how the SecurityComponent validation checks the hash. Here's how to see exactly what is happening behind the scenes.
Checking the input to the FormHelper. Open CORE/Cake/View/Helper/FormHelper.php. In the secure() function add some pr lines around the $files=Security::hash line to see how the tokens are built:
pr($fields);//hashed into computed token on next line
$fields = Security::hash(serialize($fields) . $unlocked . Configure::read('Security.salt'), 'sha1');
pr($unlocked); //hashed into computed token
pr(Configure::read('Security.salt')); //hashed into computed token
pr($fields); //computed token passed via hidden token field in form
Check how form is processed
Now check how the submitted form is processed and compared to the passed token:
Open the CORE/Cake/Controller/Component/SecurityComponent.php. Insert some pr lines in the _validatePost() routine at the end:
pr($fieldList); //hashed into computed token
pr($unlocked); //hashed into computed token
pr(Configure::read('Security.salt')); //hashed into computed token
pr($token); //passed token from FormHelper
pr($check); //computed token
Hopefully this helps someone else who has problems with locked/unlocked or missing fields quickly figure out what is going on inside of your cake.
Remember also that you have to have an exact match between the Token generated by the FormHelper and that retrieved bu cake using Session. The mismatch can happen, as the doc says, when you dynamically generate input or when make ajax call: remember to serialize the form and submit it via ajax!
If you have input tag generated not generated by using the FormHelper, you have to unlock'em. For example in your beforeFilter():
$this->Security->unlockedFields =
array('MyModel.some_field1','MyModel.some_field2')
where field1 and field2 are fields generated "by hand", i.e. by not using the Helper.
To answer the question: "Is there any way to get the detailed error description?"
First thing is to add more valuable debugging to your controller when it comes to SecurityComponent. Here's one way to do it:
public function beforeFilter() {
parent::beforeFilter();
//your beforeFilter code
//Enable CSRF and other protections
$this->Security->csrfExpires = '+1 hour';
$this->Security->csrfUseOnce = true;
$this->Security->blackHoleCallback = 'blackhole';
}
public function blackhole($errorType) {
$errorMap['auth'] = 'form validation error, or a controller/action mismatch error.';
$errorMap['csrf'] = 'CSRF error.';
$errorMap['get'] = 'HTTP method restriction failure.';
$errorMap['post'] = $errorMap['get'];
$errorMap['put'] = $errorMap['get'];
$errorMap['delete'] = $errorMap['get'];
$errorMap['secure'] = 'SSL method restriction failure.';
$errorMap['myMoreValuableErrorType'] = 'My custom and very ' .
'specific reason for the error type.';
CakeLog::notice("Request to the '{$this->request->params['action']}' " .
"endpoint was blackholed by SecurityComponent due to a {$errorMap[$errorType]}");
}
As AD7six mentioned take a look at the CORE/Cake/Controller/Component/SecurityComponent.php. Specifically SecurityComponent::startup(). In that method you will notice that SecurityComponent::blackhole() method is ran a few times. It's ran whenever the criteria fails a security check and looks like this:
return $this->blackHole($controller, 'auth');
In this case 'auth' represents the type of security check that failed. You could customize the 'auth' string to be more valuable. For example instead of 'auth' use 'myMoreValuableErrorType' and then map that to something more meaningful.
So instead of running $this->blackHole($controller, 'auth') when a security check fails, you would run $this->blackHole($controller, 'myMoreValuableErrorType') and then map 'myMoreValuableErrorType' to a specific reason on why it failed by using the code above.

Make semantic_errors render the exact error-message

I have a model Camping which has_many Images. At least one image is required on Camping:
class Camping < ActiveRecord::Base
attr_accessible :images_attributes
has_many :images
validates_presence_of :images, :message => "At least one image is required"
accepts_nested_attributes_for :images, :allow_destroy => true
end
Then, in active_admin, which uses formtastic, I render the error message At least one image is required, with f.semantic_errors:
ActiveAdmin.register Camping do
form :html => { :multipart => true } do |f|
f.semantic_errors :images
#....
f.inputs "Images" do
f.has_many :images do |img|
#....
end
end
#....
end
end
This renders as:
Images At least one image is required.
How can I make it render: At least one image is required?
changing the f.semantic_errors :images into 'f.semantic_errors (removing :images) makes it render nothing; no error at all.
Note: The API documentation seems to imply that Formtastic always adds the :attribute name to the error; but I am not entirely sure how this code works.
If you want to use such custom messages you can add error messages that are related to the object’s state as a whole, instead of being related to a specific attribute
Change this
validates_presence_of :images, :message => "At least one image is required"
to something like
validate :should_have_images
def should_have_images
errors.add(:base, "At least one image is required") if images.blank?
end
If you want to use such custom messages you can add new method to Formtastic::Helpers::ErrorsHelper As follows
create new file at config/initializers/errors_helper.rb
Place following code to file
module Formtastic
module Helpers
module ErrorsHelper
def custom_errors(*args)
return nil if #object.errors.blank?
messages = #object.errors.messages.values.flatten.reject(&:blank?)
html_options = args.extract_options!
html_options[:class] ||= 'errors'
template.content_tag(:ul, html_options) do
messages.map do |message|
template.content_tag(:li, message)
end.join.html_safe
end
end
end
end
end
In activeadmin form use
f.custom_errors instead of f.semantic_errors *f.object.errors.keys

Resources