I am trying to build a form for a has_many :through relationship. The Problem is, that I can access the attributes from the join table (:through) in the form, but not the other table.
My models look like this:
class Recipe < ActiveRecord::Base
has_many :quantities
has_many :ingredients, through: :quantities
accepts_nested_attributes_for :quantities
end
class Quantity < ActiveRecord::Base
belongs_to :recipe
belongs_to :ingredient
end
class Ingredient < ActiveRecord::Base
has_many :quantities
has_many :recipes, through: :quantities
end
My Form:
<%= form_for(#recipe) do |f| %>
<div class="field">
<%= f.label :title %><br>
<%= f.text_field :title %>
</div>
<div class="field">
<%= f.label :description %><br>
<%= f.text_area :description %>
</div>
<div class="quantities">
<%= f.fields_for :quantities do |builder| %>
<%= render 'quantity_fields', :f => builder%>
<% end %>
<%= link_to_add_association 'Add', f, :quantities %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
And the quantity_fields partial:
<p>
<%= f.label :value, "Value" %>
<%= f.text_field :value %>
<%= f.label :unit, "Unit" %>
<%= f.text_field :unit %>
<%= f.text_field :ingredient %>
<%= f.fields_for :ingredient do |builder|%>
<%= builder.label :name, "Ingredient"%>
<%= builder.text_field :name %>
<% end %>
</p>
The fields for the quantities attributes are correct, but the text field for the ingredient name (within the quantity_fields partial) stays empty.
On the Rails console on the other hand I can easily use the ingredient method on a quantity object and get a result.
I try to find a solution to this since a while now, and it becomes really frustrating to me. I am sure this is a standard task and I am just missing a small part. Can anyone help me?
Edit:
Also when I change my recipe form to directly render the ingredients, it doesn't work. The text fields remain empty.
<%= form_for(#recipe) do |f| %>
<div class="field">
<%= f.label :title %><br>
<%= f.text_field :title %>
</div>
<div class="field">
<%= f.label :description %><br>
<%= f.text_area :description %>
</div>
<div class="ingredients">
<%= f.fields_for :ingredients do |builder| %>
<p>
<%= builder.label :name, "Name" %>
<%= builder.text_field :name %>
<%= builder.label :description, "Description" %>
<%= builder.text_field :description %>
</p>
<% end %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
Edit 2:
The recipes controller:
class RecipesController < ApplicationController
before_action :set_recipe, only: [:show, :edit, :update, :destroy]
# GET /recipes
# GET /recipes.json
def index
#recipes = Recipe.all
end
# GET /recipes/1
# GET /recipes/1.json
def show
end
# GET /recipes/new
def new
#recipe = Recipe.new
end
# GET /recipes/1/edit
def edit
end
# POST /recipes
# POST /recipes.json
def create
#recipe = Recipe.new(recipe_params)
respond_to do |format|
if #recipe.save
format.html { redirect_to #recipe, notice: 'Recipe was successfully created.' }
format.json { render :show, status: :created, location: #recipe }
else
format.html { render :new }
format.json { render json: #recipe.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /recipes/1
# PATCH/PUT /recipes/1.json
def update
respond_to do |format|
if #recipe.update(recipe_params)
format.html { redirect_to #recipe, notice: 'Recipe was successfully updated.' }
format.json { render :show, status: :ok, location: #recipe }
else
format.html { render :edit }
format.json { render json: #recipe.errors, status: :unprocessable_entity }
end
end
end
# DELETE /recipes/1
# DELETE /recipes/1.json
def destroy
#recipe.destroy
respond_to do |format|
format.html { redirect_to recipes_url, notice: 'Recipe was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_recipe
#recipe = Recipe.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def recipe_params
params.require(:recipe).permit(:title, :description, quantities_attributes: [:value, :unit, :recipe, :ingredient, :_destroy])
end
end
When utilizing a has_many :through association in that way, you can set up your models like this:
class Recipe < ActiveRecord::Base
has_many :quantities
has_many :ingredients, inverse_of: :recipes
accepts_nested_attributes_for :quantities
end
class Quantity < ActiveRecord::Base
belongs_to :recipe
belongs_to :ingredient
accepts_nested_attributes_for :ingredients
end
class Ingredient < ActiveRecord::Base
has_many :quantities
has_many :recipes, inverse_of: :ingredients
end
This article covers nested attributes with has_many :through associations in a similar context. Hope this helps!
Related
I'm trying to create a nested form in Ruby on Rails. The form appears as expected. But when it is saved the nested attribute, Booking, is not saved.
cleaner.rb
class Cleaner < ActiveRecord::Base
validates_presence_of :first_name
validates_presence_of :last_name
validates_presence_of :quality_score
validates :quality_score, inclusion: 0.0...5.0
has_many :assignments
has_many :bookings
has_many :cities, through: :assignments
has_many :customers, through: :bookings
end
customer.rb
class Customer < ActiveRecord::Base
validates_presence_of :first_name
validates_presence_of :last_name
validates_uniqueness_of :phone_number
belongs_to :city
has_many :bookings
has_many :cleaners, through: :bookings
accepts_nested_attributes_for :bookings
end
booking.rb
class Booking < ActiveRecord::Base
belongs_to :customer
belongs_to :cleaner
validates_presence_of :customer
validates_presence_of :cleaner
validates_presence_of :date
end
customers_controller.rb
class CustomersController < ApplicationController
before_action :set_customer, only: %i[show edit update destroy]
def index
#customers = Customer.all
end
def show; end
def new
#customer = Customer.new
end
def edit; end
def create
#customer = Customer.find_or_initialize_by(phone_number: params[:phone_number])
#customer.assign_attributes(customer_params)
respond_to do |format|
if #customer.save
format.html { redirect_to #customer, notice: 'Customer was successfully created.' }
format.json { render :show, status: :created, location: #customer }
else
format.html { render :new }
format.json { render json: #customer.errors, status: :unprocessable_entity }
end
end
end
def update
respond_to do |format|
if #customer.update(customer_params)
format.html { redirect_to #customer, notice: 'Customer was successfully updated.' }
format.json { render :show, status: :ok, location: #customer }
else
format.html { render :edit }
format.json { render json: #customer.errors, status: :unprocessable_entity }
end
end
end
def destroy
#customer.destroy
respond_to do |format|
format.html { redirect_to customers_url, notice: 'Customer was successfully destroyed.' }
format.json { head :no_content }
end
end
private
def set_customer
#customer = Customer.find(params[:id])
end
def customer_params
params.require(:customer).permit(:first_name, :last_name, :phone_number, :city, :city_id, bookings_attributes: %i[cleaner_id date])
end
end
HomeController.rb
class HomeController < ApplicationController
def index
#customer = Customer.new
#customer.bookings.build
end
end
Parameters
{"utf8"=>"✓", "authenticity_token"=>"c4xo2M4r57+/xBsmcc+7yajpQU13u1kiwmOthx/nP7HiJXJIfS9/OqC0MrWCcaDrSW/xN8UGk2+LVfnUnbTb3A==", "customer"=>{"first_name"=>"adfad", "last_name"=>"fad", "phone_number"=>"9392323", "city_id"=>"1", "bookings_attributes"=>{"0"=>{"cleaner_id"=>"1"}}}, "#<ActionView::Helpers::FormBuilder:0x007f86bec96480>"=>{"date(1i)"=>"2017", "date(2i)"=>"8", "date(3i)"=>"2"}, "commit"=>"Create Customer"}
updated form
<h1>Sign Up Now</h1>
<%= form_for #customer do |f| %>
<% if #customer.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#customer.errors.count, "error") %> prohibited this customer from being saved:</h2>
<ul>
<% #customer.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :first_name %><br>
<%= f.text_field :first_name %>
</div>
<div class="field">
<%= f.label :last_name %><br>
<%= f.text_field :last_name %>
</div>
<div class="field">
<%= f.label :phone_number %><br>
<%= f.text_field :phone_number %>
</div>
<div class="field">
<%= f.label :city %><br>
<%= f.select :city_id, options_for_select(City.pluck(:name, :id)) %>
</div>
<%= f.fields_for :bookings do |b| %>
<%= b.date_select :date %>
<br>
<%= b.select :cleaner_id, Cleaner.all.pluck(:first_name, :id) %>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
It's now failing with 2 errors
Bookings customer can't be blank
Bookings date can't be blank
use the iteration of the first #customer so rails can read properly
i saw this example https://www.sitepoint.com/complex-rails-forms-with-nested-attributes/
<%= f.fields_for :booking do |ff| %>
<%= ff.select :city_id,options_for_select(City.pluck(:name, :id)) %>
i hope it works cross finger :)
I'm very new to Rails development and having a problem saving multiple images/attachments to a model. My problem is that the code below is not actually saving to the item_images table when I submit the form. I am following This Article as a guide, though it seems to be a bit out of date. I feel I'm in a little over my head at this point so I hope someone can point out what I'm missing. Thanks!
I have the following models:
item.rb
class Item < ActiveRecord::Base
has_many :item_images, :dependent => :destroy
accepts_nested_attributes_for :item_images, :reject_if => lambda { |t| t['item_image'].nil? }
end
item_image.rb
class ItemImage < ActiveRecord::Base
belongs_to :item
has_attached_file :image,
:styles => { thumb: "100x100#", small: "400x400#", large: "700x700" }
validates_attachment_content_type :image, :content_type => /\Aimage\/.*\Z/
end
My controller looks like this:
items_controller.rb
class ItemsController < ApplicationController
before_action :set_item, only: [:show, :edit, :update, :destroy]
# GET /items
# GET /items.json
def index
#items = Item.all
end
# GET /items/1
# GET /items/1.json
def show
end
# GET /items/new
def new
#item = Item.new
4.times {#item.item_images.build}
end
# GET /items/1/edit
def edit
4.times {#item.item_images.build}
end
# POST /items
# POST /items.json
def create
#item = Item.new(item_params)
respond_to do |format|
if #item.save
format.html { redirect_to #item, notice: 'Item was successfully created.' }
format.json { render :show, status: :created, location: #item }
else
format.html { render :new }
format.json { render json: #item.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /items/1
# PATCH/PUT /items/1.json
def update
respond_to do |format|
if #item.update(item_params)
format.html { redirect_to #item, notice: 'Item was successfully updated.' }
format.json { render :show, status: :ok, location: #item }
else
format.html { render :edit }
format.json { render json: #item.errors, status: :unprocessable_entity }
end
end
end
# DELETE /items/1
# DELETE /items/1.json
def destroy
#item.destroy
respond_to do |format|
format.html { redirect_to items_url, notice: 'Item was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_item
#item = Item.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def item_params
params.require(:item).permit(:title, :description, :price, :available, :sort_shop, :sort_gallery, :item_type, :size)
end
end
form.html.erb
<%= form_for #item, html: { multipart: true } do |f| %>
<% if #item.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#item.errors.count, "error") %> prohibited this item from being saved:</h2>
<ul>
<% #item.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :title %><br>
<%= f.text_field :title %>
</div>
<div class="field">
<%= f.label :description %><br>
<%= f.text_area :description %>
</div>
<div class="field">
<%= f.label :price %><br>
<%= f.text_field :price %>
</div>
<div class="field">
<%= f.label :available %><br>
<%= f.check_box :available %>
</div>
<div class="field">
<%= f.label :sort_shop %><br>
<%= f.number_field :sort_shop %>
</div>
<div class="field">
<%= f.label :sort_gallery %><br>
<%= f.number_field :sort_gallery %>
</div>
<div class="field">
<%= f.label :item_type %><br>
<%= f.text_field :item_type %>
</div>
<div class="field">
<%= f.label :size %><br>
<%= f.text_field :size %>
</div>
<%= f.fields_for :item_images do |builder| %>
<% if builder.object.new_record? %>
<div class="field">
<%= builder.label :image, "Image File" %>
<%= builder.file_field :image %>
</div>
<% end %>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
Try this in strong parameters in Items controller
params.require(:item).permit(:title, :description, :price, :available, :sort_shop, :sort_gallery, :item_type, :size,item_images_attributes: [:image ])
than in ItemImage.rb add this line
belongs_to :item, optional: true,
and remove this line from Item.rb
:reject_if => lambda { |t| t['item_image'].nil? }
`
If you get any error please reply
I have some trouble to fill my associated objects.
I have associated object, i try to fill my local DB's tables with form data, i can fill the building and all the object which composed itself : light_reseller, stage, furniture, location. But i don't find how to fill the component "points" of stage and
furniture !
here is my model building.rb
class Building < ActiveRecord::Base
has_many :stage, dependent: :destroy
has_many :furniture, dependent: :destroy
has_one :light_reseller, dependent: :destroy
has_one :location, dependent: :destroy
accepts_nested_attributes_for :light_reseller, :location, :stage, :furniture, :allow_destroy => true
end
class LightReseller < ActiveRecord::Base
belongs_to :building
end
class Location < ActiveRecord::Base
belongs_to :building
end
class Stage < ActiveRecord::Base
belongs_to :building
has_many :points, dependent: :destroy
accepts_nested_attributes_for :points
end
class Furniture < ActiveRecord::Base
belongs_to :building
has_many :points, dependent: :destroy
accepts_nested_attributes_for :points
end
class Point < ActiveRecord::Base
belongs_to :furniture
belongs_to :stage
end
I use a form to fill my objects :
<%= form_for (#building) do |f| %>
<% if #building.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#building.errors.count, "error") %> prohibited this store from being
saved:</h2>
<ul>
<% #building.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<h2>Store</h2>
<%= f.label :name %><br>
<%= f.text_field :name, class: "form-control" %>
<%= f.fields_for :location do |loc| %>
<%= loc.label :address %><br>
<%= loc.text_field :address, class: "form-control" %>
<%= loc.label :city %><br>
<%= loc.text_field :city, class: "form-control" %>
<% end %>
<%= f.fields_for :light_reseller do |lr| %>
<%= lr.label :"light_reseller name" %><br>
<%= lr.text_field :name, class: "form-control" %>
<% end %>
<h2>Stage</h2>
<%= f.fields_for :stage do |ft| %>
<%= ft.label :name %><br>
<%= ft.text_field :name, class: "form-control" %>
<%= ft.fields_for :point do |pt| %>
<%= pt.label :"point value" %><br>
<%= pt.text_field :val, class: "form-control" %>
<% end %>
<h3>Stage entries</h3>
<%= ft.fields_for :entrie do |et| %>
<%= et.label :"entrie name" %><br>
<%= et.text_field :name, class: "form-control" %>
<%= et.fields_for :point do |ept| %>
<%= ept.label :"point value" %><br>
<%= ept.text_field :val, class: "form-control" %>
<% end %>
<% end %>
<% end %>
<%= f.fields_for :furniture do |ft| %>
<h2>Furniture</h2>
<%= ft.label :name %><br>
<%= ft.text_field :name, class: "form-control" %>
<%= ft.fields_for :point do |pt| %>
<%= pt.label :"point value" %><br>
<%= pt.text_field :val, class: "form-control" %>
<% end %>
<%= ft.fields_for :point do |pt| %>
<%= pt.label :"point value" %><br>
<%= pt.text_field :val, class: "form-control" %>
<% end %>
<% end %>
</div>
<div class="actions">
<%= f.submit "Add a Store", class: "btn btn-default"%>
</div>
<% end %>
and here is my controller :
class BuildingsController < ApplicationController
before_action :set_building, only: [:show, :edit, :update, :destroy]
def index
#buildings = Building.all
end
def new
#building = Building.new
#building.build_location
#building.build_light_reseller
#building.furniture.new
#building.stage.build
end
def show
end
def create
#building = Building.new(buildings_params)
respond_to do |format|
if #building.save
format.html { redirect_to #building, notice: 'building was successfully created.' }
format.json { render :index, status: :created, location: #building }
else
format.html { render :new }
format.json { render json: #building.errors, status: :unprocessable_entity }
end
end
end
def update
respond_to do |format|
if #building.update(buildings_params)
format.html { redirect_to #building, notice: 'building was successfully updated.' }
format.json { render :show, status: :ok, location: #buildings }
else
format.html { render :edit }
format.json { render json: #buildings.errors, status: :unprocessable_entity }
end
end
end
def destroy
#building.destroy
respond_to do |format|
format.html { redirect_to buildings_path, notice: 'building was successfully destroyed.' }
format.json { head :no_content }
end
end
private
def set_building
#building = Building.find(params[:id])
end
def buildings_params
params.require(:building).permit(:name, location_attributes:[:address, :city], light_reseller_attributes:[:name], furniture_attributes:[:name, point_attributes:[:val]], stage_attributes:[:name, point_attributes:[:val]])
end
So when i submit my form, my controller receive these data:
"building"=>{"name"=>"Fnac", "location_attributes"=>{"address"=>"12 bird road", "city"=>"Paris"}, "light_reseller_attributes"=>{"name"=>"Marc"}, "stage_attributes"=>{"0"=>{"name"=>"First", "point"=>{"val"=>"1235"}}}, "furniture_attributes"=>{"0"=>{"name"=>"shelf", "point"=>{"val"=>"45"}}}}
You have a relation has_many :points with furniture and stage, so you need to change
<%= ft.fields_for :point do |pt| %>
to
<%= ft.fields_for :points do |pt| %>
And also change point_attributes to points_attributes in your buildings_params.
Try the below changes too along with the above.
class Building < ActiveRecord::Base
has_many :stages, dependent: :destroy
has_many :furnitures, dependent: :destroy
has_one :light_reseller, dependent: :destroy
has_one :location, dependent: :destroy
accepts_nested_attributes_for :light_reseller, :location, :stages, :furnitures, :allow_destroy => true
end
def new
#building = Building.new
#building.build_location
#building.build_light_reseller
#building.furnitures.build
#building.stages.build
end
def buildings_params
params.require(:building).permit(:name, location_attributes:[:address, :city], light_reseller_attributes:[:name], furnitures_attributes:[:name, points_attributes:[:val]], stages_attributes:[:name, points_attributes:[:val]])
end
<%= f.fields_for :stage do |ft| %> to <%= f.fields_for :stages do |ft| %>
and
<%= f.fields_for :furniture do |ft| %> to <%= f.fields_for :furnitures do |ft| %>
I'm stuck and I don't know why it is not working right.
I have a model product which has many tags.
When I update the product rails update properly the products attributes but is creating another tag record instead of just updating it.
here is my code:
View form:
<%= form_for ([#product.user, #product]), id: 'edit_form' do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<div class="field">
<%= f.label :name %><br>
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.label :description %><br>
<%= f.text_area :description %>
</div>
<div class="field">
<%= f.fields_for :tags do |t| %>
<%= t.label :name %>
<%= t.text_field :name %>
<% end %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
product model:
class Product < ActiveRecord::Base
belongs_to :user, :foreign_key => "user_id"
has_many :tags, :dependent => :destroy
accepts_nested_attributes_for :tags, reject_if: :all_blank, allow_destroy: true, :update_only => true
end
tags model:
class Tag < ActiveRecord::Base
belongs_to :product, :foreign_key => "product_id"
# before_save { name.downcase! }
end
product controller:
def edit
user = User.find(params[:user_id])
#product = user.products.find(params[:id])
#tags = #product.tags.all
respond_to do |format|
format.html
format.js
end
end
def update
user = User.find(params[:user_id])
#product = user.products.find(params[:id])
#tags = #product.tags.all
respond_to do |format|
if #product.update(product_params)
format.html { redirect_to([#product.user, #product], :notice => 'Product successfully updated.') }
else
format.html { render :action => "edit" }
end
end
end
def product_params
params.require(:product).permit(:name, :description, tags_attributes: :name)
end
You have to pass the tag id in the permit params in your controller
def product_params
params.require(:product).permit(:name, :description, tags_attributes: [:id,:name])
end
I'm still super new with Rails, and just trying to get my first has_many through association set up.
Recipes have many ingredients, and each ingredient has an amount needed for the recipe. The ingredient_amount table has a recipe_id, an ingredient_id, and an amount.
When creating a new recipe, I want to be able to create these recipe/ingredient associations in the same place. In the end, I'm going to build an AJAX autocompleter for the ingredients. For now, as a baby step, I'd like to just assume the ingredient exists, and take care of checking once I've got this part down.
So, how can I make new.html.erb for recipes do this? How can I extend the form for more than one ingredient?
As it stands now, after going through http://weblog.rubyonrails.org/2009/1/26/nested-model-forms
I still can't get any fields to add ingredients. The current code is below.
class Recipe < ActiveRecord::Base
has_many :ingredient_amounts
has_many :ingredients, :through => :ingredient_amounts
accepts_nested_attributes_for :ingredient_amounts, :allow_destroy => true
end
class IngredientAmount < ActiveRecord::Base
belongs_to :ingredient
belongs_to :recipe
end
class Ingredient < ActiveRecord::Base
has_many :ingredient_amounts
has_many :recipes :through => :ingredient_amounts
end
Here's new.html.erb as I have it currently:
<h1>New recipe</h1>
<% form_for #recipe do |f| %>
<%= f.error_messages %>
<p>
<%= f.label :name %><br />
<%= f.text_field :name %>
</p>
<p>
<%= f.label :instructions %><br />
<%= f.text_area :instructions %>
</p>
<p>
<%= f.label :numberOfServings %><br />
<%= f.text_field :numberOfServings %>
</p>
<p>
<%= f.label :prepTime %><br />
<%= f.text_field :prepTime %>
</p>
<p>
<% f.fields_for :ingredient_amounts do |ingredient_form| %>
<%= ingredient_form.label :ingredient_formedient_id, 'Ingredient' %>
<%= ingredient_form.collection_select :ingredient_id, Ingredient.all, :id, :name, :prompt => "Select an Ingredient"%>
<%= ingredient_form.text_field :amount %>
<% unless ingredient_form.object.new_record? %>
<%= ingredient_form.label :_delete, 'Remove:' %>
<%= ingredient_form.check_box :_delete %>
<% end %>
</p>
<% end %>
<p>
<%= f.submit 'Create' %>
</p>
<% end %>
<%= link_to 'Back', recipes_path %>
The important bits of the recipe controller:
def new
#recipe = Recipe.new
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => #recipe }
end
end
def create
#recipe = Recipe.new(params[:recipe])
respond_to do |format|
if #recipe.save
flash[:notice] = 'Recipe was successfully created.'
format.html { redirect_to(#recipe) }
format.xml { render :xml => #recipe, :status => :created, :location => #recipe }
else
format.html { render :action => "new" }
format.xml { render :xml => #recipe.errors, :status => :unprocessable_entity }
end
end
end
And... I have no idea where to start in the ingredient_amounts controller.
This was my very first stab, and I'm pretty sure it's not so close :)
def new
#recipe = Recipe.find(params[:recipe_id])
#ingredient = Ingredient.find(params[:ingredient_id])
#ingredient_amount = Recipe.ingredient_amounts.build
end
Thanks for the help!
I believe what you are looking for is 'Nested Model Forms'.
Try this link: http://weblog.rubyonrails.org/2009/1/26/nested-model-forms
It's hard to know what to search for when you dont really know the terminology to begin with :)