Nested form with collection_select and multiple selection - ruby-on-rails

Sorry for long post, usually don't post here often.
Rails 3.1
I have a Company model. Company has many custom properties. All properties names are standardized (e.g. company size, company revenue, etc). Some property values are custom and some are "standartized" (relation to another table CompanyProperty - property_value_id).
What I am trying to do is a form where new Company Properties are added. In a form I try to add different properties at once. Everything works except I cannot figure out how to add multiple values via collection_select.
Here are models:
Company:
has_many :properties, :class_name => 'CompanyProperty'
CompanyProperty:
belongs_to :company
belongs_to :property, :class_name => 'Property'
belongs_to :property_value, :class_name => 'PropertyValue', :foreign_key => 'properties_value_id'
Property:
has_many :company_properties
has_many :property_values
PropertyValue:
belongs_to :properties
has_many :company_properties
Form (marked comment # problem where using collection_select with multiple selection):
<%= form_for(#company, :url => {:action => 'update_company', :id => #company.id}, :html => {:multipart => true}) do |f| %>
<% #company.properties.each.with_index do |p,i| %>
<%= f.fields_for :properties, p do |builder| %>
<%= p.property.title %><br />
<% if p.property.standard == 0 %>
<%= builder.hidden_field :property_id %>
<%= builder.text_field :value %> <br />
<% elsif p.property.standard == 1 %>
<%= builder.hidden_field :property_id %>
<%= builder.collection_select(:properties_value_id, p.property.property_values, :id, :text_value, {:include_blank => true},
{:class => 'form-control'}) %>
# Problem:
<% elsif p.property.standard == 2 %>
<%= builder.hidden_field :property_id %>
<%= builder.collection_select(:properties_value_id, p.property.property_values, :id, :text_value, {:include_blank => false},
{:multiple => true, :size => p.property.property_values.count, :class => 'form-control'}) %>
<% end %>
<% end %>
<% end %>
<%= submit_tag("Save", :class => "btn btn-primary btn-block") %>
<% end %>
Controller:
def update_company
#company = Company.find(params[:id])
if #company.update_attributes(params[:company])
render :text => "Saved"
else
error = #company.errors.full_messages.map{|o| "<li>" + o + "</li>" }.join("") + "</ul>"
render :text => error
end
end
All property_id and values are saved, except values from collection_select with multiple selection. The post parameters goes something like this (4 records created, expected result - 6 new records):
Parameters: {"company"=>{"properties_attributes"=>{"0"=>{"property_id"=>"1", "properties_value_id"=>"2"}, "1"=>{"property_id"=>"2", "value"=>"34"},
"2"=>{"property_id"=>"3", "properties_value_id"=>["", "4", "5", "6"]},
"3"=>{"property_id"=>"4", "value"=>"34"}}}, "commit"=>"Save", "id"=>"16"}
property_id=>3 is that particular property that I'm trying to save. Expected result is to see three new records on CompanyProperties table with property_id = 3 and values properties_value_id accordingly. But this does not happens. Only one records is being created with property_id=3 and the properties_value_id is 1 (why?, there is no such value).
Plus I cannot understand why there is blank param here "properties_value_id"=>["", "4", "5", "6"]}. There are no such value :/
Could any one help to reach my expected result?

This should be a comment, but I'll post here to keep it readable:
The blank param will likely be a "selector" / "default" value in your select box. Failing that, is there any way you can see how your HTML element may be including a separate element in the property_value_ids?
If you were using Rails 4, I'd recommend your strong params were incorrect - however, as you're using Rails 3, I'd surmise you should do something like this:
<%= builder.collection_select("properties_value_id[]", p.property.property_values, :id, :text_value, {:include_blank => false},
{:multiple => true, :size => p.property.property_values.count, :class => 'form-control'}) %>

Related

Rails - Display Nested Attributes in an "Update" Form

I am having a bit of difficulty getting my "update form" to display the nested attributes. Specifically, images (e.g., "choices") to display. All other data fields are showing. Just not this is my form:
<%= bootstrap_form_for #template, :url => {:action => 'update', :id => #template.id } do |f| %>
<fieldset>
<legend>Update Your Template</legend>
<%= f.text_field :prompt, :class => :span6, :placeholder => "Which one is running?", :autocomplete => :off %>
<%= f.select 'group_id', options_from_collection_for_select(#groups, 'id', 'name', selected: #template.group.id) %>
<div class="row-fluid">
<ul class="thumbnails">
<%= f.fields_for :template_assignments do |builder| %>
<li class="span3" id="">
<div class="thumbnail">
<%= builder.text_field :choice_id %>
<%= image_tag #template.template_assignments.builder.choice.image %>
</div>
</li>
<% end %>
</ul>
</div>
<% end %>
The main line I am having trouble with is:
<%= image_tag #template.template_assignments.builder.choice.image %>
I cannot get it to iterate through each of the 4 nested attributes for the image. It iterates through the 4 nested attributes pertaining to :choice_id, which displays correctly in the text_field.
If i change it to:
<%= image_tag #template.template_assignments.first.choice.image %>, it displays the first image no problem.
However, I need it to iterate and display the "first", "second", "third", and "fourth" images.
Any help on how to display these images, just as the image_id's are being displayed?
EDIT:
Here are my models
# app/models/template.rb
class Template < ActiveRecord::Base
belongs_to :group
has_many :template_assignments, dependent: :destroy
has_many :choices, :through => :template_assignments
accepts_nested_attributes_for :template_assignments, allow_destroy: true
end
# app/models/template_assignment.rb
class TemplateAssignment < ActiveRecord::Base
belongs_to :template
belongs_to :choice
end
# app/models/choice.rb
class Choice < ActiveRecord::Base
has_many :template_assignments
has_many :templates, :through => :template_assignments
end
You'll probably want to just use builder directly, just like you're doing in the text_field.
<%= image_tag builder.choice.image %>
[UPDATE] after some trial and error the correct form would be :
<%= image_tag builder.object.choice.image %>
What's happening is that when f.fields_for :template_assignments do |builder| is used to render the nested items, the builder object that is yielded to the block is not the object itself (in this case a TemplateAssignment), but is a FormBuilder object, which is what supplies the convenience methods like builder.text_field. (If you tried to do template_assignment.text_field you'd get an error.) The builder stores the object that it is representing in the form as object, so you can get a hold of your template_assignment object by using builder.object. From there you can deal the the template_assignment like normal. I hope that helps.

Setting a primary attribute via form radio buttons

So I'm having an issue setting a primary image for my Dress object via a form.
The form allows the user to edit the dress details and then add/remove images to the form (using nested_form) and for each of them set a label and assign a primary image.
Everything works so far except for setting the primary image via radio buttons.
Dress Model:
class Dress < ActiveRecord::Base
has_many :dress_images
has_one :primary_dress_image, :class_name => "DressImage", :conditions => { :is_primary => true }
accepts_nested_attributes_for :dress_images, :allow_destroy => true
validates :name, :presence => true, :length => { :maximum => 99 }
end
DressImage Model
class DressImage < ActiveRecord::Base
belongs_to :dress
# Same as:
# def self.primary
# where(:is_primary => true)
# end
scope :primary, where(:is_primary => true)
# clear old primary if:
# this is a new record
# this is existing and is_primary has been set to true
before_save :clear_primary,
:if => Proc.new{ |r| (r.new_record? && r.is_primary) || (r.is_primary_changed? && r.is_primary) }
validates :label, :presence => true, :length => { :maximum => 60 }
validates :caption, :length => { :maximum => 200 }
mount_uploader :image, ImageUploader
def clear_primary
DressImage.update_all( {:is_primary => false}, :dress_id => self.dress_id )
end
end
Dress edit form
<h1>Dress</h1>
<% #dress.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %> <%#= f.label :name %>
<%= nested_form_for #dress, :as => :dress, :url => { :action => :update }, :html=>{ :multipart => true } do |f| %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.label :description %>
<%= f.text_area :description %>
<%= f.fields_for :dress_images do |dress_image_form| %>
<div class="dress-image">
<%= image_tag dress_image_form.object.image_url(:thumb) %>
<%= dress_image_form.text_field :label %>
<%= dress_image_form.file_field :image %>
<div class="primary-image-radio">
<%= dress_image_form.label :is_primary, "Main Image" %>
<%= f.radio_button :primary_dress_image_id, dress_image_form.object.id %>
</div>
<p>
<%= dress_image_form.link_to_remove "Remove this attachment" %>
</p>
</div>
<% end %>
<%= f.link_to_add "Add Photo", :dress_images %>
<%= f.submit "Save Dress" %>
<% end %>
With this radio button, the primary_dress_image_id attribute is set on the Dress object, but #dress.primary_dress_image gives a different result to the ID.
If I change the radio button to <%= dress_image_form.radio_button :is_primary, true %> it works better but because the name of each radio button is different, they are not treated as the same group.
I'm new to rails so I might be missing something completely obvious or doing it all wrong.
Here's one solution.
Add a hidden input to each of your nested field groups. Use a radio_button_tag instead of radio_button to make sure they are in the same group:
<div class="dress-image">
<%= dress_image_form.hidden_field :is_primary, :class => 'primary-image' %>
...
<%= radio_button_tag "select_primary_image", true, dress_image_form.object.is_primary? %>
...
</div>
Then add some javascript to update the hidden field according to the radio button selection:
$("body").on "change", ".primary-image-radio input:radio" ->
$(#).closest(".dress-image").find(".primary-image").val( $(#).is(":checked") )
You might need to modify the code a little because it's just an untested quick example, but it should give you an idea.
So I ended up using this method: <%= dress_image_form.radio_button :is_primary, true %> and using jquery to deselect all the other radio buttons when one is clicked.
This seems like a bit of a hacky method to me - there must be a purely Rails way of doing this without having to resort to JS? Until I find a better one, I'm going to stick with this solution.

saving array values in each new row

I have tried for sometime and i think i got it wrong.
The form that i use is a nested form with fields_for and all i wanted is to save each of the array values in the rails select function into new rows in the db.
I have serialized :newpages in my blackwhite.rb model.
<% forms_for #prints do |f| %>
...
...
<%= f.fields_for :blackwhites_attributes do |blackwhite| %>
<%= blackwhite.select :newpages , options_for_select((1..(#print.number_of_images_entry.to_i)).to_a), :multiple => true, :size => #print.number_of_images_entry.to_i %>
<% end %>
<% end %>
Edit 1:
It has "multiple" as i wanted to have multiple selections for the pages.
blackwhite.rb model:
class Blackwhite < ActiveRecord::Base
attr_accessible :print_id
serialize :newpages
belongs_to :print
end
print.rb model:
class Print < ActiveRecord::Base
has_many :blackwhites
belongs_to :user
accepts_nested_attributes_for :blackwhites, :allow_destroy => true
...
...
end
Update 2:
I have watched railscasts and had modified my nested forms as below:
<%= f.fields_for :blackwhites do |blackwhite| %>
<% render 'blackwhites', f: blackwhite %>
<% end %>
in partial _blackwhites.html.erb:
<%= f.select :newpages , (1..(#print.number_of_images_entry)), { :prompt => "0" }, :multiple => true, :size => #print.number_of_images_entry ) %>
and my select fields is no longer appearing.
Your render is not printed because you forgot the equal sign.
<%= render 'blackwhites', f: blackwhite %>

Database querying through Http in rails?

I am working on rails app , In which I have created a table Product Name:string and Number: integer.
The application should give user a form where he can search a product by his number if product exists it should give product name from database.
My search.html.erb is this.
<%= form_for #products, :url => { :action => "create" }, :html => {:class => "nifty_form"} do |f| %>
<%= f.text_area :number, :size => "60x12" %>
<%= f.submit "Search" %>
<% end
What will be the definition of search Method in ProductController and routes i need to add in routes.rb?
Irrespective of nifty forms, this is how I would have done this:
In config/routes.rb
replace resources :products' with
resources :products do
post 'search', :on => :collection
end
This will give me a search_products_path
In your view:
<%= form_for(:search, :url => search_products_path) do |f| %>
<%= f.text_field :number, :placeholder => "Enter number to search" %>
<%= f.submit "Search" %>
<% end %>
In your products_controller.rb
def search
number = params[:search][:number]
#result = Product.find_by_number(number)
#not_found = true unless #result
end
In your views/products/search.html.erb, use #result to show the product information; take care while checking whether or not the desired product is actually found or not. I have set the boolean #not_found in case it doesn't exist.

How do I create a parent from child in Rails?

In my app I'm receiving this error.
Couldn't find Vendor with ID=1 for InventoryItem with ID=
InventoryItem.rb
belongs_to :vendor
accepts_nested_attributes_for :vendor
Vendor.rb
has_many :inventory_items
_form.html.erb
<%= simple_nested_form_for #inventory_item, :html => {:class => 'form-inline' } do |f| %>
<h2>Inventory Data</h2>
<%= f.input :name, :input_html => {:autocomplete => :off, :placeholder => 'Item Name' }%>
<%= f.input :id, :as => :hidden %>
<%= f.simple_fields_for :vendor do |v| %>
<%= v.input :name, :label => 'Vendor name', :input_html => {:autocomplete => :off, :placeholder => 'Vendor Name' } %>
<%= v.input :id, :as => :hidden %>
<% end %>
<% end %>
----snip----
My parameters hash comes out accordingly
{"utf8"=>"✓",
"authenticity_token"=>"ZY9fum4XGStTMNbpRQxrzmP7PT3A6BUU+wOymV0fZ/c=",
"inventory_item"=>{"name"=>"testing",
"id"=>"7678",
"vendor_attributes"=>{"name"=>"test",
"id"=>"1"},
"item_instances_attributes"=>{"0"=>{"barcode"=>"",
"funding_source"=>"",
"serial"=>"",
"version"=>"",
"website_id"=>"",
"item_type"=>"Retail",
"type_of_at"=>"Vision",
"os"=>"Mac",
"registration_key"=>"",
"dealer_price"=>"",
"retail_price"=>"",
"reuse_price"=>"",
"estimated_current_purchase_price"=>"",
"cost_to_consumer_for_loan"=>"",
"repair_status"=>"Working",
"date_reviewed"=>"10-15-2012",
"qr_url"=>"",
"location"=>"",
"restrictions"=>"",
"notes"=>""}}},
"commit"=>"Create Inventory item"}
inventory_items_controller.rb
def create
params[:inventory_item].delete(:estimated_dealer_price)
#inventory_item = InventoryItem.create(params[:inventory_item])
#inventory_item.name = inventory_item.name.downcase
if inventory_item.save
redirect_to(inventory_items_path, :notice => "Item created.")
else
render 'new'
end
end
The controller is receiving the id and attempting to find the right vendor (which exists), has issues when left to the built-in rails methods for finding the vendor and building the relationship.
The input for vendor name is an autocomplete which assigns the id to the hidden id field.
possible solutions:
Handle manually in the controller, fetching the id and building the relationship
change the form so that the inventory_item.vendor.name autocompletes inventory_item.vendor_id and strip the name if an id is provided
fix something I'm missing?
Sounds like you have it in reverse, normally child should not be creating parent records and you should check if its posible to make it in more standard approach of parent child relationship.
That being said you can do something like this
InventoryItem << ActiveRecord::Base
belongs_to :vendor
def vendor_attributes=(params)
self.vendor = Vendor.find(params[:id]) || Vendor.create_by_name!(params[:name])
end
end

Resources