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.
Related
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.
How to display products divided into three parts(three different filter like: product_1, product_2, product_3 ) and need choose only one product from each part
After submit. I should to save all that products for one order.
I have 4 tables:
Users
Orders
Order_details
Products
class Order < ActiveRecord::Base
has_many :order_details
belongs_to :user
has_many :products, through: :order_details
accepts_nested_attributes_for :order_details
end
class OrderDetail < ActiveRecord::Base
belongs_to :order
belongs_to :product
end
class Product < ActiveRecord::Base
has_many :order_details
has_many :orders, through: :order_details
def self.get_first_course
Product.where(product_type: "exem_product_1")
end
def self.get_main_course
Product.where(product_type: "exem_product_2")
end
def self.get_drink
Product.where(product_type: "exem_product_3")
end
end
I am not sure how to write strong params for that situation and how create that objects for save data.
class OrdersController < ApplicationController
before_action :authenticate_user!
def index
#order = Order.new
#I think need something like this..?!
##order.order_details.build
end
def create
end
private
def order_params
params.require(:order).permit(:date, :product_id => [])
end
end
You can do something like this in your controller:
class OrdersController < ApplicationController
before_action :authenticate_user!
def index
#order = Order.all
end
def new
#order = Order.new
end
def create
#order = current_user.orders.new(order_params)
if #order.save
#your actions here
else
#your actions to rescue error
end
end
private
def order_params
params.require(:order).permit(:date, :product_id => [])
end
end
And to use simple form for radio button collections, you have to do something like this:
= simple_form_for(#order, html: {:class => 'well form-horizontal', :method => :post, :action=> :create }) do |f|
.col-xs-12.col-sm-6.col-md-8
= render 'shared/error_messages', object: f.object
= f.collection_radio_buttons :product_ids, Product.get_first_course, :id, :product_name, :item_wrapper_class => 'inline'
%hr
= f.collection_radio_buttons :product_ids, Product.get_main_course, :id, :product_name, :item_wrapper_class => 'inline'
%hr
= f.collection_radio_buttons :product_ids, Product.get_drink, :id, :product_name,,:item_wrapper_class => 'inline'
%hr
= f.association :products, as: :radio_buttons
= f.button :submit, class: "btn btn-primary"
for select collection and get 3 different ids from form, that works for me..
post:
~ products_ids => {array ids}
= simple_form_for #order do |f|
= render 'shared/error_messages', object: f.object
= simple_fields_for :product_ids do |product|
= product.collection_select(nil, Product.get_first_course, :id, :product_name,
{prompt: "Select first course" },class: "form-control product-select")
= product.collection_select(nil, Product.get_main_course, :id, :product_name,
{prompt: "Select first course"},class: "form-control product-select")
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.
I'm working on a website that allows people who run bed and breakfast businesses to post their accommodations.
I would like to require that they include a "profile image" of the accommodation when they post it, but I also want to give them the option to add more images later (this will be developed after).
I thought the best thing to do would be to use the Paperclip gem and have a Accommodation and a Photo in my application, the later belonging to the first as an association.
A new Photo record is created when they create an Accommodation. It has both id and accommodation_id attributes. However, the image is never uploaded and none of the Paperclip attributes get set (image_file_name: nil, image_content_type: nil, image_file_size: nil), so I get Paperclip's "missing" photo.
Any ideas on this one? It's been keeping me stuck for a few days now.
Accommodation
models/accommodation.rb
class Accommodation < ActiveRecord::Base
validates_presence_of :title, :description, :photo, :thing, :location
attr_accessible :title, :description, :thing, :borough, :location, :spaces, :price
has_one :photo
end
controllers/accommodation_controller.erb
class AccommodationsController < ApplicationController
before_filter :login_required, :only => {:new, :edit}
uses_tiny_mce ( :options => {
:theme => 'advanced',
:theme_advanced_toolbar_location => 'top',
:theme_advanced_toolbar_align => 'left',
:theme_advanced_buttons1 => 'bold,italic,underline,bullist,numlist,separator,undo,redo',
:theme_advanced_buttons2 => '',
:theme_advanced_buttons3 => ''
})
def index
#accommodations = Accommodation.all
end
def show
#accommodation = Accommodation.find(params[:id])
end
def new
#accommodation = Accommodation.new
end
def create
#accommodation = Accommodation.new(params[:accommodation])
#accommodation.photo = Photo.new(params[:photo])
#accommodation.user_id = current_user.id
if #accommodation.save
flash[:notice] = "Successfully created your accommodation."
render :action => 'show'
else
render :action => 'new'
end
end
def edit
#accommodation = Accommodation.find(params[:id])
end
def update
#accommodation = Accommodation.find(params[:id])
if #accommodation.update_attributes(params[:accommodation])
flash[:notice] = "Successfully updated accommodation."
render :action => 'show'
else
render :action => 'edit'
end
end
def destroy
#accommodation = Accommodation.find(params[:id])
#accommodation.destroy
flash[:notice] = "Successfully destroyed accommodation."
redirect_to :inkeep
end
end
views/accommodations/_form.html.erb
<%= form_for #accommodation, :html => {:multipart => true} do |f| %>
<%= f.error_messages %>
<p>
Title<br />
<%= f.text_field :title, :size => 60 %>
</p>
<p>
Description<br />
<%= f.text_area :description, :rows => 17, :cols => 75, :class => "mceEditor" %>
</p>
<p>
Photo<br />
<%= f.file_field :photo %>
</p>
[... snip ...]
<p><%= f.submit %></p>
<% end %>
Photo
The controller and views are still the same as when Rails generated them.
models/photo.erb
class Photo < ActiveRecord::Base
attr_accessible :image_file_name, :image_content_type, :image_file_size
belongs_to :accommodation
has_attached_file :image,
:styles => {
:thumb=> "100x100#",
:small => "150x150>" }
end
To create an upload with paperclip, you need to use the name you provided for the has_attached_file line, on the model you defined it on. In your case, this will result in this view code:
<%= form_for #accommodation, :html => { :multipart => true } do |f| %>
<%= f.fields_for :photo do |photo_fields| %>
<p>
Photo<br />
<%= photo_fields.file_field :image %>
</p>
<% end %>
<% end %>
In the controller:
class AccommodationsController < ApplicationController
# also protect create and update actions!
before_filter :login_required, :only => [ :new, :create, :edit, :update ]
def new
# always make objects through their owner
#accommodation = current_user.accommodations.build
#accommodation.build_photo
end
def create
#accommodation = current_user.accommodations.build(params[:accommodation])
if #accommodation.save
# always redirect after successful save/update
redirect_to #accommodation
else
render :new
end
end
end
Tell Rails to handle the nested form:
class Accommodation
has_one :photo
accepts_nested_attributes :photo
attr_accessible :photo_attributes, :title, :description, :etc
end
And make sure to set the accessible attributes right in your photo model:
class Photo
attr_accessible :image # individual attributes such as image_file_name shouldn't be accessible
has_attached_file :image, :styles => "etc"
end
Be sure to watch your log files to spot things that are protected by attr_accessible, but still are in your form.
I can't seem to find an example that is complete in all the components. I am having a hard time deleting image attachments
Classes
class Product
has_many :product_images, :dependent => :destroy
accepts_nested_attributes_for :product_images
end
class ProductImage
belongs_to :product
has_attached_file :image #(etc)
end
View
<%= semantic_form_for [:admin, #product], :html => {:multipart => true} do |f| %>
<%= f.inputs "Images" do %>
<%= f.semantic_fields_for :product_images do |product_image| %>
<% unless product_image.object.new_record? %>
<%= product_image.input :_destroy, :as => :boolean,
:label => image_tag(product_image.object.image.url(:thumb)) %>
<% else %>
<%= product_image.input :image, :as => :file, :name => "Add Image" %>
<% end %>
<% end %>
<% end %>
<% end %>
Controller
class Admin::ProductsController < AdminsController
def edit
#product = Product.find_by_permalink(params[:id])
3.times {#product.product_images.build} # added this to create add slots
end
def update
#product = Product.find_by_permalink(params[:id])
if #product.update_attributes(params[:product])
flash[:notice] = "Successfully updated product."
redirect_to [:admin, #product]
else
flash[:error] = #product.errors.full_messages
render :action => 'edit'
end
end
end
Looks good, but, literally nothing happens when I check the checkbox.
In the request I see:
"product"=>{"manufacturer_id"=>"2", "size"=>"", "cost"=>"5995.0",
"product_images_attributes"=>{"0"=>{"id"=>"2", "_destroy"=>"1"}}
But nothing gets updated and the product image is not saved.
Am I missing something fundamental about how 'accepts_nested_attributes_for' works?
From the API docs for ActiveRecord::NestedAttributes::ClassMethods
:allow_destroy
If true, destroys any members from the attributes hash with a _destroy key and a value that evaluates to true (eg. 1, ‘1’, true, or ‘true’). This option is off by default.
So:
accepts_nested_attributes_for :product_images, allow_destroy: true