Nested attributes on Rails: Can't mass-assign protected attributes - ruby-on-rails

I've been stuck on getting nested attributes to work on Rails 3.2.14 for a while and having looked at many examples I still can't seem to get it to work. At the moment when I try to submit the form I get the following error:
ActiveModel::MassAssignmentSecurity::Error in Admin::CategoriesController#create
Can't mass-assign protected attributes: products, category_departments
Here's my code:
class Category < ActiveRecord::Base
extend FriendlyId
friendly_id :category_name, use: :slugged
attr_accessible :category_name, :products_attributes, :slug, :department_id, :category_departments_attributes
has_many :products
has_many :category_departments
has_many :departments, :through => :category_departments
validates_presence_of :category_name
accepts_nested_attributes_for :products
accepts_nested_attributes_for :category_departments
scope :department_category, lambda {|department| joins(:department, :products).where("departments.department_name" => department ) }
end
Controller:
class Admin::CategoriesController < ApplicationController
def new
#category = Category.new
#category.products.build
#category.category_departments.build
end
def create
#category = Category.new(params[:category])
if #category.save
redirect_to admin_products_path
else
flash.now[:error] = "Could not save the category"
render "new"
end
end
Form-view
So I finally solved this by removing the products fields_for and allowing the department to be selected from the products creation page.
Form:-
<h1> Create a Category </h1>
<%= form_for :category, :url => { :action => "create" }, :method => :post do |f| %>
<%= render :partial => 'form_category', :locals => {:f => f} %>
<% end %>
Partial:-
<div class="category-form-update">
<form class="form-horizontal">
<div class="control-group">
<label class="control-label">
<%= f.label :category_name %>
</label>
<div clas="controls">
<%= f.text_field :category_name %>
</div>
</div>
<div class="control-group">
<%= f.fields_for :category_departments do |builder| %>
<label class="control-label">
<%= builder.label :department, "Department" %>
</label>
<div class="controls">
<% department = Department.all.map { |dep| [dep.department_name.capitalize, dep.id] } %>
<%= builder.select(:department_id ,options_for_select(department)) %>
<% end %>
</div>
</div>
<div class="control-group">
<div class="controls">
<button class="btn">
<%= f.submit %>
</button>
</div>
</div>
</div>
What am I not doing correctly here?

I think this is as easy as adding :products and :category_departments to attr_accessible like so:
class Category < ActiveRecord::Base
extend FriendlyId
friendly_id :category_name, use: :slugged
attr_accessible :category_name, :products_attributes, :slug, :department_id, :category_departments_attributes, :products, :category_departments
...
Without checking the internals, I assume this is necessary because the nested_attributes magic uses the :products_attributes to populate :products.
EDIT:
This only revealed the actual issue which is the nested attribute naming magic didn't kick in and name the fields properly. My guess is the form_for doesn't have the category object as a parameter.

Related

Rails ActiveRecord 'slightly' complex Association

I would like to structure a music web-application's database to use the following structure.
Song(Attributes: name, year, etc(any other relevant attributes))
has many Performances
has many Performers (through Performances)
has many Artists (through Performers)
Performance(Foreign key:Song_id, Attributes: enum version: [:official, :remix, :cover])
belongs to Song
has many Performers
has many Artists (through Performers)
Performers (Foreign keys: Performance_id, Artist_id, Attributes: enum role: [:main_artist, :collaborating_artist, :featuring_artist, :versus ])
has one Song (through Performers)
belongs to Performances
belongs to Artist
Artist(Attributes: name, DOB, etc.(any other relevant attributes))
has many Performers
has many Performances (through Performers)
has many Songs (through Performances)
So that upon saving a Song, i can have all the rest of the other tables populated too, that is via the associations, however, so far i had the following implementations:
Song model:
class Song < ActiveRecord::Base
#Added to make the db more logical and ended up complicated
has_many :performances
has_many :performers, :through => :performances
has_many :artists, :through => :performers
accepts_nested_attributes_for :artists, allow_destroy: true
accepts_nested_attributes_for :performers, allow_destroy: true
accepts_nested_attributes_for :performances, allow_destroy: true
validates :name, :presence => true, uniqueness: true
validates :year, :presence => true
end
Performance model:
class Performance < ActiveRecord::Base
enum version: [:official, :remix, :cover]
belongs_to :song
has_many :performers
has_many :artists, :through => :performers
end
Performer model:
class Performer < ActiveRecord::Base
enum role: [:main_artist, :collaborating_artist, :featuring_artist, :versus]
belongs_to :artist
belongs_to :performance
has_one :song, :through => :performers #this upsets the back hairs!
end
Artist model:
class Artist < ActiveRecord::Base
#Added to make the db more logical and ended up complicated
has_many :performers
has_many :performances, :through => :performers
has_many :songs, :through => :performances
validates :name, :presence => true, uniqueness: true
end
Song controller
class SongsController < ApplicationController
before_action :find_song, only: [:show, :edit, :update, :destroy]
def index
...
end
def new
#song = Song.new
#song.performers.build
#song.performances.build
#genres = Genre.all #.map{|c| [ c.name, c.id ] }
#countries = Country.all.map{|c| [ c.name, c.id ] }
end
def create
#song = Song.new(song_params)
if #song.save
redirect_to #song, notice: 'Successfully added a song.'
else
render 'new'
end
end
def show
...
end
def update
if #song.update(song_params)
redirect_to #song, notice: 'Successfully updated the song.'
else
render 'edit', notice: 'Unable to save the changes'
end
end
def edit
...
end
def destroy
...
end
private
def song_params
params.require(:song).permit(:name, :year, artists_attributes: [ :id, :name, :DOB], performances_attributes: [:id, :version, :song_id], performers_attributes: [:id, :performance_id, :artist_id])
end
def find_song
#song = Song.find(params[:id])
end
end
new.html.erb
<div class="content">
<%= form_for #song, html: { multipart: true } do |f| %>
<% if #song.errors.any? %>
<div>
<%= #song.errors.count %>
Prevented this song from saving
<ul>
<% #song.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :name %><br>
<%= f.text_field :name, class: "input-field" %>
</div>
<div class="song-version">
<div class="field">
<%= f.fields_for :performances do |performances_for_form| %>
<%= render 'performances_fields', f: performances_for_form %>
<% end %>
</div>
</div>
<div class="performers-fields">
<h2>Artist(s)</h2>
<div class="field">
<%= f.fields_for :performers do |performers_for_form| %>
<%= render 'performers_fields', f: performers_for_form %>
<% end %>
</div>
</div>
<div class"btn">
<%= f.submit %>
</div>
<% end %>
</div>
Performance partial
<fieldset>
<div class="field">
<%= f.label :version %>
<%= f.select :version, Performance.versions.keys %>
</div>
</fieldset>
Performer partial
<fieldset>
<h2>Artists Details</h2>
<div class="field">
<%= f.fields_for :artists do |artists_for_form| %>
<%= render 'artist_fields', f: artists_for_form %>
<% end %>
</div>
<div class="field">
<%= f.label :artists_role %>
<%= f.select :role, Performer.roles.keys %>
</div>
</fieldset>
Artist partial
<fieldset>
<div class="field">
<%= f.label :name %>
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.label :DOB %>
<%= f.datetime_select :DOB, :ampm => true, :minute_step => 15 %>
</div>
</fieldset>
When I fill out all the data on the form, upon saving, only the Song's attributes and Performances attributes are saved to the database, while the Performers & Artists are never saved, neither is the connection established, my questions are:
Is this structure logically correct? If not what is it that is being incorrectly done.
Is the new, create methods of songs_controller wrongly implemented and thus, the reason for not being able to save the data appropriately?
What right and or best practices could be employed developing this type of web-application with this kind of database structure?
Or is there any other better or succinct database structure that would better be followed rather than this?

has_many :through and collection_select rails form

I have tried all of the solutions to similar problems and haven't gotten this one figured out.
I have a has_many :through relationship between 'Clinician', and 'Patient' with a joined model 'CareGroupAssignment'. None of the methods I have tried so far been able to save the clinician to patient association. I would like to have a patient be able to have multiple clinicians associated with it and clinicians will have multiple patients.
clinician.rb (simplified)
class Clinician < ActiveRecord::Base
belongs_to :care_group
has_many :patients ,:through=> :care_group_assignments
has_many :care_group_assignments, :dependent => :destroy
belongs_to :user
accepts_nested_attributes_for :user, :allow_destroy => true
end
patient.rb
class Patient < ActiveRecord::Base
belongs_to :care_group
has_many :clinicians ,:through=> :care_group_assignments
has_many :care_group_assignments
belongs_to :user
accepts_nested_attributes_for :user, :allow_destroy => true
end
care_group_assignments.rb
class CareGroupAssignment < ActiveRecord::Base
belongs_to :clinician
belongs_to :patient
end
I first tried to follow the example from Railscasts PRO #17- HABTM Checkboxes to at least start getting the data collected and to have the models set up correctly. Below is the form with the checkboxes for each clinician as described in the RailsCast, checkboxes show up and the data is sent but not stored (can't figure out why).
patient new.html.erb form
<%= form_for #patient do |form| %>
<%= form.fields_for :user do |builder| %>
<div class="form-group">
<%= builder.label "Email or Username" %>
<%= builder.text_field :email, class: "form-control" %>
</div>
<div class="form-group">
<%= builder.label :password %>
<%= builder.password_field :password, class: "form-control" %>
</div>
<% end %>
<div class="form-group">
<%= form.label :first_name %>
<%= form.text_field :first_name, class: "form-control", placeholder: "First name" %>
</div>
<div class="form-group">
<%= form.label :last_name %>
<%= form.text_field :last_name, class: "form-control", placeholder: "Last name" %>
</div>
<div class="form-group">
<% Clinician.where(care_group_id: #care_group.id).each do |clinician| %>
<%= check_box_tag "patient[clinician_ids][]", clinician.id, #patient.clinician_ids.include?(clinician.id), id: dom_id(clinician) %>
<%= label_tag dom_id(clinician), clinician.full_name %><br>
<% end %>
</div>
<%= form.button 'Create Patient', class: "btn btn-u btn-success" %>
<% end %>
Next, I tried the collection_select answer to this question. This creates a badly formatted list where only one clinician can be selected. The data seems to get sent but again doesn't save.
patient new.html.erb form
<div class="form-group">
<%= collection_select(:patient, :clinician_ids,
Clinician.where(care_group_id: #care_group.id).order("first_name asc"),
:id, :full_name, {:selected => #patient.clinician_ids, :include_blank => true}, {:multiple => true}) %>
</div>
Lastly, I copied what was done in this questions/solution. Also isn't formatted as a normal collection_select dropdown but instead a list with a boarder around it where only one clinician can be selected.
patient new.html.erb form
<div class="form-group">
<% Clinician.where(care_group_id: #care_group.id).each do |clinician| %>
<%= check_box_tag "patient[clinician_ids][]", clinician.id, #patient.clinician_ids.include?(clinician.id), id: dom_id(clinician) %>
<%= label_tag dom_id(clinician), clinician.full_name %><br>
<% end %>
</div>
None of these methods have so far been able to save the clinician to patient association.
patient_controller.rb
def new
#patient = Patient.new
#user = User.new
#patient.build_user
#care_group = current_clinician.care_group
end
def create
#patient = Patient.create(patient_params)
#patient.care_group = current_clinician.care_group
if #patient.save
redirect_to patient_path(#patient), notice: "New patient created!"
else
render "new"
end
end
def show
#patient = Patient.find_by(id: params["id"])
end
private
def patient_params
params.require(:patient).permit({:clinician_ids => [:id]},:first_name,:last_name,:user_id,:care_group_id, user_attributes: [ :email, :password, :patient_id, :clinician_id ])
end
I plan to display the clinicians associated with a patient on the patient show page:
patient show.html.erb
<strong>Shared with:</strong>
<% #patient.clinicians.each do |clinician| %>
<%= clinician.full_name %><
<% end %>
This works if I seed the database but since the data doesn't seem to be stored, nothing is showing up.
Rails 4.1.8, ruby 2.2.1p85, PostgreSQL
Thanks
Found the answer on another question I asked:
problem is this line in the controller:
params.require(:patient).permit({:clinician_ids => [:id]}...
It should be:
params.require(:patient).permit({:clinician_ids => []}...

Creating a form based on a :has_many through relationship

I am new to ruby and I am trying to put together a form that will allow you to add items to an order. The form will need to take a quantity for each item in the order.
class Order < ActiveRecord::Base
belongs_to :restaurant
has_many :selections
has_many :items, :through =>:selections;
end
class Selection < ActiveRecord::Base
belongs_to :order
belongs_to :item
end
class Item < ActiveRecord::Base
belongs_to :menu
has_many :selections
has_many :orders, :through => :selections
end
class Restaurant < ActiveRecord::Base
has_many :orders
has_many :menus
has_many :items, :through => :menus
end
class Menu < ActiveRecord::Base
belongs_to :restaurant
has_many :items
end
order controller
# GET /orders/new
def new
#order = Order.new
#restaurant.items.all.each do |item|
#order.selections.build
end
end
orders/_form.html.erb :
The form is supposed to list out the available items and allow you to enter the quantity for an item.
<%= form_for [#restaurant,#order], :html => { :class => 'form-horizontal' } do |f| %>
<div class="control-group">
<%= f.label :current_table, :class => 'control-label' %>
<div class="controls">
<%= f.text_field :current_table, :class => 'text_field' %>
</div>
</div>
<% f.fields_for :selections do |ff| %>
<div class="control-group">
<%= ff.label :quantity, :class => 'control-label' %>
<div class="controls">
<%= ff.text_field :quantity, :class => 'text_field' %>
</div>
</div>
<% end%>
<div class="form-actions">
<%= f.submit nil, :class => 'btn btn-primary' %>
<%= link_to t('.cancel', :default => t("helpers.links.cancel")),
course_orders_path, :class => 'btn' %>
</div>
<% end %>
When I try to render the page I get the following error:
undefined method `quantity' for
<ActiveRecord::Associations::CollectionProxy::ActiveRecord_Associations_CollectionProxy_Selection:0x007ffa0287cd60>
I realize this is probably because I haven't initialized any selections but I'm not entirely sure how/where I should do that. When the form is submitted I want to create an order with a selection for each of the non-empty quantities.
So my first question is how do I construct my form so that I can take a quantity for each item that I know about?
Do I need to initialize anything in the order controller to make this work?
Can you give me any advice or point me to a tutorial that shows me how to set up the create method of the order controller?
EDIT:
I added some code to the Order controller and the form, so when I render the page I no longer get an error, but none of my 'selection' fields rendered. I confirmed with some logging and the debugger that I correctly 'build' 4 selections so I would expect those form elements show up.
Any ideas would be appreciated.
Are you missing a
accepts_nested_attributes_for :nested_class
somewhere?
Also, I had to do something like
<%= form_for [#restaurant,#order] do |f| %>
...
<%= f.fields_for :selections, #order.selections do |ff| %>
...
<% end %>
...
<% end %>

Rails 3 autocomplete gem with belongs_to association

I am using the rails3-jquery-autocomplete gem and it works neat when using it for fields that exist in the model. However, I have been trying to use it for associations and I fail to see how to make it work. So let's explain what I have:
Models:
class Receipt < ActiveRecord::Base
belongs_to :user
end
class User < ActiveRecord::Base
has_many :receipts
end
Controller
class Admin::ReceiptsController < AdminController
autocomplete :user, :name
def index
#receipts = Receipt.all
end
def show
#receipt = Receipt.find_by_id(params[:id])
end
def edit
#receipt = Receipt.find_by_id(params[:id])
#users = User.all
end
end
View (form):
<%= form_for(#receipt, :url => admin_receipt_path, :html => { :multipart => true }) do |f| %>
<div class="clearfix">
<%= f.label :value, "Value($)" %>
<div class="input"><%= f.text_field :value %></div>
</div>
<div class="clearfix">
<%= f.label :user_id, "User" %>
<div class="input">
<%= f.autocomplete_field :user_id, autocomplete_user_name_admin_receipts_path %>
</div>
</div>
.....
The thing is... I am able to fetch user names, but I want to actually store the user id in there. The same way I would like to show the name when the admin tries to edit an existing receipt with a user associated. Something that I am able to do with this drop down:
<div class="clearfix">
<%= f.label :user_id, "User" %>
<div class="input"><%= f.select :user_id, #users.collect {|p| [ p.name, p.id ] },{:prompt => 'Select a User'} %></div>
</div>
I am failing to see how would I do this with this gem....
Ryan Bates did a screencast on this. Clean and well explained
Screencast
If you don't feel like subscribing try torrent

How to get fields_for to create new objects and many-to-many relationships simultaneously?

I've been following through these Railscasts and trying to amend the code so it works with Rails 3:
http://railscasts.com/episodes/73-complex-forms-part-1
http://railscasts.com/episodes/73-complex-forms-part-2
http://railscasts.com/episodes/73-complex-forms-part-3
I am trying to create Groups, Users and Memberships (the many-to-many relationships) simultaneously. People can add users to the group as they create it and then I want it to route through to a view of the group with all the members. I can get memberships to create just fine but having trouble creating users and associating them the group. My code currently looks like this:
Group.rb
class Group < ActiveRecord::Base
has_many :memberships
has_many :users, :through => :memberships
attr_accessible :group_name
def user_attributes=(user_attributes)
user_attributes.each do |attributes|
users.build(attributes)
end
end
end
Membership.rb
class Membership < ActiveRecord::Base
belongs_to :groups
belongs_to :users
end
User.rb
class User < ActiveRecord::Base
has_many :memberships
has_many :groups, :through => :memberships
end
groups_controller.rb
class GroupsController < ApplicationController
def create #todo test for number of groups they're already in before creation
#group = Group.new(params[:group])
#group.memberships.build(:user_id => current_user.id)
##group..build
respond_to do |format|
if #group.save
format.html { redirect_to(#group, :notice => 'Group was successfully created and user added...?') }
else
format.html { render :action => "new" }
end
end
end
end
My form looks like this:
Which is created by:
views/groups/new.html.rb
<h1>New group</h1>
<%= form_for(#group) do |f| %>
<fieldset>
<legend>Create a new group</legend>
<%= render 'shared/group_error_messages', :object => f.object %>
<div class="clearfix">
<%= f.label :group_name %>
<div class="input">
<%= f.text_field :group_name %>
</div>
</div>
<div id="users">
<%= render :partial => 'user', :collection => #group.users %>
</div>
</div>
<p><%= add_user_link "Add a member" %></p>
<div class="actions">
<%= f.submit :value => 'Create your group', :class => 'btn success'%>
<!-- todo test for if they already have a group...-->
</div>
</fieldset>
<% end %>
views/groups/_user.html.rb
<div class="user">
<%= fields_for :user do |f| %>
<div class="clearfix">
<%= f.label :name %>
<div class="input">
<%= f.text_field :name %>
</div>
</div>
<div class="clearfix">
<%= f.label :number %>
<div class="input">
<%= f.text_field :number %>
</div>
</div>
<%= link_to_function "remove", "$(this).up('.user').remove()" %>
<% end %>
</div>
Thanks very, very much in advance :)
You stumbled upon a fairly out of date Railscast it seems. Some age very well... this one not as much. What you're looking for is a accepts_nested_attributes_for which was introduced in rails 2.3.
Group model:
#app/models/group.rb
class Group < ActiveRecord::Base
has_many :memberships
has_many :users, :through => :memberships
accepts_nested_atrributes_for :users
end
Group Controller:
#app/controllers/groups_controller.rb
Class GroupsController < ApplicationController
def create
#group = Group.new(params[:group)
#group.save ? redirect_to(#group) : render("new")
end
end
New Group action:
<!-- Giving you the important changes only -->
<div id="users">
<%= render :partial => 'user', :collection => #group.users, :locals => { :form => f } %>
</div>
User partial:
<div class="user">
<%= form.fields_for :user do |f| %>
As a general rule of thumb, you should use more descriptive names for your form block variables, especially with nested forms.

Resources