fields_for and has_many_through - ruby-on-rails

I have these models:
class Order < ActiveRecord::Base
has_many :order_lines
has_many :prizes, :through => :order_lines
accepts_nested_attributes_for :order_lines
end
class Prize < ActiveRecord::Base
has_many :order_lines
end
class OrderLine < ActiveRecord::Base
belongs_to :order
belongs_to :prize
end
I would like a nested form in the order form that displays every prize with a text box next to it where the user can enter pieces (eg. the amount to order). When the form is submitted the create action should create the order_lines accordingly. I can't find a solution anywhere.

First of all in the Order model use accepts_nested_attributes_for :prizes, instead of what you have.
After that it's easy, just add a form in the view (there's no additional step controller)
<%= form_for #order do |order_form| %>
..
<%= order_form.fields_for :prizes do |prizes_form| %>
<%= prizes_form.text_field :piece %>
..
<% end %>
..
<% end %>
This is straight from the documentation... You should definitely check that first.

Related

Rails nested models and child validation

I have two models.
Exemplary:
class Book < ActiveRecord::Base
has_many :pages, dependent: :destroy
accepts_nested_attributes_for :pages, allow_destroy: true
end
class Page < ActiveRecord::Base
belongs_to :book
validate_on_create :count_within_bounds
LIMIT = 200
private
def count_within_bounds
if self.book.pages.count >= LIMIT
errors.add_to_base("Number of pages cannot be greater than #{LIMIT}")
end
end
end
Now when update the book through a nested form everything is working just fine. I can edit let's say the title and add new pages. But if the page validation fails the other changes made to the book model are not getting saved either.
I understand that it's all being saved in one transaction but is there a way to persist the parent regardless without having to do it manually in two steps, i.e. saving the parent first without pages_attributes?
You can take away the validation and do something like:
<%= form_for(#book) do |f| %>
# book attribute stuff....
<% if #book.pages.count < 200 %>
<%= f.fields_for :pages, #book.pages.create do |ff| %>
<%= ff.text_field :attribute %><br>
<% end %>
<% end %>
<% end %>
Now only books with less than 200 pages will get a form that includes fields to add pages.
If you want to save the parent only and skip the validations for your Page subclass you can use the validate option in your Book model.
class Book < ActiveRecord::Base
has_many :pages, validate: false, dependent: :destroy
end

Rails where to create :through object

Hi I`m learning has_many :through and I have a association like this.
student:
has_many :subjects, through: :participations
has_many :participations
subject:
has_many :students, through: :participations
has_many :participations
belongs_to :student
participation:
belongs_to :student
belongs_to :subject
The student subjects are updated through checkboxes in update view:
= f.association :subjecs, label_method: :title, value_method: :id, label: 'Subjects', as: :check_boxes
And I went so far :( My student have subjects id, but it can`t get them since no participation is created.
My update action:
def create
student = Student.new(student_params)
if student.save
redirect_to students_path
else
render 'edit'
end
end
My question is when should I create participation object, and where is the appropriate place for the function ?
When you have a has_many relationship, you actually get a bunch of methods which can help you.
One of these is association_singular_ids- which if you populate it correctly, will automatically create the associative data you need.
The way to do this will be to use the following:
#app/views/students/new.html.erb
<%= form_for #student do |f| %>
<%= f.collection_check_boxes :subject_ids, Subject.all, :id, :name %>
<%= f.submit %>
<% end %>
I know you're using f.association (which is built through simple_form) - you'll be much better suited to using collection_check_boxes (it even explains an example of what you're having problems with).
You shouldn't need to pass the params or anything - and because your participations model acts as a join, it should be populated automatically if you use the above code.
HABTM
You may also wish to look at has_and_belongs_to_many:
#app/models/student.rb
class Student < ActiveRecord::Base
has_and_belongs_to_many :subjects
end
#app/models/subject.rb
class Subject < ActiveRecord::Base
has_and_belongs_to_many :students
end
#join table - students_subjects
This is often preferred over has_many :through because it requires less maintenance (as described in the link above).
Nested Attributes
Finally, to give you some more perspective, you'll need to know about the nested attributes aspect of Rails.
I originally thought your answer would be that you're not using accepts_nested_attributes_for, but I don't think so now. Nonetheless, you'll still gain benefit from knowing about it.
--
One of the reasons you'd use has_many through would be to populate the join model with other attributes. HABTM does not allow this; because has_many :through has a join model (in your case participations), it allows you to add extra attributes into it.
As such, if you're looking to change any of those attributes, you'll need to pass them through your various models:
#app/models/student.rb
class Student < ActiveRecord::Base
has_many :participations
has_many :subjects, through: :participations
accepts_nested_attributes_for :participations
end
#app/models/participation.rb
class Participation < ActiveRecord::Base
belongs_to :student
belongs_to :subject
accepts_nested_attributes_for :student
end
#app/models/subject.rb
class Subject< ActiveRecord::Base
has_many :participations
has_many :students, through: :participations
end
This will allow you to use the following:
#app/controllers/students_controller.rb
class StudentsController < ApplicationController
def new
#student = Student.new
#student.participations.build
end
def create
#student = Student.new student_params
#student.save
end
private
def student_params
params.require(:student).permit(:student, :params, participations_attributes: [subject_attributes:[]])
end
end
This should allow you to use the following:
#app/views/students/new.html.erb
<%= form_for #student do |f| %>
<%= f.fields_for :participations do |p| %>
<%= p.text_field :name %>
<%= p.collection_check_boxes :subject_id, Subject.all, :id, :name %>
<% end %>
<%= f.submit %>
<% end %>
My problem was that I didn`t permited the chenge od subject_ids in the student controller:
params.require(:student).permit(:first_name, :last_name, subject_ids: [])

How to setup a deeply nested form in Rails 3.2

I'm trying to build a rather complex nested form in rails and am stuck.
Basically, I have three models - Applicant, DataPoint, ApplicantDataPointValue .
The user can create a new DataPoint, give it a name ("gender" etc.) select it's type ("string","integer" etc.). The type determines what column the data will eventually be saved in in the ApplicantDataPointValue table.
I then want the user, when they're creating a new Applicant, to be able to add a value for each DataPoint into the ApplicantDataPointValue table
My models look like the following:
Applicant:
class Applicant < ActiveRecord::Base
has_many :applicant_data_point_values, dependent: :destroy
has_many :data_points, :through => :applicant_data_point_values
accepts_nested_attributes_for :data_points
accepts_nested_attributes_for :applicant_data_point_values
attr_accessible :data_points_attributes, :applicant_data_point_values_attributes
end
DataPoint:
class DataPoint < ActiveRecord::Base
has_many :applicant_data_point_values
has_many :applicants, :through => :applicant_data_point_values
accepts_nested_attributes_for :applicant_data_point_values
end
ApplicantDataPointValue:
class ApplicantDataPointValue < ActiveRecord::Base
belongs_to :data_point
belongs_to :applicant
end
But I'm at a loss to what to do in the 'new' and 'create' sections of my controller or how to construct the form.
Any insight would be greatly appreciated.
From what I understand, the form for the User will also have multiple ApplicantDataPointValue fields. (but that form won't allow creating of new DataPoint fields, right?)
In the controller new action, you'll want to set up your model with associated data point values:
def new
#user = User.new
DataPoint.all.each do |data_point|
applicant_data_point_value = #user.applicant_data_point_values.build
applicant_data_point_value.data_point = data_point
end
end
And then, display a text box for each data point value.
<%= form_for #user do |f| %>
<%= f.label :name %>
<%= f.text_field :name %>
<% #user.applicant_data_point_values.each do |data_point_value| %>
<%= f.fields_for :applicant_data_point_values, data_point_value do |fields| %>
<%= fields.label :value, data_point_value.data_point.type %>
<%= fields.text_field :value %>
<% end %>
<% end %>
Reference: http://railscasts.com/episodes/196-nested-model-form-part-1

How do I save many-to-many relationships in a model

I have the following setup in my rails app:
A user registers and he chooses from a set of check boxes for Music Styles.
The Music Styles are only 4 right now but should be extendable. I'd like to have a list of MusicStyles that I can extend and change easily.
My approach would be to create a model 'MusicStyles' and a model 'UserMusicStyles' and then use a has_many_through association similar to:
class User < ActiveRecord::Base
has_many :user_music_styles
has_many :music_styles, :through => :user_music_styles
end
class UserMusicStyle < ActiveRecord::Base
belongs_to :user
belongs_to :music_style
end
class MusicStyle < ActiveRecord::Base
has_many :music_styles
has_many :users, :through => :user_music_styles
end
Now, during registration I would do something like MusicStyle.all.each do |m| ... to display the checkboxes but how do I save it to the database correctly in the user controller?
Any help much appreciated!
You can do it like this:
<%= form_for #user do |f| %>
<!-- User stuff -->
...
<% MusicStyle.all.each do |m| %>
<%= check_box_tag('user[music_style_ids][]', m.id, #user.music_styles.include?(m)) %>
<% end %>
<%= f.submit 'Save' %>
<% end %>

Rails 3 Data Modeling Help - Has Many, Belongs to, Nested Atrributes

I am working on a project involving three models (recipient, award, announcer) and need to have a nested attributes when issuing an award by an announcer to multiple recipients. For an example, award form need to have the ability to do 3 things:
Can add multiple-recipients (i.e. "add recipient", "remove recipient") - nested attributes
After creating a new award, the award will be posted into recipient's profile.
Enables future polling of #recipient.awards and #announcer.awards
Really struggle in terms of how to smartly solve this problem. The following data structure kind of made sense, however can not do "accepts_nested_attributes_for :recipients" in the award form. Can you help? Many thanks in advance.
class Recipient < ActiveRecord::Base
has_many :awards
has_many :announcers, :through => :awards
end
class Announcer < ActiveRecord::Base
has_many :awards
has_many :recipients, :through => :awards
end
class Award < ActiveRecord::Base
belongs_to :announcer
belongs_to :recipient
end
You're just about there. The main issue is that you're trying to create recipient objects in the form rather than just creating a relationship between the award and another object (user). You could do something like this:
class User < ActiveRecord::Base
has_many :recipients
has_many :awards, :through => :recipients
end
# this is your relationship between an award and a user
class Recipient < ActiveRecord::Base
belongs_to :user
belongs_to :award
end
class Award < ActiveRecord::Base
has_many :recipients
has_many :users, :through => :recipients
belongs_to :announcer
accepts_nested_attributes_for :recipients, :allow_destroy => true
end
class Announcer < ActiveRecord::Base
has_many :awards
has_many :recipients, :through => :awards
end
Then you would just do a nested form that would build the recipients_attributes array:
<%= form_for #award do |f| %>
<%= f.text_field :name %>
<div id="recipients">
<% #award.recipients.each do |recipient| %>
<%= render :partial => '/recipients/new', :locals => {:recipient => recipient, :f => f} %>
<% end %>
</div>
<%= link_to_function 'add recipient', "jQuery('#recipients').append(#{render(:partial => '/recipients/new').to_json})" %>
<% end %>
And, to keep it DRY just push the nested part into a partial:
# app/views/recipients/_new.html.erb
<% recipient ||= Recipient.new %>
<%= f.fields_for 'recipients_attributes[]', recipient do |rf| %>
<%= rf.select :user_id, User.all %>
<%= fr.check_box '_delete' %>
<%= fr.label '_delete', 'remove' %>
<% end %>
Obviously the User.all call isn't ideal so maybe make that an autocomplete.

Resources