Rails 4 trouble with accepts_nested_attributes_for - ruby-on-rails

I am new to rails and I'm trying the accepts_nested_attributes_for function. I am creating an inventory system and the accepts_nested_attributes_for feature is being used to attach multiple order details to an order. An order must also be associated with store location.
The problem I'm having is the order is being created but no data is being passed to the order details table.
My views are below:
Orders View
<h1>Place An Order</h1>
<%= form_for ([#location, #order]) do |f| %>
<p>
<%= f.label :customer_id %><br />
<%= f.text_field :customer_id %>
</p>
<p>
<h3>Items</h3>
<%= f.fields_for :order_details do |builder| %>
<%= render 'order_detail_fields', :f => builder %>
<% end %>
</p>
<p><%= link_to_add_fields "Add Item", f, :order_details %></p>
<p>
<%= f.submit %>
</p>
<% end %>
Order_details_fields Partial
<p class="fields">
<%= f.label :item_id %><br />
<%= f.text_field :item_id %></br>
<%= f.label :quantity %></br>
<%= f.text_field :quantity %></br>
<%= f.label :cost %></br>
<%= f.text_field :cost %></br>
<%= f.label :discount %><br />
<%= f.text_field :discount %><br />
<%= f.hidden_field :_destroy %>
<%= link_to_function "remove", "remove_fields(this)" %>
</p>
Orders Controller
class OrdersController < ApplicationController
def index
#orders = Order.all
end
def show
#order = Order.find(params[:id])
end
def new
#order = Order.new
#location = Location.find(params[:location_id])
end
def create
#location = Location.find(params[:location_id])
#order = #location.orders.create(order_params)
##order = #order.order_details.create
if #order.save
redirect_to #order
else
render :action => 'new'
end
end
private
def order_params
params.require(:order).permit(:customer_id, order_detials_attributes: [:id, :item_id, :quantity, :cost, :discount])
end
end
Orders Model
class Order < ActiveRecord::Base
belongs_to :location
has_many :order_details, :dependent => :destroy
accepts_nested_attributes_for :order_details, :reject_if => lambda { |a| a[:content].blank? }, :allow_destroy => true
end
Order Details Model
class OrderDetail < ActiveRecord::Base
belongs_to :order
end
Routes
resources :locations do
resources :orders
end
resources :orders do
resources :order_details
end
Any help with this would be greatly appreciated

Build
Looks like everything is right to me - the only problem being the issue #Pavan outlined, which is that when you use accepts_nested_attributes_for, you have to build the associative object, so it can be used in the form:
#app/controllers/orders_controller.rb
Class OrdersController < ApplicationController
def new
#location = Location.find parmas[:id]
#order = Order.find params[:id]
#order.order_details.build
end
end
Although this looks like the only issue you have, there may be other problems (validation on the OrderDetail model as an example (which you don't have)
The only issue with what both I and Pavan have recommended is if you don't build your associative data, the fields_for don't show on the form. If your fields are showing, it may be a different issue, which will be highlighted in the params hash

Related

Create multiple new records from checkboxes in form using nested attributes in Rails

I'm trying to come up with a contact form that creates a contact record and potentially multiple location records, if multiple locations are checked in a list of checkboxes. I thought of having all location records created and then destroyed, if they aren't checked. I don't think that's optimal though.
I'm using many to many relationships in the models.
This is what they look like at the moment:
contact.rb
class Contact < ApplicationRecord
has_many :contact_locations, dependent: :destroy
has_many :locations, through: :contact_locations
accepts_nested_attributes_for :contact_locations, allow_destroy: true, reject_if: :empty_location?
private
def empty_location?(att)
att['location_id'].blank?
end
end
location.rb
class Location < ApplicationRecord
has_many :locations, dependent: :destroy
has_many :contacts, :through => :contact_locations
has_many :contact_locations
end
contact_location.rb
class ContactLocation < ApplicationRecord
belongs_to :location
belongs_to :contact
end
contacts_controller.rb
def new
#contact = Contact.new
#locations = Location.all
4.times {#contact.contact_locations.new}
end
private
def contact_params
params.require(:contact).permit(:name, :phone, ..., contact_locations_attributes: [:location_ids])
end
new.html.rb
<%= form_with model: #contact do |f| %>
...
<%= #locations.each do |location| %>
<%= f.fields_for :contact_locations do |l| %>
<%= l.check_box :location_id, {}, location.id, nil %><%= l.label location.name %>
<% end %>
<% end %>
...
<% end %>
Does anyone how to make it work properly?
I'm working on Ruby 2.5.1 and Rails 5.2.1.
Thanks a lot.
I think your solution is the form objects pattern.
You can have something like this:
<%= form_for #user do |f| %>
<%= f.email_field :email %>
<%= f.fields_for #user.build_location do |g| %>
<%= g.text_field :country %>
<% end %>
<% end%>
And convert it in something more readable that permits you to instance the locations inside the registration object, checking the value of the checkboxes.
<%= form_for #registration do |f| %>
<%= f.label :email %>
<%= f.email_field :email %>
<%= f.input :password %>
<%= f.text_field :password %>
<%= f.input :country %>
<%= f.text_field :country %>
<%= f.input :city %>
<%= f.text_field :city %>
<%= f.button :submit, 'Create account' %>
<% end %>
Here you will find how to apply the pattern: https://revs.runtime-revolution.com/saving-multiple-models-with-form-objects-and-transactions-2c26f37f7b9a
I ended up making it work with Kirti's suggestion on the following question:
Rails Nested attributes with check_box loop
It turns out I needed to make a small adjustment in my form's fields_for tag.
Thanks a lot the help!

Rails not saving nested attributes

I have the tables Task and Item. I have a form for Item where I record all the possible items that my Tasks may have, which is working fine. Then I have the form for Task where all the Items are displayed alongside a field to put a cost value for each item. This will result in a join between Task and Item: TaskItem (this table contains task_id, item_id and cost).
When I submit the form, it's saving the Task but not the TaskItems associated. I don't see what I'm missing as I searched a lot for this problem and nothing seems to work. Please, see the code below.
Model:
class Task < ApplicationRecord
has_many :task_items
has_many :items, :through => :task_items
accepts_nested_attributes_for :task_items, :allow_destroy => true
end
class Item < ApplicationRecord
has_many :task_items
has_many :tasks, :through => :task_items
end
class TaskItem < ApplicationRecord
belongs_to :task
belongs_to :item
accepts_nested_attributes_for :item, :allow_destroy => true
end
Controller:
def new
#items = Item.all
#task = Task.new
#task.task_items.build
end
def create
#task = Task.new(task_params)
#task.save
redirect_to action: "index"
end
private def task_params
params.require(:task).permit(:id, :title, task_items_attributes: [:id, :item_id, :cost])
end
My view:
<%= form_for :task, url:tasks_path do |f| %>
<p>
<%= f.label :title %><br>
<%= f.text_field(:title, {:class => 'form-control'}) %><br>
</p>
<% #items.each do |item| %>
<% #task_items = TaskItem.new %>
<%= f.fields_for :task_items do |ti| %>
<%= ti.label item.description %>
<%= ti.text_field :cost %>
<%= ti.hidden_field :item_id, value: item.id %>
<% end %>
<% end %>
<p>
<%= f.submit({:class => 'btn btn-primary'}) %>
</p>
You need to add inverse_of option to the has_many method in class Task:
class Task < ApplicationRecord
has_many :task_items, inverse_of: :task
has_many :items, through: :task_items
accepts_nested_attributes_for :task_items, :allow_destroy => true
end
This is due to the when creating a new TaskItem instance, it requires that the Task instance already exists in database to be able to grab the id fo the Task instance. Using this option, it skips the validation.
You can read this post about inverse_of option and its use cases.
fields_for has an option to specify the object which is going to store the information. This combined with building each of the TaskItem from the has_many collection should ensure that all the relationship are set correctly.
View Code:
<%= form_for #task do |f| %>
<p>
<%= f.label :title %><br>
<%= f.text_field(:title, {:class => 'form-control'}) %><br>
</p>
<% #items.each do |item| %>
<% task_item = #task.task_items.build %>
<%= f.fields_for :task_items, task_item do |ti| %>
<%= ti.label item.description %>
<%= ti.text_field :cost %>
<%= ti.hidden_field :item_id, value: item.id %>
<% end %>
<% end %>
<p>
<%= f.submit({:class => 'btn btn-primary'}) %>
</p>
<% end %>
Controller Code:
def index
end
def new
#items = Item.all
#task = Task.new
end
def create
#task = Task.new(task_params)
#task.save
redirect_to action: "index"
end
private
def task_params
params.require(:task).permit(:id, :title, task_items_attributes: [:id, :item_id, :cost])
end

Nested form fields for associated STI model

I have a model Event that has one Payoption, which is a STI model. Payoption could be BankPayoption, CashPayoption etc, each of them has totally different fields.
The models, Payoption just have string attributes:
class Event < ActiveRecord::Base
has_one :payoption
end
class Payoption < ActiveRecord::Base
belongs_to :event
end
class BankPayoption < Payoption
end
class CashPayoption < Payoption
end
Event controller:
class EventsController < ApplicationController
def new
end
def create
#event = Event.new(post_params)
#event.user_id = current_user.id
#event.save
redirect_to #event
end
private
def post_params
params.require(:event).permit(:title, :text, :code)
end
end
This is the new Event view:
<%= form_for :event, url: events_path do |f| %>
<p>
<%= f.label :title %><br>
<%= f.text_field :title %>
</p>
<p>
<%= f.label :text %><br>
<%= f.text_area :text %>
</p>
<p>
<%= f.label :code %><br>
<%= f.text_field :code %>
</p>
<p>
<%= f.submit %>
</p>
<% end %>
This code works fine but it's obviously not creating a Payoption association, I'm not sure how to implement this in the current form_for. I want to be able to pick on of the Payoption types with a select element and then the correct fields should show. I know the field show/hide action is done by javascript but the real problem is, how do I make a nested form that creates the chosen subclass and associates that with the event object?
Thanks
very simple do it this way
class EventsController < ApplicationController
def new
#event = Event.new
#event.build_payoption
end
end
<%= form_for(#event) do |f| %>
<p>
<%= f.label :title %><br>
<%= f.text_field :title %>
</p>
<p>
<%= f.label :text %><br>
<%= f.text_area :text %>
</p>
<p>
<%= f.label :code %><br>
<%= f.text_field :code %>
</p>
<%= f.fields_for :payoption do |p| %>
<%= p.label :payoption_type %>
<%= p.select(:payoption_type, Payoption::PAY_OPTION , {:prompt => "Select"}, {class: "payoption"}) %>
<% end %>
<p>
<%= f.submit %>
</p>
<% end %>
class Event < ActiveRecord::Base
has_one :payoption, dependent: :destroy
accepts_nested_attributes_for :payoption
end
class Payoption < ActiveRecord::Base
belongs_to :event
PAY_OPTION = ["option1", "option2", "option3"]
end
m assuming payoption_type is a field in your Payoption model

rails: mass-assign error caused by updating child (Post) from parent (Topic) controller (edit view)

I am trying to edit a Topic which has many Posts.
Edit page for a Topic has Topic's name and Post's content that can be edited.
The mass-assignment error occurs in topics_controller.rb, update method, post.update_attributes(params[:post]).
How do I avoid mass-assignment error.
topic.rb
class Topic < ActiveRecord::Base
has_many :posts, :dependent => :destroy
belongs_to :forum
accepts_nested_attributes_for :posts, :allow_destroy => true
attr_accessible :name, :last_post_id, :posts_attributes
end
post.rb
class Post < ActiveRecord::Base
belongs_to :topic
attr_accessible :content
end
topics_controller.rb
def update
#topic = Topic.find(params[:id])
post = #topic.posts.first
if #topic.update_attributes(params[:topic]) && post.update_attributes(params[:post])
topic = Topic.find(#post.topic_id)
flash[:success] = "Success!"
redirect_to topic_posts_path(topic)
else
render 'edit'
end
end
views/topics/edit.html.erb
<%= form_for #topic do |f| %>
<!-- render 'shared/error_messages_topic' -->
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.fields_for #topic.posts.first do |post| %>
<%= render :partial => "posts/form", :locals => {:f => post} %>
<% end %>
<%= f.submit "Edit", class: "btn btn-large btn-primary" %>
<% end %>
views/posts/_form.html.erb
<%= f.label :content %>
<%= f.text_area :content %>
In update method you don't have to update attributes of both the models instead of if #topic.update_attributes(params[:topic]) && post.update_attributes(params[:post]) it should this only if #topic.update_attributes(params[:topic]) it will update the posts automatically.
And change your view from this <%= f.fields_for #topic.posts.first do |post| %> to <%= f.fields_for :posts, #topic.posts.first do |post| %> it will work fine.
For more information read this http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-fields_for

Nested Form: Resource adds dynamically but doesnt get created?

I am using the nested form gem and i add products dynamically to the form. When i do click "add", another product resource appears but on creation it ERASES the former ones from being created entirely. This is how the scenario goes:
Fill in Location
Choose Date
Fill in Product ( one is already on form)
Add 5 more products (Products 2, 3, 4, 5)
Fill in All Products
"click" Create
Created Product 5
This is how my nested form looks:
<%= nested_form_for #location, :url => products_path(#product) do |f| %>
<%= f.label :business %>
<%= f.text_field :business %>
<%= f.label :address %>
<%= f.text_field :address %>
<%= f.fields_for :product_dates, :url => products_path(#product) do |d| %>
<%= d.label :date %>
<%= d.date_select :date %>
<%= d.fields_for :products, :url => products_path(#product) do |p| %>
<%= p.text_field :name %>
<%= p.text_field :price %>
<%= p.text_field :tag_list %>
<%= p.link_to_remove "Remove Product" %>
<% end %>
<%= d.link_to_add "Add", :products %>
<% end %>
<%= f.submit "Finish" %>
<% end %>
Controller:
class ProductsController < ApplicationController
def new
#location = Location.new
#product = Product.new
product_date = #location.product_dates.build
product_date.products.build
end
def create
#location = Location.create(params[:location])
if #location.save
flash[:notice] = "Products Created."
redirect_to :action => 'index'
else
render :action => 'new'
end
end
Models:
class User < ActiveRecord::Base
devise
attr_accessible :email, :password, :password_confirmation, :remember_me
has_many :products, :dependent => :destroy
end
class Location < ActiveRecord::Base
attr_accessible :address, :business, :product_dates_attributes
has_many :products
has_many :product_dates
accepts_nested_attributes_for :product_dates
end
class ProductDate < ActiveRecord::Base
attr_accessible :date, :products_attributes
belongs_to :location
belongs_to :user
has_many :products
accepts_nested_attributes_for :products
end
class Product < ActiveRecord::Base
attr_accessible :name, :price, :tag_list
belongs_to :user
belongs_to :location
belongs_to :product_date
end
Any Suggestions?
First of all remove the url_for declarations on the fields_for declarations so you get
<%= nested_form_for #location, :url => products_path(#product) do |f| %>
<%= f.label :business %>
<%= f.text_field :business %>
<%= f.label :address %>
<%= f.text_field :address %>
<%= f.fields_for :product_dates do |d| %>
<%= d.label :date %>
<%= d.date_select :date %>
<%= d.fields_for :products do |p| %>
<%= p.text_field :name %>
<%= p.text_field :price %>
<%= p.text_field :tag_list %>
<%= p.link_to_remove "Remove Product" %>
<% end %>
<%= d.link_to_add "Add", :products %>
<% end %>
<%= f.submit "Finish" %>
<% end %>
What is really confusing is your whole routing and params approach. It's just not right. You have a form_for #location with a :url products_path(#product) This will right royally cause issues with the params that are sent back and there in lies the problem.
Stick with routing to location controller not the products controller by removing the products_path(#product) form your nested_form_for declaration and you will find that you will have all the necessary records saved but you will most likely need to change the redirect_to declaration in the locations_controller create action and the same for the update_action.
But why use the products controller at all when you are dealing with a location? Again this just isn't natural or intuitive.
One last thing. Your remove links won't work as you have not added the necessary :dependent => :destroy declaration to the has_many declarations and you are also missing the :reject_if procs and the :allow_destroy => true declarations on the accepts_nested_attributes declarations.
Can I strongly suggest that you
1) Use either the locations controller or the products controller not both
I mean link to get to this form link_to the locations controller and set everything up there or use form_for #product rather than #location and handle everything in the products controller
2) watch the railscasts that this gem is based on very closely
http://railscasts.com/episodes/196-nested-model-form-part-1
http://railscasts.com/episodes/197-nested-model-form-part-2
3) Spend some time learning about how rails form view helpers arrange for the params hash to be organised in the controllers actions. In your case, have a close look at your log file for the parameters that come into the create action as things currently stand.
You will most likely see that the params are not nested as you would exect them to be which is why the nested attributes declaration is not behaving as expected

Resources