1

I am using devise for authentication but have also added a display_name field that I don't want the user to be able to edit after creating registration/sign up. The problem is unless I add this column/field with attr_accessible in the model, I get the following error.

Can't mass-assign protected attributes: display_name

But I can't enable attr_accessible because that will open the app up to mass assignment hack in through the edit method.

I thought about overriding Devise::RegistrationsController to supplement devise's create method, as shown in this gist, but filtering the params before calling super will obviously result in the display_name not being saved at all, and the field would show up empty in a form after validation fails for any reason.

Is there method I can override or a hook I can add into the model to prevent mass assigment of a single variable in certain conditions? Perhaps I can remove the display_name from the parameter list before_validation and put it back after_validation, while also validating it with a technique like this, and then doing a normal validation-less single assignment? Of course this still wouldn't prevent a mass assignment hack through the edit method, so I'd have to add a signature to params[:user] hash in the create method, which would be checked before removing/adding display_name from params when validating/updating the display_name attribute by itself. This seems like a mess to me, is there a better way to control mass assignment in the controller while using devise?

One approach that would be quick and dirty would be to override Devise::RegistrationsController like I mentioned first and simply cut and paste the original create method from the original devise registrations_controller.rb source, and manually add/remove display_name from params[:user] before and after the build_resource(sign_up_params) call. Just before the save call, I'd also have to add display_name individually to the model object and use single attribute validation hackery mentioned above if I want validation on display_name to work. If I update devise in the future this approach has a high probability of breaking the create method, so I've ruled it out.

After typing out these various approaches I've realized I will probably override the edit method in Devise::RegistrationsController and raise an error if params[:user][:display_name] or any other attribute not in attr_accessible is set. This sort of blacklist in multiple places approach also rubs me the wrong way, it will be too easy to forget adding this to new methods during future expansion, even if the blacklist is kept DRY in a separate private method. Still, this appears to be the least convoluted way.

Is there another approach worth trying?

Community
  • 1
  • 1
emice
  • 11
  • 1
  • 1
    Is the upgrade to Rails 4 on your roadmap? If so, consider in the meantime using https://github.com/rails/strong_parameters to solve this issue. strong_parameters requires a lot more boilerplate than attr_accessible, but lets you execute arbitrary ruby in your policy methods so you can, for example, permit some attributes if the user hasn't been persisted yet and other attributes if the user has been persisted. Also, the policy methods are often per-controller anyway so your devise controller could have a different method than your users controller. – Gabe Kopley Aug 14 '13 at 00:12
  • This looks nice, reading the docs now. Thanks. I'm on Rails 3, but have just begun this project. I was worried about possibility of it being hard to find compatible gems, and documentation, with the newer version. I will probably try a over before this project gets too large, after I look into how mature it is at this point. – emice Aug 14 '13 at 00:37
  • Oh gosh, in that case I *strongly* recommend starting a fresh Rails 4 app! Rails 4 is in good shape, and it's much trickier to upgrade your app than starting fresh :/ – Gabe Kopley Aug 14 '13 at 00:46

2 Answers2

0

Mass assignment protection only protects from assigning parameter via methods like create, save or assign_attributes, which all take a hash of arguments as an argument. You should still be able to use

user.display_name = value
user.save

in your create action.

BroiSatse
  • 44,031
  • 8
  • 61
  • 86
  • Unfortunately it is tricky for me to get to the user object in the controller because that code is in the devise engine. I'd have to cut and paste devise's create method after subclassing Devise::RegistrationsController, something I mentioned I didn't want to do in my post. – emice Aug 14 '13 at 00:46
  • Have you tried this solution: http://stackoverflow.com/questions/3546289/override-devise-registrations-controller – BroiSatse Aug 14 '13 at 07:47
0

If I don't misunderstand, your goal is just that forbids to modify display_name when updates the user.

You can put display_name in attr_accessible and have a hack in before_update:

class User

  before_update :bu

  def bu
    if changed? && changed_attributes['display_name']
      self.display_name = changed_attributes['display_name']
    end
  end
end

Prevent display_name to be modified, but there is no way to modify display_name after user is created.

You probably need to add an attribute as a switch and improve before_update to avoid this situation.

Bigxiang
  • 6,252
  • 3
  • 22
  • 20