Setting a primary attribute via form radio buttons - ruby-on-rails

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.

Related

Nested form with collection_select and multiple selection

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'}) %>

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 %>

Rails form - How to create a nested has many form?

Currently I have a has_one relationship betweeen Users and photos.
User model:
has_one :photo
accepts_nested_attributes_for :photo
Photo model:
belongs_to :user
Paperclip.options[:command_path] = "/usr/local/bin"
has_attached_file :image,
:path => ':rails_root/public/images/ads/:id/:basename.:extension',
:url => "images/ads/:id/:basename.:extension"
The nested form:
<%= f.simple_fields_for :photo_attributes, :html => { :multipart => true } do |d| %>
<%= d.input :billed_navn %>
<%= d.label :image, :label => 'Upload logo', :required => false %>
<%= d.file_field :image, :label => 'Image', :class => 'imagec', :required => 'false', :style => 'margin-bottom:2px;float:left;width:250px;' %>
<input type="button" value="Clear" id="clear" style="width:70px;float:left;margin-right:2px;">
<%= d.input :image_url, :label => 'Billed URL', :input_html => { :class => 'imagec'}, :required => false %>
<%= f.label :image, :label => 'Billed preview', :required => false %><div id="preview"></div>
<% end %>
This setup works as it should, I can upload 1 photo.
I users to be able to upload multiple photos at once.
Therefor I have changed the assocition in useres model to:
User model:
has_many :photos
accepts_nested_attributes_for :photos
But I how should the nested form then be? If it should be possible to upload mulitple images at once?
The accepts_nested_attributes_for thing only allows mass assignment to add multiple photos at once. (Beware of mass assignment security vulnerabilities! strong_parameters gem recommended). This means that the update action accepts multiple photos.
It will only add it if they are sent, which happens if there are fields in the form that a user fills out. This is mainly determined by the edit view.
Because you don't know how many photos a user will want to add, the best way to do this is to use javascript to dynamically add an extra set of fields for a photo when requested by the user. This can be a link, which when clicked, appends the fields to the form. This way the user can submit as many photos at once as they want.
You will also want to have some validation so that if a set of empty fields (for a photo) are submitted, it doesn't add a non-photo photo.
If you don't want to use javascript, the best you can do is to just assume the user will upload at most say 3 at a time, and include 3 sets of photo fields. Again, being careful to deal with empty fields appropriately.
Example:
<% (1..5).each do |I| %>
<%= fields_for "user[photo_attributes][]", nil, :index => I do |form| %>
<%= form.input :billed_navn %>
...
<% end %>
<% end %>

accepts_nested_attributes and validates_uniqueness_of

The central problem: How do you merge attribute collections by a key during mass assignment from a nested form.
The details: I am using the following models:
class Location < ActiveRecord::Base
has_many :containers,
:dependent => :destroy,
:order => "container_type ASC"
validates_associated :containers
accepts_nested_attributes_for :containers,
:allow_destroy => true,
:reject_if => proc {|attributes| attributes["container_count"].blank? }
end
class Container < ActiveRecord::Base
belongs_to :location, :touch => true
validates_presence_of :container_type
validates_uniqueness_of :container_type, :scope => :location_id
validates_numericality_of :container_count,
:greater_than => 0,
:only_integer => true
end
So there is a constraint of having only one container type per location. The following views render the location and associated containers:
admin/containers/_index.html.erb
<% remote_form_for [:admin, setup_containers(#location)] do |f| -%>
<% f.fields_for :containers do |container_form| -%>
<%= render "admin/containers/form", :object => container_form %>
<% end -%>
<%= f.submit "Speichern" %>
<% end -%>
admin/containers/_form.html.erb
<% div_for form.object do -%>
<span class="label">
<%- if form.object.new_record? -%>
<%= form.select :container_type, { "Type1" => 1, "Type2" => 2, ... } %>
<%- else -%>
<%= form.label :container_count, "#{form.object.name}-Container" %>
<%= form.hidden_field :container_type %>
<%- end -%>
</span>
<span class="count"><%= form.text_field :container_count %></span>
<%- unless form.object.new_record? -%>
<span class="option"><%= form.check_box :_destroy %> Löschen?</span>
<%- end -%>
<% end -%>
module Admin::ContainersHelper
def setup_containers(location)
return location if location.containers.any? {|l| l.new_record? }
returning location do |l|
all_container_types = [1, 2, ...]
used_container_types = l.containers.try(:collect, &:container_type) || []
next_container_type = (all_container_types - used_container_types).first
l.containers.build :container_type => next_container_type if next_container_type
end
end
Essentially, the helper adds an new container to the collections except all types have already been associated or there is already a new container in the collection. This container is preinitialized to the first not-yet-defined container type. This works out pretty well so far. Adding containers works. Deleting containers works.
The problem is: I want to achieve that choosing and adding a container type which is already in the collection should sum up their counts (instead it would violate the unique constraint). I'm not sure what would be the best way without implementing/reinventing the complete accepts_nested_attributes_for magic - actually I wanted to reduce - not increase - code and complexity by using that.

confusion about accepts_nested_attributes_for

Somehow, I was under impression that accepts_nested_attributes_for will let me populate child object through parent:
person.update_attributes person_hash
but in practice I ended up doing this:
person.address.update_attributes person_hash[:address]
person_hash.delete :address
person.update_attributes person_hash
Now, http://guides.rubyonrails.org mentions accepts_nested_attributes_for only indirectly and API documentation for this method is also quite cryptic.
1) Could someone show basic use-case for accepts_nested_attributes_for? I mean, I understand how it's declared, I'm confused about the difference it makes.
2) Is the way I populate nested object the 'right' one in rails, or there's something more 'elegant'?
Thanks!
update
Model, for clarity
class Person < ActiveRecord::Base
has_one :address
accepts_nested_attributes_for :address
end
update2, for j.
Form declaration goes like this
<% fields_for "people", person, :index => person.id do |person_form| %>
...
<% person_form.fields_for person.address do |address_form| %>
<%= address_form.text_field :street %>
<% end %>
...
<% end %>
But it gives me html names like people[66][address][street] and not people[66][address_attributes][street]
With accepts_nested_attributes_for you can do the following:
# example from railsapi.com
# view
<% form_for #person, :url => { :action => "create" } do |person_form| %>
Name: <%= person_form.text_field :name %>
...
<% person_form.fields_for :address do |address_fields| %>
Street : <%= address_fields.text_field :street %>
Zip code: <%= address_fields.text_field :zip_code %>
<% end %>
<% end %>
With this, your parameters will look like
params = {
:person => {
:name => 'Jack',
:address_attributes => { :street => 'Street', :zip_code => '11111' }
}
}
and you're able to create a person and the related address just using
#person = Person.create(params)
For more information, railsapi.com. I hope it helps.
Edit
I have no idea why your params look like this :/
But using
#person.address.update_attributes person_hash.delete(:address) OR
person.address.update_attributes person_hash[:address]
person_hash.delete :address
person.update_attributes person_hash
is not wrong. Just could be done with more elegant code, as you said.

Resources