When running functional tests on my controller code in a Rails 3 project, I have a fatal error; the params variable contains controller and action, and ActiveModel is not happy about it:
ActiveModel::MassAssignmentSecurity::Error: Can't mass-assign protected attributes: controller, action
/Users/phooze/.rvm/gems/ruby-1.9.3-p0/gems/activemodel-3.2.1/lib/active_model/mass_assignment_security/sanitizer.rb:48:in `process_removed_attributes'
/Users/phooze/.rvm/gems/ruby-1.9.3-p0/gems/activemodel-3.2.1/lib/active_model/mass_assignment_security/sanitizer.rb:20:in `debug_protected_attribute_removal'
/Users/phooze/.rvm/gems/ruby-1.9.3-p0/gems/activemodel-3.2.1/lib/active_model/mass_assignment_security/sanitizer.rb:12:in `sanitize'
/Users/phooze/.rvm/gems/ruby-1.9.3-p0/gems/activemodel-3.2.1/lib/active_model/mass_assignment_security.rb:228:in `sanitize_for_mass_assignment'
/Users/phooze/.rvm/gems/ruby-1.9.3-p0/gems/activerecord-3.2.1/lib/active_record/attribute_assignment.rb:75:in `assign_attributes'
/Users/phooze/.rvm/gems/ruby-1.9.3-p0/gems/activerecord-3.2.1/lib/active_record/base.rb:495:in `initialize'
/Users/phooze/Documents/rails-app/app/controllers/credentials_controller.rb:40:in `new'
The application call is to the "new" method (where the error is occurring), the code is:
# Credential#create (POST)
def create
#credential = Credential.new(params)
# ... controller continues
end
Finally, my test case:
test "should create credential" do
assert_difference('Credential.count', 1) do
post :create, { :fid => "foobarbaz", :credentials_hash => "f00ba7f00ba7", :uid => "10023", :cid => "342" }
end
assert_response :created
end
Changing my controller code to a "separate" parameter hash containing ONLY the fid, credentials_hash, uid, and cid makes it work. I'm pretty sure Rails is trying to be "nice" and provide me with addtional values for testing, but it seems to be causing problems.
Any recommendations on how to solve this?
Looks like you have set config.active_record.mass_assignment_sanitizer = :strict
in your test environment only, but not in development or production, because params always contains controller and action, in any environment.
I think the best-practice recommendation here is to always use form_for, so that you'd have your credentials in params[:credential], or, indeed, do params.slice(:fid, :uid, etc).
I'm building a REST api for this project that uses Mongoid.
I've setup the following to catch the Mongoid::Errors::DocumentNotFound exception:
rescue_from Mongoid::Errors::DocumentNotFound in my base controller
In my controller I've this query code:
#current_account.users.find(:first, :conditions => {:name => "some_name"})
The above query just returns nil. It doesn't raise the exception.
Tried with another syntax as well:
User.find(:conditions => {:name => "same"}).first
All those methods just runs where internally and afaik where doesn't raise exception, its simply returns []
So what can be the solution to this? I want partially dynamic finder but should raise the exception too?
I've met same problem today, and found another solution.
Set raise_not_found_error to false. so your config/mongoid.yml should be
development:
host: localhost
port: 10045
username: ...
password: ...
database: ...
raise_not_found_error: false
from http://mongoid.org/docs/installation/configuration.html
I believe that Mongoid will only raise a DocumentNotFound exception when using the find method by passing in an object's id (and not with conditions). Otherwise it will return nil. From the Mongoid source:
# lib/mongoid/errors/document_not_found.rb
# Raised when querying the database for a document by a specific id which
# does not exist. If multiple ids were passed then it will display all of
# those.
You will have to check manually to see if you got any results and either raise the DocumentNotFound exception yourself (not great), or raise your own custom exception (better solution).
An example of the former would be something like this:
raise Mongoid::Errors::DocumentNotFound.new(User, params[:name]) unless #current_account.users.first(:conditions => {:name => params[:name]})
Update: I haven't tested any of this, but it should allow you to make calls like (or at least point you in the right direction - i hope!):
#current_account.users.where!(:conditions => {:name => params[:name]})
Which will throw a custom Mongoid::CollectionEmpty error, if the collection returned from the query is empty. Note that it's not the most efficient solution, since in order to find out if the returned collection is empty - it has to actually process the query.
Then all you need to do is rescue from Mongoid::CollectionEmpty instead (or as well).
# lib/mongoid_criterion_with_errors.rb
module Mongoid
module Criterion
module WithErrors
extend ActiveSupport::Concern
module ClassMethods
def where!(*args)
criteria = self.where(args)
raise Mongoid::EmptyCollection(criteria) if criteria.empty?
criteria
end
end
end
end
class EmptyCollection < StandardError
def initialize(criteria)
#class_name = criteria.class
#selector = criteria.selector
end
def to_s
"Empty collection found for #{#class_name}, using selector: #{#selector}"
end
end
end
# config/application.rb
module ApplicationName
class Application < Rails::Application
require 'mongoid_criterion_with_errors'
#...snip...
end
end
# app/models/user.rb
class User
include Mongoid::Document
include Mongoid::Timestamps
include Mongoid::Criterion::WithErrors
#...snip...
end
I have a method in a model that interacts with an external video encoding service (Zencoder). It uses the zencoder_rb gem. https://github.com/zencoder/zencoder-rb
class EncodingRequest
def check_encoding_status
response = Zencoder::Job.details(self.request_id)
if response.success?
# do something
else
# do something else
end
end
end
The Zencoder::Job#details method makes a call to the Zencoder web service to find out if the video encoding is complete.
The question is how can I hijack the Zencoder::Job#details method so when it is called by check_encoding_status it will return an object that I can craft. The plan is to make that object respond to #success? in whatever way makes sense for my test.
So here's how I'd like the spec to look
it "should mark the encoding as failed if there was an error in Zencoder" do
dummy_response = Zencoder::Response.new(:body => {:state => "finished"}, :code => 200)
# code that will force Zencoder::Job#details to return this dummy_response
#encoding = EncodingRequest.new(:source_asset => #final_large, :target_asset => #final_small)
#encoding.check_encoding_status
#encoding.status.should eql "success"
end
I am currently using rspec 2.5.
I read a bit about mocks and stubs, but I am not sure it's possible to use them in this scenario.
Your help is much appreciated.
Zencoder::Job.stub_chain(:details, :success).and_return(true)
I need to pass the url for each post into user model so it can be shared to twitter. Right now I can pass attributes of the post, such as title and content, which is shared to twitter, but I can't seem to figure out how to pass the post url. Thanks in advance.
post.rb
after_commit :share_all
def share_all
if user.authentications.where(:provider => 'twitter').any?
user.twitter_share(self)
end
end
user.rb
def twitter_share(post)
twitter.update("#{post.title}, #{post.content}") #<--- this goes to twitter feed
end
I haven't tried or tested it but I guess you can do something like:
def share_all
if user.authentications.where(:provider => 'twitter').any?
user.twitter_share(title, content, post_url(self, :host => "your_host"))
end
end
Prior to that, in your model add this:
include ActionController::UrlWriter
This will make the url helper available in your model as well. You can read this to get more information about it.
Please try this as well (found it on this page again):
Rails.application.routes.url_helpers.post_url(self, :host => "your_host")
[EDIT]
I have just read your gist, what you should do is this instead:
## posts.rb
after_commit :share_all
def share_all
# note that I am using self inside the method not outside it.
url = Rails.application.routes.url_helpers.post_url(self, :host => "localhost:3000")
if user.authentications.where(:provider => 'twitter').any?
user.twitter_share(url)
end
end
Or:
include ActionController::UrlWriter #very important if you use post_url(..) directly
after_commit :share_all
def share_all
# if you use the url helper directly you need to include ActionController::UrlWriter
url = post_url(self, :host => "localhost:3000")
if user.authentications.where(:provider => 'twitter').any?
user.twitter_share(url)
end
end
It is very important that you get that url inside the share_all method and not outside it, because self has not the same value whether it's inside or outside. When it's inside the method, self references the instance of Post on which the share_all method is called. When it's outside it's the class Post itself.
I have tested those two variants and they work just well :).
I have written a Rails 3.1 engine with the namespace Posts. Hence, my controllers are found in app/controllers/posts/, my models in app/models/posts, etc. I can test the models just fine. The spec for one model looks like...
module Posts
describe Post do
describe 'Associations' do
it ...
end
... and everything works fine.
However, the specs for the controllers do not work. The Rails engine is mounted at /posts, yet the controller is Posts::PostController. Thus, the tests look for the controller route to be posts/posts.
describe "GET index" do
it "assigns all posts as #posts" do
Posts::Post.stub(:all) { [mock_post] }
get :index
assigns(:posts).should eq([mock_post])
end
end
which yields...
1) Posts::PostsController GET index assigns all posts as #posts
Failure/Error: get :index
ActionController::RoutingError:
No route matches {:controller=>"posts/posts"}
# ./spec/controllers/posts/posts_controller_spec.rb:16
I've tried all sorts of tricks in the test app's routes file... :namespace, etc, to no avail.
How do I make this work? It seems like it won't, since the engine puts the controller at /posts, yet the namespacing puts the controller at /posts/posts for the purpose of testing.
I'm assuming you're testing your engine with a dummy rails app, like the one that would be generated by enginex.
Your engine should be mounted in the dummy app:
In spec/dummy/config/routes.rb:
Dummy::Application.routes.draw do
mount Posts::Engine => '/posts-prefix'
end
My second assumption is that your engine is isolated:
In lib/posts.rb:
module Posts
class Engine < Rails::Engine
isolate_namespace Posts
end
end
I don't know if these two assumptions are really required, but that is how my own engine is structured.
The workaround is quite simple, instead of this
get :show, :id => 1
use this
get :show, {:id => 1, :use_route => :posts}
The :posts symbol should be the name of your engine and NOT the path where it is mounted.
This works because the get method parameters are passed straight to ActionDispatch::Routing::RouteSet::Generator#initialize (defined here), which in turn uses #named_route to get the correct route from Rack::Mount::RouteSet#generate (see here and here).
Plunging into the rails internals is fun, but quite time consuming, I would not do this every day ;-) .
HTH
I worked around this issue by overriding the get, post, put, and delete methods that are provided, making it so they always pass use_route as a parameter.
I used Benoit's answer as a basis for this. Thanks buddy!
module ControllerHacks
def get(action, parameters = nil, session = nil, flash = nil)
process_action(action, parameters, session, flash, "GET")
end
# Executes a request simulating POST HTTP method and set/volley the response
def post(action, parameters = nil, session = nil, flash = nil)
process_action(action, parameters, session, flash, "POST")
end
# Executes a request simulating PUT HTTP method and set/volley the response
def put(action, parameters = nil, session = nil, flash = nil)
process_action(action, parameters, session, flash, "PUT")
end
# Executes a request simulating DELETE HTTP method and set/volley the response
def delete(action, parameters = nil, session = nil, flash = nil)
process_action(action, parameters, session, flash, "DELETE")
end
private
def process_action(action, parameters = nil, session = nil, flash = nil, method = "GET")
parameters ||= {}
process(action, parameters.merge!(:use_route => :my_engine), session, flash, method)
end
end
RSpec.configure do |c|
c.include ControllerHacks, :type => :controller
end
Use the rspec-rails routes directive:
describe MyEngine::WidgetsController do
routes { MyEngine::Engine.routes }
# Specs can use the engine's routes & named URL helpers
# without any other special code.
end
– RSpec Rails 2.14 official docs.
Based on this answer I chose the following solution:
#spec/spec_helper.rb
RSpec.configure do |config|
# other code
config.before(:each) { #routes = UserManager::Engine.routes }
end
The additional benefit is, that you don't need to have the before(:each) block in every controller-spec.
Solution for a problem when you don't have or cannot use isolate_namespace:
module Posts
class Engine < Rails::Engine
end
end
In controller specs, to fix routes:
get :show, {:id => 1, :use_route => :posts_engine}
Rails adds _engine to your app routes if you don't use isolate_namespace.
I'm developing a gem for my company that provides an API for the applications we're running. We're using Rails 3.0.9 still, with latest Rspec-Rails (2.10.1). I was having a similar issue where I had defined routes like so in my Rails engine gem.
match '/companyname/api_name' => 'CompanyName/ApiName/ControllerName#apimethod'
I was getting an error like
ActionController::RoutingError:
No route matches {:controller=>"company_name/api_name/controller_name", :action=>"apimethod"}
It turns out I just needed to redefine my route in underscore case so that RSpec could match it.
match '/companyname/api_name' => 'company_name/api_name/controller_name#apimethod'
I guess Rspec controller tests use a reverse lookup based on underscore case, whereas Rails will setup and interpret the route if you define it in camelcase or underscore case.
It was already mentioned about adding routes { MyEngine::Engine.routes }, although it's possible to specify this for all controller tests:
# spec/support/test_helpers/controller_routes.rb
module TestHelpers
module ControllerRoutes
extend ActiveSupport::Concern
included do
routes { MyEngine::Engine.routes }
end
end
end
and use in rails_helper.rb:
RSpec.configure do |config|
config.include TestHelpers::ControllerRoutes, type: :controller
end