I'm getting started with Ruby on Rails and I have encountered an issue with the has_many :through association.
The models I'm using are:
class Phrase < ActiveRecord::Base
attr_accessible :event_type_id, :template_pieces
belongs_to :event_type
has_many :phrases_pieces
has_many :template_pieces, :through => :phrases_pieces
end
class TemplatePiece < ActiveRecord::Base
attr_accessible :datatype, :fixed_text, :name
has_many :phrase_pieces
has_many :phrases, :through => :phrases_pieces
end
class EventType < ActiveRecord::Base
attr_accessible :name
has_many :phrases
end
class PhrasesPiece < ActiveRecord::Base
attr_accessible :order, :phrase_id, :template_piece_id
belongs_to :phrase
belongs_to :template_piece
end
And I'm trying to create a new phrase, editing its default form to:
<%= form_for(#phrase) do |f| %>
<% if #phrase.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#phrase.errors.count, "error") %> prohibited this phrase from being saved:</h2>
<ul>
<% #phrase.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
Select the event type:
<%= collection_select(:phrase, :event_type_id, EventType.all, :id, :name) %>
Select the phrases to be used:
<%= collection_select(:phrase, :template_pieces, TemplatePiece.all, :id, :name) %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
I first had an issue with Mass Assignment, but I fixed that adding the attr_accessible :template_pieces to the phrases model. I'm unsure if that is the correct way of fixing it, but at least it stopped complaining that it could not mass assign the protected attribute.
Now, I'm getting the following error when submitting a new phrase:
undefined method `each' for "1":String
Which I kinda think happens due to the fact that there are supposed to be many template_pieces for a given phrase, but I'm currently only able to submit them one at a time. So it just finds the one, tries to iterate through it and fails.
How would I go about fixing that? Is there a better way of entering models with the has_many :through to the database? Do I have to do it manually (as in dismissing the default controller #phrase = Phrase.new(params[:phrase])?
Thanks!
You should use the fields_for helper to wrap the nested attributes:
<%= f.fields_for :template_pieces do |template_f| %>
<%= template_f.collection_select, :event_type_id, EventType.all, :id, :name %>
Select the phrases to be used:
<%= template_f.collection_select, :template_pieces, TemplatePiece.all, :id, :name %>
<% end %>
Reference
fields_for documentation.
Related
So im working through the Odin Project's "Flight Booker" project. https://www.theodinproject.com/courses/ruby-on-rails/lessons/building-advanced-forms. Which essentially is what it sounds like and im running into a problem with passing nested attributes.
First and foremost the Relevant Models:
class Booking < ApplicationRecord
belongs_to :passenger
belongs_to :flight
accepts_nested_attributes_for :passenger
end
class Flight < ApplicationRecord
has_many :bookings, dependent: :destroy
has_many :passengers, through: :bookings
belongs_to :to_airport, class_name: 'Airport', foreign_key: 'origin_id'
belongs_to :from_airport, class_name: 'Airport', foreign_key: 'destination_id'
end
class Passenger < ApplicationRecord
has_many :bookings, dependent: :destroy
has_many :flights, through: :bookings
end
The passenger schema just contains an email and name for right now. But the problem is when I pass the information to the "booking" controller. Here is my "New" form for booking.
<%= form_for #booking do |f| %>
<%= f.hidden_field :flight_id, value: params[:booking][:flight_num] %>
<%= f.hidden_field :passengers_num, value: params[:booking][:passengers_num] %>
<% params[:booking][:passengers_num].to_i.times do |passenger| %>
<%= fields_for :passenger do |passenger| %>
<%= passenger.label :name, 'Name', class: "Label" %>
<%= passenger.text_field :name %>
<%= passenger.label :email, 'email', class: "Label" %>
<%= passenger.email_field :email %>
<% end %>
<% end %>
<%= f.submit "Book Flight" %>
<% end %>
(Ignore the hidden fields for now, they are passed from the "Flights" search page and Im getting those just fine.)
So I am getting the multiple forms (name and email fields) but when I "Submit" I am only getting parameters for the last field sets. (So if there are 3 sets of name/email fields, I only get parameters for the last one).
It's possible im not understanding the fields_for however as I can't find a ton of good examples.
Thanks!
There could be many issues with your implementation...I'll layout a few...
Move <% params[:booking][:passengers_num].to_i.times do |passenger| %> logic into the new action of your bookings controller...ie
def new
#booking = Booking.new
3.times { #booking.passengers.new } # or whatever your logic is to display x amount of passenger fields
end
Make sure that in your bookings controller you are permitting the nested attributes like this...
params.require(:booking).permit(passengers_attributes: [:name, :email])
As far as the form, you'll need to treat it like a form within a form (makes sense...nested attributes created from a nested form!) and use the block variable...like this
<ul>
<%= f.fields_for :passengers do |passenger_form| %>
<li>
<%= passenger_form.label :name
<%= passenger_form.text_field :name %>
</li>
<!-- other permitted fields -->
<% end %>
</ul>
I have 2 models (and resources) - Institute and Admin.
I want to have a view with 1 submit button that creates 2 types of resources. Would I need to have 2 separate forms? An example would be great!
Also, what naming convention should this view use (given that it creates 2 types resources).
There is a "has-many through" association between Institute and Admin.
What you want is a design pattern called Form Object.
https://robots.thoughtbot.com/activemodel-form-objects
With a Form Object, you can create a class that represents the form, validate the data and then persist to the resource (or resources) that you need.
There's also a gem called Virtus for that. For me, it's a overkill if what you want is simple. You could just create a ActiveModel model and do your stuff.
Would I need to have 2 separate forms?
Answer is Non. you can make one form nested.
So example : Gessing your "has many through" association like this: One institue has many admins throuth mettings
Models :
class Institute < ActiveRecord::Base
has_many :mettings
has_many :admins, :through => :mettings
accepts_nested_attributes_for :mettings
end
class Admin < ActiveRecord::Base
has_many :mettings
has_many :institues, :through => :mettings
accepts_nested_attributes_for :mettings
end
class Metting < ActiveRecord::Base
belongs_to :institue
belongs_to :admin
accepts_nested_attributes_for :institues
end
Controller :
def new
#institue= Institue.new
#metting= #institue.mettings.build
#admin = #metting.build_admin
end
def create
Institue.new(institue_params)
end
def institue_params
params.require(:institue).permit(:id, mettings_attributes: [:id, :metting_time, admin_attributes: [:id ] )
end
Views can be called _form.erb.rb included in edit.erb.rb:
<% form_for(#institue) do |institue_form| %>
<%= institue_form.error_messages %>
<p>
<%= institue_form.label :name, "Institue Name" %>
<%= institue_form.text_field :name %>
</p>
<% institue_form.fields_for :mettings do |metting_form| %>
<p>
<%= metting_form.label :metting_date, "Metting Date" %>
<%= metting_form.date_field :metting_date %>
</p>
<% metting_form.fields_for :admin do |admin_form| %>
<p>
<%= admin_form.label :name, "Admin Name" %>
<%= admin_form.text_field :name %>
</p>
<% end %>
<% end %>
<p>
<%= institue_form.submit 'Create' %>
</p>
<% end %>
<%= link_to 'Back', institues_path %>
I'm learning Rails building an ordering system and I'm stuck trying to build a form for Orders. My models look like this:
class Restaurant < ActiveRecord::Base
has_many :orders
end
class Order < ActiveRecord::Base
belongs_to :restaurant
has_many :order_recipes, dependent: :destroy
has_many :recipes, through: :order_recipes
end
class Recipe < ActiveRecord::Base
belongs_to :restaurant
has_many :order_recipes
has_many :orders, through: :order_recipes
end
I used to handle the input for Recipes using checkboxes, which only allowed me to add one of each recipe:
<%= form_for([#restaurant, #order]) do |f| %>
<%= f.label :Table_Number %>
<%= f.number_field :table_id %>
<strong>Recipes: </strong>
<br>
<%= f.collection_check_boxes :recipe_ids, #recipes, :id, :name do |cb| %>
<% cb.label(class: "checkbox-inline input_checkbox") {cb.check_box(class: "checkbox") + cb.text} %>
<% end %>
<%= f.collection_select(:recipe_ids, #recipes, :id, :name) %>
<%= f.submit(#order.new_record? ? "Create Order" : "Edit Order", class: "btn btn-success") %>
<% end %>
But now I need to also handle quantities. So I added the "quantity" column to the order_recipes table. And I can't figure out how to build the proper form to be able to submit an Order object, with an order_recipe array of objects containing [recipe_id, order_id, quantity] per row. I'm also opened to using formtastic if it makes things easier, although I'm not very good with it yet.
ETA: Since the quantity field is on the orders_recipes table, you'll want to create an #orders_recipes object with all the correct recipe_ids instead:
#orders_recipes = #recipes.map{|r| #order.order_recipes.build(recipe: r)}
Then you can use the FormHelper's fields_for method:
<%= f.fields_for :order_recipes, #order_recipes do |orf| %>
<%= orf.hidden_field :recipe_id %>
Quantity: <%= orf.number_field :quantity %>
<%- end -%>
If desired, you'll need to "manually" remove order_recipes with nil or 0 values for quantity.
For this to work, you need an accepts_nested_attributes_for in your model, like so:
class Order < ActiveRecord::Base
…
accepts_nested_attributes_for :order_recipes
end
I've got an application that uses nested resources (see routes.rb below) to completely segregate users. It works great until I use collection_select to allow users to select objects from other models. For example, if I visit the store index view as user A, I only see stores created by user A. However, if I visit the store_group view and try to select a store to add to the group from the collection_select menu under the fields_for :store_group_details, I see all stores created by all users.
As far as I can tell, the problem might happen because there is no filter for stores in the store_group controller. store_group_details doesn't have a controller, but from what I've read, that seems to be correct since the model can only be accessed through a nested form in the store_group view. I have another situation where another view for another resource has several collection_select menus for selecting objects from other models, and all of those have the same problem (they display all objects in that model, regardless of which user created them).
How can I filter the objects shown in the collection_select menus? Is it a problem with what I'm passing into collection_select, or is it because the controllers don't do anything to filter the other models before those models' objects are displayed? I've looked at the docs for collection_select, and couldn't make it work based on that.
Thanks for your help, I've spent quite a bit of time trying to get this to work.
user.rb
class User < ActiveRecord::Base
has_many :store_groups
has_many :stores
has_many :store_group_details
end
store.rb
class Store < ActiveRecord::Base
belongs_to :user
has_many :store_group_details
has_many :store_groups, :through => :store_group_details
end
store_group.rb
class StoreGroup < ActiveRecord::Base
belongs_to :user
has_many :store_group_details, :inverse_of => :store_group
has_many :stores, :through => :store_group_details
accepts_nested_attributes_for :store_group_details
attr_accessible :store_group_details_attributes
end
store_group_detail.rb
class StoreGroupDetail < ActiveRecord::Base
belongs_to :store
belongs_to :store_group
belongs_to :user
attr_accessible :store_id
delegate :store_name, :to => :store
end
_store_group_form.html.erb
<div class="container">
<div class="span8">
<%= nested_form_for([#user, #store_group]) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<%= f.label "Store Group Name (required)" %>
<%= f.text_field :store_group_name %>
<%= f.label "Store Group Description" %>
<%= f.text_area :store_group_description %>
<%= f.fields_for :store_group_details %>
<p><%= f.link_to_add "Add store to group", :store_group_details %></p>
<br>
<%= f.submit "Submit", class: "btn btn-large btn-primary" %>
<% end %>
</div>
</div>
_store_group_detail_fields.html.erb
<p>
<%= f.label "Select Store:" %>
<%= f.collection_select :store_id, Store.order(:store_name),
:id, :store_name, include_blank: true %>
<%= f.link_to_remove "remove" %>
</p>
routes.rb
resources :users do
resources :stores
resources :store_groups
resources :store_group_details
end
There must be a problem with your controller. Did you ever solve this issue?
Look at this answer as it might lead to a solution. Or add you stores_controller.rb.
I'm currently using the nested_forms gem and I'm trying to be able to add multiple landlords to a property.
At the moment the associations are quite deep:
Property -> Landlord -> Contact_Detail -> Address
In my Property controller I'm building the associations and the initial form is displayed correctly. However, after using the add fields button, there are no fields. I know it is something to do with the object not getting built, but I can't understand why.
Here's my Property model:
belongs_to :address
belongs_to :estate_agent
belongs_to :property_style
has_and_belongs_to_many :landlord
has_and_belongs_to_many :tenancy_agreement
attr_accessible :landlord_attributes, :address_attributes, :estate_agent_attributes,
:property_style_attributes, :sector, :reference , :occupied, :available_date, :property_style_attributes,...
accepts_nested_attributes_for :landlord, :address, :estate_agent, :property_style, :tenancy_agreement
And here's the new function in the Property controller:
def new
#property = Property.new
#property.build_address
#property.landlord.build.build_contact_detail.build_address
#property.estate_agent_id = current_user.estate_agent_id
respond_to do |format|
format.html # new.html.erb
format.json { render json: #property }
end
end
I've had quite a few attempts at this, but can't see where I'm going wrong, is it a problem with the nested_form gem not supporting this many levels of association or the type of association?
Thanks!
EDIT
Changes made:
belongs_to :address
belongs_to :estate_agent
belongs_to :property_style
has_and_belongs_to_many :landlords
has_and_belongs_to_many :tenancy_agreements
attr_accessible :landlords_attributes, :address_attributes, :estate_agent_attributes,
:property_style_attributes, :sector, :reference , :occupied, :available_date, :property_style_attributes,...
accepts_nested_attributes_for :landlords, :address, :estate_agent, :property_style, :tenancy_agreements
Properties controller:
#property.landlords.build.build_contact_detail.build_address
Landlords model
has_and_belongs_to_many :properties
Here is my view:
<%= nested_form_for(#property) do |f| %>
<% if #property.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#property.errors.count, "error") %> prohibited this property from being saved:</h2>
<ul>
<% #property.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<h2>Landlords</h2>
<%= f.fields_for :landlords %>
<p><%= f.link_to_add "Add a Landlord", :landlords %></p>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
Unless you've specified "landlord" as an irregular inflection, Rails will assume that it is singular. Many-to-many associations should be declared in the plural.
Try changing the many-to-many associations to:
has_and_belongs_to_many :landlords
has_and_belongs_to_many :tenancy_agreements
You'll also need to change all calls to these to be plural as well. In addition, you must change the accepts_nested_attributes_for to landlords, and the attr_accessible from landlord_attributes to landlords_attributes.
I attempted to use both awesome-nested-forms and cocoon and it still wouldn't work.
In the end, I found a workaround by building the object in the partial and not in the controller. Like this:
<% f.object.build_contact_detail.build_address %>
I hope this helps someone else!