I’m following the (now slightly outdated) Meet Rails 3 tutorial from PeepCode, and am having trouble getting a couple of the tutorial’s suggestions to work together with Rails 3.2.
The tutorial has you create a Role model that belongs to a Project:
class Role < ActiveRecord::Base
belongs_to :project
validates :project_id, :presence => true
attr_protected :project_id
end
The routes.rb file nests Role resources such that you must work with a Role in the context of a Project:
resources :projects do
resources :roles
end
Note in the model code above, the tutorial advises you to use attr_protected to protect the :project_id field, because it can be set “more securely” by creating every Role in the context of a project, like this in roles_controller.rb:
class RolesController < ApplicationController
⋮
def create
@role = project.roles.new(params[:role])
⋮
The problem is, the HTML form for creating a Role, which is created with Formtastic, contains a project_id field for selecting the project. Therefore, when project.roles.new(params[:role]) tries to use the parameters from the form to populate the new Role object, it tries to set the project_id using mass assignment, and fails with:
ActiveModel::MassAssignmentSecurity::Error in RolesController#create
Can’t mass-assign protected attributes: project_id
What is the accepted way to implement this? Was protecting the project_id attribute a bad idea? Or is there some way to populate the new Role with the form data without including project_id?
If you are getting
projectviaparams[:project_id]rather thanparams[:role][:project_id]you could actually be setting conflicting values anyway.The reason Mass Assignment would want to protect this is to prevent a user entering in an arbitrary value for
project_idthat could allow aprojectthat isn’t under this users control. You have a couple of options.If you had an authorative
useroraccountattached to the object you could add in abefore_savecallback, such asself.project_id = nil unless user.projects.find(project_id).Since you don’t, I’d use the
project_idfrom the hash to find the project, and fall back to the route id (I’m not sure if it would beproject_idor justidoff the top of my head).The easiest thing would be to just drop the select box from the form, since they’ve selected a project when choosing to create a new role – it’s a nested resource.