How to selectively ignore some model validations?

Hi -

So, I have a user model, with a bunch of validations, one of which is
validates_acceptance_of :terms_of_service. I’ve decided to keep a
database column tracking this attribute, and it works just as intended
for signups (people have to check the box before they can register).

However, a user may also be created via an admin interface, which has an
entirely separate controller and view. When a user is created via this
interface, I’d like to keep most of the validations intact (ensuring
that the name and email are unique, that the password is confirmed,
etc.) but there are some validations that I’d like to skip and not be
bothered by (for example, the terms of service checkbox).

I’m not sure how to make this happen. I’ve played with the :if and
:unless options for the validation helpers, but as far as I can tell
they’re only able to see the instance variables belonging to the user
I’m validating. It would be great if I could say validates_acceptance_of
:terms_of_service, :unless => { :controller => ‘admin/users’ } or
something similar, but that doesn’t seem to be a possibility.

Does anyone have any ideas as to how I could make this work?

Thanks!
Chris

Probably the best way is to redesign the app, so validations will be
always required (eg introduce a Signup model associated with user).

But if you are looking for the simpler way then try.

in user.rb

attr_accessor :signing_up
validates …, :if => :signing_up

in users_controller.rb

def create
User.create(params[:user].merge(:signing_up => true))
end

Or if the you want to skip those validateions for all already created
users, then just use validates …, :on => :update

Dmitry

It doesn’t matter in that case cause even if a user will try to hack
it, the merging of {:signing_up => true} will override hid value.

But to make it protected in all other parts of the app that use the
User model:
User
attr_protected :signing_up

def create
@user = User.new(params[:user])
@user.signing_up = true
@user.save
end

Dmitry

Hi -

Thanks a lot, I like the simpler way you wrote out.

My question is, though, is that method vulnerable to mass-assignment
attacks? I know that if it were attr_accessible, a user would be able to
pass in a value for :signing_up and avoid having their data validated,
but I don’t know whether the same is true for attr_accessor.

Thanks again!
Chris

Dmitry S. wrote:

Probably the best way is to redesign the app, so validations will be
always required (eg introduce a Signup model associated with user).

But if you are looking for the simpler way then try.

in user.rb

attr_accessor :signing_up
validates …, :if => :signing_up

in users_controller.rb

def create
User.create(params[:user].merge(:signing_up => true))
end

Or if the you want to skip those validateions for all already created
users, then just use validates …, :on => :update

Dmitry

I’m already using attr_accessible (which I gather is a best practice),
and I don’t think it and attr_protected work together, do they?

Thanks again

No, I think they don’t work together, so just don’t list :signing_up
in the accesible attributes list.

Dmitry S. wrote:

No, I think they don’t work together, so just don’t list :signing_up
in the accesible attributes list.

It is true that you cannot use both attr_accessible and attr_protected
in the same model. Rails will throw a runtime error if both are
specified on one model.

Ok, I’ve done some more reading and I think that I have this down now.
Somebody tell me if I’m on the right/wrong path.

attr_accessible lists attributes that are open to mass-assignment. So,
for security reasons, we shouldn’t allow anything in attr_accessible
that we wouldn’t let the user define themselves.

Active Record automatically creates setter/getter methods for columns in
databases - since my users table has a “name” column, for example, I can
use @user.name in my models/views/controllers and it’ll just work.

However, when I want to use a virtual attribute (something that isn’t
persisted in the database but that I still want to manipulate in Rails,
like @user.signing_up), ActiveRecord can’t do that for me, and I have to
make setter/getter methods for that myself. I can make those with
attr_accessor, but they won’t be mass-assignable, and so they won’t be
vulnerable to mass-assignment attacks.

Finally, since the whitelist approach to security is better than the
blacklist approach, attr_protected should just be ignored.

Do I have all that right?

  1. Mass assignment doesn’t care if the attributes are genreated by
    ActiveRecord, defined using attr-accessor, or implemented explicitly.
    It just doesn’t allow some attributes to be mass assigned, so if
    the :secret is protected then
    user.attributes = {:secret => …}
    user.update_attributes(:secret => …)
    User.create(:secret => …)
    won’t work, but
    user.secret = …
    will work always for protected & for not-protected attributes.

attr_accesseble & attr_protected can be used interchangeably, just use
the one you like more, eg when User has 3 attributes: name, age &
salary, then:
attr_accessible :name, :age
is the same as
attr_protected :salary

In both case name & age will be accessible and salary will be
protected.