Im trying to create two hidden fields, and one shows up no problem, but the other that comes from the nested form does not
product.rb
class Product < ActiveRecord::Base
has_many :product_options, dependent: :destroy
accepts_nested_attributes_for :product_options, allow_destroy: true, :reject_if => proc { |x| x[:option_name].blank? }
belongs_to :user
end
product_option.rb
class ProductOption < ActiveRecord::Base
belongs_to :product
end
products_controller.rb
class ProductsController < ActionController::Base
layout "application"
def index
#products = Product.all
#current_user = Client.find_by(id: session[:client])
if #current_user.redeemed == true
redirect_to root_path
end
end
def show
#product = Product.find(params[:id])
#product_option = #product.product_options.find(params[:id])
#current_user = Client.find_by(id: session[:client])
#current_user.update(:product_option => #product_option.option_name)
#current_user.update(:selected_product => #product.id)
render :nothing => true
end
private
def product_params
params.require(:product).permit(:name, :id, :position, :product_description, :product_image_type, :product_image, :product_detail, :product_option_id,
:product_options_attributes => [:id, :option_name, :ranking, :total_redeemed, :product_id])
end
end
_form.html.erb
<%= simple_form_for Product.new, :method => "post",:remote => true, :class => "item_prompt" do |f| %>
<%= f.hidden_field :id, :class => 'product_id' %>
<%= f.simple_fields_for :product_options do |ff| %>
<%= ff.hidden_field :id, :class => 'product_option_id' %>
<% end %>
<%= f.submit "Yep!", :class => "yep ready_button confirm_button", :name => "confirm_button" %>
<% end %>
html output
<form accept-charset="UTF-8" action="/products" class="simple_form new_product" data-remote="true" id="new_product" method="post" novalidate="novalidate">
<input class="product_id" id="product_id" name="id" type="hidden" value="240">
<input class="yep ready_button confirm_button" name="confirm_button" type="submit" value="Yep!">
<form>
I figured this out, ... the problem was
fields_for will loop over a collection association, rendering out as many times as there are items in it, which means 0 times if the association is empty
so to fix the problem I had to add
#product = Product.new
#product.product_options.build
to the index action in the controller.
Related
I've already looked through every other stackoverflow for this issue, but none of the solutions have fixed this. My elements in a nested_form are not being saved in the database. I've also made sure that all model associations are correct. I've been trying to fix this for nearly 8 hours now, and would really appreciate some help, especially considering every other solution hasn't worked.
Basically, I have a Playlist model that contains multiple Song models. I'm trying to use a nested_form to add the Song models to the Playlist. However, none of the Songs are ever being saved. I apologize if my methods are misguides, as I'm still fairly new to Rails.
GitHub Repo:https://github.com/nsalesky/Ultra-Music
playlists_controller.rb
def index
#user = current_user
#playlists = #user.playlists
end
def show
#user = current_user
#playlist = #user.playlists.find(params[:id])
end
def new
#playlist = Playlist.new
#I was told to do this
#playlist.songs.build
end
def create
#user = current_user
#playlist = #user.playlists.create(playlist_params)
if #playlist.save
redirect_to #playlist
else
render :action => 'new'
end
end
def edit
#playlist = current_user.playlists.find(params[:id])
end
def update
#user = current_user
#playlist = #user.playlists.find(params[:id])
if #playlist.update_attributes(playlist_params)
redirect_to #playlist
else
render :action => 'edit'
end
end
def destroy
#user = current_user
#playlist = #user.playlists.find(params[:id])
#playlist.destroy
redirect_to playlists_path(#user.playlists)
end
private
def playlist_params
params.require(:playlist).permit(:name, :description, songs_attributes: [:id, :name, :link, :_destroy])
end
playlist.rb
belongs_to :user
has_many :songs, dependent: :destroy
accepts_nested_attributes_for :songs, :allow_destroy => true, :reject_if => lambda { |a| a[:content].blank? }
validates :name, presence: true
validates_associated :songs, presence: true
_form.html.erb
<%= nested_form_for #playlist do |f| %>
<div>
<%= f.label :name %>
<%= f.text_field :name %>
</div>
<div>
<%= f.label :description %>
<%= f.text_field :description %>
</div>
<!--<div>
<button type="button" id="addsong">Add Song</button><br>
<button type="button" id="removesong">Remove Song</button><br>
</div> !-->
<div>
<%= f.fields_for :songs do |song_form| %>
<%= song_form.text_field :name %>
<%= song_form.text_field :link %>
<%= song_form.link_to_remove "Remove Song" %>
<% end %>
<p><%= f.link_to_add "Add Song", :songs %></p>
</div>
<div>
<%= f.submit %>
</div>
<% end %>
In your playlist.rb, you wrote:
:reject_if => lambda { |a| a[:content].blank? }
Here the block parameter |a| stands for attributes of a specific song. So a[:attribute] relates to a single attribute. The problem is your Song doesn't have a :content attribute. So this a[:content].blank? will always be true, means you would be rejected building a song.
Just change a[:content] to a valid attribute such as a[:name]
I am building a simple form with Ruby on Rails to submit an order.
My form needs to submit information from 3 different models: the user, the catalog_item and the order itself.
Here's my order model:
class Order < ApplicationRecord
after_initialize :default_values
validates :quantity, presence: true
belongs_to :user
belongs_to :catalog_item
validates :user_id, presence: true
validates :catalog_item_id, presence: true
accepts_nested_attributes_for :user
validates_associated :user
end
Here's my user model:
class User < ApplicationRecord
has_many :orders
end
Here's my controller:
class OrdersController < ApplicationController
def checkout
#order = Order.new(catalog_item: CatalogItem.find(params[:catalog_item_id]), user: User.new)
end
def create
#order = Order.new(order_params)
if #order.save
# redirect_to confirmation_path
else
# redirect_to error_path
end
end
private
def user_params
[:name, :email, :phone_number]
end
def order_params
params.require(:order).permit(:id, :catalog_item_id, user_attributes: user_params)
end
end
And here is my view form:
<%= form_for #order do |order_form| %>
<%= order_form.hidden_field :catalog_item_id %>
<%= order_form.fields_for :user do |user_fields| %>
<%= user_fields.label :name %>
<%= user_fields.text_field :name %>
<%= user_fields.label :email %>
<%= user_fields.text_field :email %>
<%= user_fields.label :phone_number %>
<%= user_fields.text_field :phone_number %>
<% end %>
<%= order_form.submit %>
<% end %>
This if the form HTML:
<form class="new_order" id="new_order" action="/orders" accept-charset="UTF-8" method="post"><input name="utf8" type="hidden" value="✓"><input type="hidden" name="authenticity_token" value="+z8JfieTzJrNgsr99C4jwBtXqIrpNtiEGPdVi73qJrpiGPpjYzbLwUng+e+yp8nIS/TLODWVFQtZqS/45SUoJQ==">
<input type="hidden" value="1" name="order[catalog_item_id]" id="order_catalog_item_id">
<label for="order_user_attributes_name">Name</label>
<input type="text" name="order[user_attributes][name]" id="order_user_attributes_name">
<label for="order_user_attributes_email">Email</label>
<input type="text" name="order[user_attributes][email]" id="order_user_attributes_email">
<label for="order_user_attributes_phone_number">Phone number</label>
<input type="text" name="order[user_attributes][phone_number]" id="order_user_attributes_phone_number">
<input type="submit" name="commit" value="Create Order" data-disable-with="Create Order">
Here are my routes:
get 'checkout/:catalog_item_id', to: 'orders#checkout', as: 'checkout'
post 'orders', to: 'orders#create'
When I try to save the #order inside the action create I get this error:
#<ActiveModel::Errors:0x007fe95d58b698 #base=#<Order id: nil, quantity: 1, created_at: nil, updated_at: nil, user_id: nil, catalog_item_id: 1>, #messages={:user_id=>["can't be blank"]}, #details={:user_id=>[{:error=>:blank}]}>
However it does work if I do this:
#catalog_item = CatalogItem.find(order_params[:catalog_item_id])
#user = User.new(order_params[:user_attributes])
#user.save
#order = Order.new(catalog_item: #catalog_item, user: #user)
This is what is being sent in the HTTP request when I post the form:
order[catalog_item_id]:1
order[user_attributes][name]:Ana
order[user_attributes][email]:ana#gmail.com
order[user_attributes][phone_number]:123123123
commit:Create Order
I am new to RoR and I don't understand why order_params doesn't have the user but it does have the catalog_item_id.
Any help will be truly appreciated. Thank you!
Assuming that your Order model belongs_to :user, My "suggested-rails-best-practice" solution is as follows:
See Rails Nested Attributes for more info. Basically what Nested Attributes does is it allows you to "create" also an associated record (in your example, the associated User) in just one command:
# example code:
Order.create(
catalog_item_id: 1,
user_attributes: {
name: 'Foo',
email: 'foo#bar.com'
}
)
# above will create two records (i.e.):
# 1) <Order id: 1 catalog_item_id: 1>
# 2) <User id: 1, order_id: 1, name: 'Foo', email: 'foo#bar.com'>
Now that you can also pass in user_attributes as part of the hash when creating an order, it's easy enough to just treat user_attributes as also part of the request params, see controller below.
Model:
# app/models/order.rb
belongs_to :user
accepts_nested_attributes_for :user
# from our discussion, the validation needs to be updated into:
validates :user, presence: true
validates :category_item, presence: true
Controller:
# app/controllers/orders_controller.rb
def create
#order = Order.new(order_params)
if #order.save
# DO SOMETHING WHEN SAVED SUCCESSFULLY
else
# DO SOMETHING WHEN SAVING FAILED (i.e. when validation errors)
render :checkout
end
end
private
# "Rails Strong Params" see for more info: http://api.rubyonrails.org/classes/ActionController/StrongParameters.html
def order_params
params.require(:order).permit(:id, :catalog_item_id, user_attributes: [:name, :email, :phone_number])
end
View;
<%= form_for #order do |order_form| %>
<!-- YOU NEED TO PASS IN catalog_item_id as a hidden field so that when the form is submitted the :catalog_item_id having the value pre-set on your `checkout` action, will be also submitted as part of the request -->
<%= order_form.hidden_field :catalog_item_id %>
<%= order_form.fields_for :user do |user_form| %>
<%= user_form.label :name %>
<%= user_form.text_field :name %>
<%= user_form.label :email %>
<%= user_form.text_field :email %>
<%= user_form.label :phone_number %>
<%= user_form.text_field :phone_number %>
<% end %>
<%= order_form.submit %>
<% end %>
User is a class so fields_for :user creates fields for a new user object.
Try calling order_form.fields_for instead of fields_for to scope the fields_for to your order object.
If you want the user to be able to create orders from the show view for an item you can setup a nested route instead:
resources :catalog_items do
resources :orders, only: [:create]
end
Make sure you have
class Order < ApplicationRecord
belongs_to :user
belongs_to :catalog_item_id
accepts_nested_attributes_for :user
validates_associated :user # triggers user validations
end
class CatalogItem
has_many :orders
end
Then you can do:
# /app/views/orders/_form.html.erb
<%= form_for [#catalog_item, #order || #catalog_item.orders.new] do |order_form| %>
<%= order_form.fields_for :user do |user_fields| %>
<%= user_fields.label :name %>
<%= user_fields.text_field :name %>
<%= user_fields.label :email %>
<%= user_fields.text_field :email %>
<%= user_fields.label :phone_number %>
<%= user_fields.text_field :phone_number %>
<% end %>
<%= order_form.submit %>
<% end %>
# /app/views/catalog_items/show
<%= render partial: 'orders/form' %>
This will set the form url to /catalog_items/:catalog_item_id/orders. which means that we pass catalog_item_id through the URL and not the form params -
- this is a better practice as it makes the route descriptive and RESTful.
Then setup the controller:
class OrderController
# POST /catalog_items/:catalog_item_id/orders
def create
#catalog_item = CatalogItem.find(params[:catalog_item_id])
#order = #catalog_item.orders.new(order_params)
# Uncomment the next line if you have some sort of authentication like Devise
# #order.user = current_user if user_signed_in?
if #order.save
redirect_to #catalog_item, success: 'Thank you for your order'
else
render 'catalog_items/show' # render show view with errors.
end
end
# ...
private
def user_params
[:name, :email, :phone_number]
end
def order_params
params.require(:order)
.permit(:id, user_attributes: user_params)
end
end
Hoping someone can help out with this. I have two models order and date_order. Each order can have multiple date_orders, and I should be able to create many date_orders as I create an order.
How do I do that? As you can see, my code is working well for creating ONE date_order and relating it to the created order.
UPDATE: I have tried to create many "builders" in my orders/new file. It worked on the view, and created an order when I entered multiple dates and times. But the fields_for did not create any date_orders.
orders_controller.rb
def new
#order = Order.new
#order.date_orders.build
end
def create
#order = Order.new(order_params)
if #order.save
flash[:success] = "blah"
redirect_to #order
else
render 'new'
end
end
private
def order_params
params.require(:order).permit(:user_id, :purpose,
date_orders_attributes: [:id, :order_date, :time_start, :time_end, :order_id])
end
order.rb
class Order < ActiveRecord::Base
has_many :date_orders, :dependent => :destroy
accepts_nested_attributes_for :date_orders, :reject_if => lambda { |a| a[:content].blank? }, :allow_destroy => true
end
date_order.rb
class DateOrder < ActiveRecord::Base
belongs_to :order
end
order/new.html.erb
<%= form_for(#order, :html => {:multipart => true}) do |f| %>
## SOME QUESTIONS ##
<%= f.fields_for :date_orders do |builder| %>
<%= builder.label :date %>
<%= builder.date_field :order_date %>
<%= builder.label :starting_time %>
<%= builder.time_field :time_start %>
<%= builder.label :ending_time %>
<%= builder.time_field :time_end %>
<% end %>
<% end %>
Build more orders_dates:
class OrdersController < ApplicationController
def new
#order = Order.new
5.times { #order.date_orders.build } # < === HERE ===
end
private
def order_params
params.require(:order).permit(:user_id, :purpose,
# |- === HERE ===
date_orders_attributes: [:id, :content, :order_date, :time_start, :time_end, :order_id])
end
end
Update:
Also, add content to your strong params whitelist.
I'm trying to create a user profile that will have skills, educations, experiences, languages, ... so for this i have this relation
class User < ActiveRecord::Base
has_one :profile, dependent: :destroy
after_create :create_profile
private
def create_profile
Profile.create(user: self)
end
end
class Profile < ActiveRecord::Base
belongs_to :user
has_many :profile_languages, dependent: :destroy
accepts_nested_attributes_for :profile_languages , :reject_if => lambda { |a| a[:language_name].blank? }, :allow_destroy => true
has_many :profile_skills, dependent: :destroy
accepts_nested_attributes_for :profile_skills , :reject_if => lambda { |a| a[:skill_name].blank? }, :allow_destroy => true
end
but the problem is after sign up and go to edit the profile the languages and skills will not be saved
this is my profiles controller
def show
#profile = Profile.find(params[:id])
end
def edit
#user = current_user
#profile = #user.profile
end
def update
#user = current_user
#profile = #user.profile
if #profile.update_attributes(profile_params)
redirect_to profile_path
else
flash[:notice] = #profile.errors.full_messages
redirect_to edit_profile_path
end
end
private
def profile_params
params.require(:profile).permit(:user_id, :summary, :profile_title, :useravatar, :profile_skills_attributes: [:id, :skill_name,:_destroy], profile_languages_attributes: [:id, :language, :proficiency, :_destroy] )
end
I was thinking to add a new class in profile controller that will be like this
def new
#profile = current_user.profile.build
#profile.profile_languages.build
#profile.profile_skills.build
end
but i don't think this would be a good solution because i'm already creating the profile after sign up, so i'm wondering what would be the better solution for this
Update
<%= form_for #profile do |f| %>
..................
<%= render 'profile_language_fields', f: f %>
<%= link_to_add_fields "Add languages", f, :profile_languages %>
<%= end %>
and this is profile_language partial
<%= f.fields_for :profile_languages do |pl| %>
<div class="plform-group">
<div class="form-planguage-half">
<%= pl.text_field :language, class: 'form-control form-two-half' %>
</div>
<div class="form-planguage-half-last">
<%= pl.select(:proficiency, [
["Débutant", 1],
["Intermédiaire", 2],
["Courant", 3],
["Bilingue", 4],
["Natif", 5]],
{}, {class: "form-control form-two-half"}) %>
</div>
<%= pl.hidden_field :_destroy %>
<%= link_to "remove", '#', class: "remove_fields" %>
</div>
<% end %>
I have a form for creating a new :thing, with a collection_select field to enter an existing :thing the new :thing is related to. Each :thing has_many :things, through an intermediary model :related_things, which has a thing_a_id and thing_b_id. So when I fill in the field and click submit, a :related_thing is supposed to be created with thing_a_id and thing_b_id equal to the two thing_ids, respectively. But no such :related_thing is created; the form doesn't do anything. The other textfields do work though. What's wrong with my code?
I'm using Rails 4.0.10.
Things/new View:
<h1>Add Something!</h1>
<p>
<%= form_for #thing, :url => things_path, :html => { :multipart => true } do |f| %>
<%= f.text_field :name, :placeholder => "Name of the thing" %>
<br>
<%= f.label :related_things %>
<%= f.collection_select :related_things, Thing.all, :id, :name %>
<br>
<%= f.label :display_picture %>
<%= f.file_field :avatar %>
<br>
<%= f.submit "Submit", class: "btn btn-primary" %>
<% end %>
</p>
Thing Model:
class Thing < ActiveRecord::Base
has_many :related_things
has_many :things, :through => :related_things
has_attached_file :avatar, :styles => { :medium => "300x300>", :thumb => "30x30!" }, :default_url => "/images/:style/missing.png"
validates_attachment_content_type :avatar, :content_type => /\Aimage\/.*\Z/
def related_things
related_thing_ids = RelatedThing.
where("thing_a_id = ? OR thing_b_id = ?", self.id, self.id).
map { |r| [r.thing_a_id, r.thing_b_id] }.
flatten - [self.id]
Thing.where(id: related_thing_ids)
end
def related_thing_ids=(ids)
ids.each do |id|
record = RelatedThing.where(thing_a_id: self.id, thing_b_id: id).first
record ||= RelatedThing.where(thing_a_id: id, thing_b_id: self.id).first
record ||= RelatedThing.create!(thing_a_id: self.id, thing_b_id: id)
end
end
end
RelatedThing Model:
class RelatedThing < ActiveRecord::Base
has_many :things
end
Things Controller:
class ThingsController < ApplicationController
def show
#thing = Thing.find(params[:id])
#related_thing = RelatedThing.all
#thing.things.build
end
def new
#thing = Thing.new
#things = Thing.all
end
def create
#thing = Thing.new(thing_params)
if #thing.save
redirect_to #thing
else
render 'new'
end
end
private
def thing_params
params.require(:thing).permit(:name, :image_path, :avatar)
end
end
RelatedThings Controller:
class RelatedThingsController < ApplicationController
def new
#things = Thing.all.by_name
end
def create
#things = Thing.all.by_name
end
def edit
#things = Thing.all.by_name
end
end
There are two problems causing this:
As Jamesuriah pointed out, your collection_select should use the related_things_ids field instead.
Despite that change, the field is actually being filtered out of the parameter map because of Rails' Strong Parameters.
Specifically, in your controller, the thing_params method should look like:
def thing_params
params.require(:thing).permit(:name, :image_path, :avatar, :related_things_ids)
end
Read up on strong parameters in the link above for more info. Hope that helps!
The collection select should be named related_thing_ids for your model to work, I believe.