We all know about why you really, really should be using attr_accessible right? Well, if not read this write-up on mass assignment a few times.
That's all well and good, but @r38y and I have been wondering what's the best way to test this lately and here's what I've come up with:
user.rb
class User < ActiveRecord::Base has_many :orders attr_accessible :email, :password, :password_confirmationend
user_spec.rb
describe User do it {should only_mass_assign_accessible_attributes(:email, :password, :password_confirmation)}endspec_helper.rb
# Finds all setter methods for the object, # builds a new object with these methods set to an arbitrary value, # checks if the given attrs are able to be mass assigned and all others are not,# and finally returns true if there are no failures.Spec::Matchers.define :only_mass_assign_accessible_attributes do |*attrs| match do |object| setters = object.methods.map{|m| m if m.match(/[a-z].*\=$/)}.compact getters = setters.map{|s| s.gsub('=', '').to_sym} params = {} getters.each do |getter| params[getter] = 'test' end record = object.class.new(params) @shouldnt, @should = [], [] getters.each do |getter| value = record.send(getter) if value == 'test' && !attrs.include?(getter) @shouldnt << getter.to_s elsif value != 'test' && attrs.include?(getter) @should << getter.to_s end end @shouldnt.length > 0 && @should.length > 0 ? false : true end failure_message_for_should do |actual| str = "" str += "The following attributes were mass assigned even though they shouldn't have been: #{@shouldnt.to_yaml}" unless @shouldnt.empty? str += "The following attributes were not mass assigned even though they should have been: #{@should.to_yaml}" unless @should.empty? str endendIn addition to your attr_accessors created from your database columns, rails is going to add additional setters such as #orders= and #order_ids= from relationship definitions like above. This list of setter methods grabbed in the above custom matcher will look something like this:
address=, address_id=, attributes=, avatar=, avatar_content_type=, avatar_file_name=, avatar_file_size=, avatar_updated_at=, created_at=, crypted_password=, email=, id=, last_login_at=, last_login_ip=, last_request_at=, login_count=, order_ids=, orders=, password=, password_confirmation=, password_salt=, persistence_token=, updated_at=
So in the above example, the custom matcher will loop through each of these setter methods and make sure that only email, password, and password_confirmation can be mass assigned.
Remarkable is a pretty cool way to easily test validations in your Rails models, but I'm a little uncomfortable with how it tests. I believe it only looks in the model to see if you have called the specific validation; it doesn't actually test if a record is valid or not.
For example, AuthLogic does some magic and automatically adds validations to your user model in the background. If testing for presence_of, Remarkable will give you false failures because the User model does not contain "validates_presence_of" in the model. I wonder if Remarkable won't correctly test sub-classed models either?
Instead, I like to build my own custom matchers that use Factory Girl to test for presence_of:
Spec::Matchers.define :require do |attribute| match do |object| factory = object.class.name.downcase.to_sym object = Factory.build(factory, attribute => "") !object.valid? endend describe User do it "should be valid given valid attributes" do user = Factory.build(:user) user.should be_valid end it {should require(:email)} it {should require(:password)} it {should require(:password_confirmation)}endCheck out how simple it is to write custom matchers on the RSpec Wiki.