I have previously used has_and_belongs_to_many associations in my older Rails apps but am moving to using has_many, :through for new and current apps. However, I believe I am missing something central to the has_many, :through association in Rails 3.
Currently, I am building an app for our town's volunteer fire department. When we have a meeting, we want to check off whether or not a fire fighter is present, excused, or absent.
My models:
#FireFighter Model
class FireFighter < ActiveRecord::Base
has_many :attendance_meetings
has_many :meetings, :through => :attendance_meetings
accepts_nested_attributes_for :meeting
#Meeting Model
class Meeting < ActiveRecord::Base
has_many :attendance_meetings
has_many :fire_fighters, :through => :attendance_meetings
accepts_nested_attributes_for :fire_fighter
#AttendanceMeeting Model
class AttendanceMeeting < ActiveRecord::Base
attr_accessor :status # this is the added property on the join model that we need populated
belongs_to :fire_fighter
belongs_to :meeting
The problem I'm having is how to set this up in the view. Currently, I have:
= hidden_field_tag "meeting[fire_fighter_ids][]"
-#meeting.fire_fighters.each do |fire_fighter|
= fire_fighter.fire_fighter_name
%span.radio_btn= radio_button_tag :fire_figher_ids, fire_fighter.id, "present"
present
%span.radio_btn= radio_button_tag :fire_figher_ids, fire_fighter.id, "excused"
excused
%span.radio_btn= radio_button_tag :fire_figher_ids, fire_fighter.id, "absent"
absent
%br
This will spit out each fire fighter and three radio buttons for each fire fighter (with options for present, excused, or absent). However, the name of the resulting radio buttons are all the same, so you can only pick one for all fire fighters.
As I noted above, I feel certain I am missing something basic but I am stumped. I've read through tons of SO questions and The Rails 3 Way book's sections on ActiveRecord. Any suggestions or directions would be most appreciated. Thank you!
It should be something like:
class Meeting < ActiveRecord::Base
has_many :attendance_meetings
has_many :fire_fighters, :through => :attendance_meetings
accepts_nested_attributes_for :attendance_meetings
# view
= form_for #meeting do |f|
= f.fields_for :attendance_meetings do |f_a_m|
= f_a_m.object.fire_fighter.name
= f_a_m.check_box :status, 'present'
= f_a_m.check_box :status, 'excused'
= f_a_m.check_box :status, 'absent'
For the approach you're taking you need to build the association for each firefighter with the meeting. Something like:
#meeting.firefighters << Firefighter.all
This doesn't seem particularly optimal however. I would form the join with a boolean :status for those who are absent (t) or excused (f) with present not included, imagine it like meeting_absentees. Seems backward but in this way your table will have far fewer rows.
Related
I have the following models:
class Product < ActiveRecord::Base
has_many :product_recommendation_sets, :dependent => :destroy
has_many :recommendation_sets, :through => :product_recommendation_sets
end
class RecommendationSet < ActiveRecord::Base
has_many :product_recommendation_sets, :dependent => :destroy
has_many :products, :through => :product_recommendation_sets
has_many :recommendations
end
class Recommendation < ActiveRecord::Base
belongs_to :recommendation_set
end
And am adding recommendations recommendations_set like so:
p = Product.find_by_wmt_id(product) || Product.create( ItemData.get_product_data(product) )
recommendation = find_by_rec_id(rec_id) || create( ItemData.get_product_data(rec_id) )
rec_set = RecommendationSet.find_or_create_by_rating_set_id_and_model_version_and_product_id(rating_set.id, model_version, p.id)
sec_set.update_attributes(
:rating_set_id => rating_set.id,
:product_id => p.id,
:model_version => model_version,
:notes => note
)
sec_set.recommendations << recommendation
sec_set.save
prs = ProductRecommendationSet.find_or_create_by_recommendation_set_id_and_rating_set_id_and_product_id(rec_set .id, rating_set.id, p.id,)
prs.update_attributes(
:recommendation_set_id => rec_set.id,
:rating_set_id => rating_set.id,
:product_id => p.id
)
This works as expected, however my problem is that I have multiple recommendation_sets which belong to multiple products, and each of the recommendation_sets may have the same recommendation. By saving each recommendation to a recommendation_set as I am currently doing, if two recommendation_sets have the same recommendation, only one of the sets will add that recommendation. Is there anyway of saving each recommendation to multiple recommendation_sets using a secondary id, such as save by recommendation_id_and_product_id, or would I need to change this releationship to a has_many :through?
Based on your clarification, I think you basically have a many-to-many relationship between RecommendationSet and Recommendation. Presently, you have a one-to-many.
There are a couple of options:
Use the has_and_belongs_to_many method in both models to describe the relationship;
Manually create a "join" model and then give both RecommendationSet and Recommendation a has_many to this join model (with two corresponding belongs_to lines in the join model pointing to the other two models);
A has_many ... :through style, like you mentioned
Note that the first two options require you to have a join table.
If you require additional information on the join table/model, I tend to go with the 2nd option. Otherwise, either the first or third are perfectly valid.
Ryan Bates of RailsCasts made an episode about this here: http://railscasts.com/episodes/47-two-many-to-many
And some more information from the Rails documentation: http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#label-Many-to-many
In short, if you don't need extra info on the join, I think your idea of the has_many ... :through is perfectly fine.
Let me know whether that helps
So, this:
p = Person
.joins('join organization o on o.id = organization_id')
.where('o.id' => 1)
.select('person.*')
.first!
p.name = 'hi!'
p.save!
works as expected, saving the person's name.
But, how would I do this:
p.organization.name = 'bye!'
p.save!
I can't figure out the right projection to get the organization fields to map (or if it's possible). I've tried '*' and 'organization.name as "person.organization.name"'.
In order for what you're doing to work, you have to set the autosave option to true in your belongs_to :organization association.
belongs_to :organization, autosave: true
or just call save on the organization
p.organization.name = 'Org Name'
p.organization.save
You have to declare association in your Person class, using
belongs_to
has_one
has_many
has_many :through
has_one :through
or has_and_belongs_to_many,
and Rails will do the join by it self and link your both class together.
Let me paste here a section of Rails guide:
With Active Record associations, we can [..] tell Rails that there is a connection between the two models. Here’s the revised code for setting up customers and orders:
class Customer < ActiveRecord::Base
has_many :orders, :dependent => :destroy
end
class Order < ActiveRecord::Base
belongs_to :customer
end
With this change, creating a new order for a particular customer is easier:
#order = #customer.orders.create(:order_date => Time.now)
I suggest you read the complete guide here: http://guides.rubyonrails.org/association_basics.html
I've got my models setup for a many-to-many relationship:
class Workshop < ActiveRecord::Base
has_many :workshop_students
has_many :students, :through => :student_workshops
accepts_nested_attributes_for :students
end
class Student < ActiveRecord::Base
has_many :student_workshops
has_many :workshops, :through => :student_workshops
accepts_nested_attributes_for :products
end
class StudentWorkshop < ActiveRecord::Base
belongs_to :student
belongs_to :workshop
end
As you can see above, a student can have many workshops and workshop can have many students.
I've looked at the following Rails casts: here and here. And most of the online sources I stumble across only show how to do nested forms for creating new objects within the parent form.
I don't want to create a new object. I only want to add an existing object to the parent form. So for example. If I decide to create a new workshop, I'd like to assign existing students to the workshop.
One thing I don't understand is, how do I link students into the workshop form? Second, when the params are passed, what should be in the controller method for update/create?
If anyone can point me to the right direction, I would appreciate it.
The easiest thing to do is:
<%= f.collection_select(:student_ids, Student.all, :id, :name, {:include_blank => true}, {:selected => #workshop.student_ids, :multiple => true} )%>
You should not have to do anything in the create action.
Ok, for anyone coming across the same issue in the future. The solution I came up with was in def create. I am able to access a POST attribute called student_ids, which comes in the form of an array
Here's my model set up.
Band Model
has_many :bands_genres
has_many :genres, :through => :bands_genres
Genre Model
has_many :bands_genres
has_many :bands, :through => :bands_genres
BandsGenre Model
belongs_to :band
belongs_to :genre
I have a form where you can add a new band and then select a genre from a dropdown field that pulls from the pre-set genres in the genre model.
So what I ultimately need to do is set up a form so that when a band adds their band and select a genre, it creates the correct join in the bands_genre model.
Not sure where to start with setting up the form, controllers and models for this.
I'm running Rails 3.0.3
There are quite a few text/video casts covering this, since its a popular use case. I would encourage you to look at:
http://railscasts.com/episodes/73-complex-forms-part-1 or its equivalent asciicast (which is a text based cast of the video).
Further I would recommend you use formtastic. Associations are managed automatically so it makes form building trivial and keeps your code tidy. And yes there are casts for that too.
http://railscasts.com/episodes/184-formtastic-part-1
Edit:
Band Model
has_many :genres, :as => :band_genres
Genre Model
has_many :bands, :as => :band_genres
Your genre table has a band_id, and your band table has a genre_id.
bands_controller
def new
#genres = Genre.all
#post = Post.new
end
posts/new.html.haml
(This part I'm a little unsure of, but it roughly goes like this)
- form_for #post do |f|
= f.select :genre_id, #genres, {}
= f.submit
I'm working on a add to cart form. It looks something like this
#Models
Order.rb
has_many :line_items
accepts_nested_attributes_for :line_items, :allow_destroy => true
LineItem.rb
has_one :product
belongs_to :order
Product.rb
belongs_to :line_item
I'd like to create a form in product#show to allow multiple related products to be added to the order/cart at once, basically create or update a line item for each product.
Probably something like this in the view (HAML to keep it brief).
-form_for #order do |f|
- if has_related?
- for related in #products.related_products
- f.field_for :line_item do |li_form|
= li_form.text_field :quantity
= li_form.hidden_field :product_id
= related.product_name
What would it take to actually make something like this work?
I would need more info to be sure, but it seems that a LineItem belongs_to :product and Product should NOT belong_to :line_item unless there really is a 1-1 relationship there (which wouldn't make sense to me, and doesn't follow the normal convention of these sorts of systems)
Note** using - before form_for and fields_for has been deprecated in rails 3 in favor of = since the form does actually render html
= fields_for :line_items do |li_form| is the syntax for a has_many relationship
The rest all depends on your user experience design.
Hope this helps!