Multiple instances of same model in form - parsing params hash - ruby-on-rails

I'm admittedly a real novice at most of this, including forming and parsing params hashes. I'm trying to get a hash of instances of the same model from a form for a voting application. Users can vote for as many candidates as they wish according to rank.
My votes#new does create Candidate.count instances of Vote with the user_id and rank properly set. I know this because the form is correctly iterating through #user.votes for each candidate.
Here's a snippet of the rendered form:
<input id="vote_rank" name="vote[rank]" type="hidden" value="1" />
<label for="vote_Candidate">Candidate</label>
<select id="vote_candidate_id" name="vote[candidate_id]"><option value=""></option>
<option value="1">Smithers</option>
<option value="2">Mr. Burns</option>
<option value="3">Bart</option>
<option value="4">Lionel Hutz</option>
<option value="5">Jimbo</option>
<option value="6">Troy McClure</option>
<option value="7">Duffman</option>
<option value="8">Maggie</option>
<option value="9">Moe</option>
<option value="10">Principal Skinner</option>
<option value="11">Apu Nahasapeemapetilan</option></select>
I can't figure out how to get the collection back from the form to my votes#create. I am able to get a params hash with a single set of candidate_id and rank values (the last), but not all 11 instances I stuck in it in the first place. The form wants to give up the right data.
#user.votes.create(vote_params) in votes#create is throwing this error: undefined method `stringify_keys' for "rank":String
Here are my controller methods and form.
def new
#user = current_user
#votes = Candidate.count.times { |i| #user.votes.build(:rank => i + 1) }
end
def create
#user = current_user
#params[:user][:votes].each do |vote_params|
##user.votes.create(vote_params)
params[:vote].each do |vote_params|
#user.votes.create(vote_params)
end
respond_to do |format|
if #user.valid?
format.html { redirect_to #user, notice: 'votes were successfully created.' }
else
format.html { render action: "new" }
end
end
end
<%= form_for :votes, :url => votes_path do |f| %>
<% #user.votes.each do |v| %>
<%= fields_for v do |vote_fields| %>
<%= vote_fields.hidden_field :rank %>
<%= vote_fields.label "Candidate" %>
<%= vote_fields.collection_select :candidate_id, Candidate.all, :id, :name, :include_blank => true %>
<% end %>
<% end %>
<p><%= f.submit :class => 'medium radius button' %></p>
<% end %>
Here's the relevant HTML
Only one set of values is getting passed:
{"utf8"=>"✓", "authenticity_token"=>"bVLXCQtOffEtu5xBNI0e94o9j9mJ8alHhuBhDkkfaRA=", "vote"=>{"rank"=>"11", "candidate_id"=>""}, "commit"=>"Save Votes", "action"=>"create", "controller"=>"votes"}
although the form is trying to give up the right values:
vote[rank]:1
vote[candidate_id]:2
vote[rank]:2
vote[candidate_id]:4
vote[rank]:3
vote[candidate_id]:2
vote[rank]:4
vote[candidate_id]:9
etc. (up to 11!)
Any help appreciated.
Update:
Solved it sort of. I'm putting this up in case it helps someone since I haven't found anything directly on point and it took me a long time to figure this problem out.
My form was creating identically named objects, which were just overwriting. Explains why I was only ever able to get the last set of values into the database.
My form now iterates over the objects passed from votes#new and includes the vote's rank in the name.
<%= form_for :vote, :url => votes_path do |f| %>
<% #user.votes.each do |v| %>
<% if v.errors.any? %>
<h1>errors</h1>
<% end %>
<%= f.fields_for "#{v.rank}" do |builder| %>
<%= builder.hidden_field :rank, :value => v.rank %>
<div class="row">
<div class="one columns"><%= builder.label "Rank #{v.rank}", :class => "left inline" %></div>
<div class="eleven columns"><%= builder.collection_select :candidate_id, Candidate.all, :id, :name, {:include_blank => true}, :class => "two" %></div>
</div>
<% end %>
<% end %>
<%= f.submit "Submit", :confirm => "Please review your ballot and confirm that your votes are correct before submitting. This message will appear as a reminder each time you press 'Submit', even if you've edited your ballot.", :class => "small round button" %>
<% end %>
This craps out a params[:vote] hash of uniquely named hashes e.g.
{"1"=>{"rank"=>"1", "candidate_id"=>"5"}, "2"=>{"rank"=>"2", "candidate_id"=>"2"}, "3"=>{"rank"=>"3", "candidate_id"=>"7"}, "4"=>{"rank"=>"4", "candidate_id"=>"4"}, "5"=>{"rank"=>"5", "candidate_id"=>"10"}, "6"=>{"rank"=>"6", "candidate_id"=>""}, "7"=>{"rank"=>"7", "candidate_id"=>""}, "8"=>{"rank"=>"8", "candidate_id"=>""}, "9"=>{"rank"=>"9", "candidate_id"=>""}, "10"=>{"rank"=>"10", "candidate_id"=>""}, "11"=>{"rank"=>"11", "candidate_id"=>""}}
My votes#create method dumps an array made of key/value pairs from the params hash into #user.votes.create(my_array)
def create
#user = current_user
#user.votes.create(sanitize_and_compress_vote_array params[:vote])
redirect_to thankyou_path
end
Then next challenge is to get validation error messages to work properly. It's over my head because what I'm seeking to validate is an array of key value pairs by the uniqueness of candidate_id scoped by user_id before any values hit the database. It looks like Rails doesn't make this easy.
validates :candidate_id, :presence => true, :uniqueness => {:scope => :user_id, :message => "You may only vote for a candidate once."}
The validation works just fine for rejecting duplicate values, but it's useless because those values that already passed are obviously already in the database. I haven't found squat on how to validate members of a collection against themselves before they save and I feel like I'm talking to myself :-(

Related

Why does this select field appear multiple times in my Rails form?

I've got a Rails application that is using nested forms. Details follow and I tried this solution (Rails 3 Nested Models unknown attribute Error), but for some reason, the field is repeating multiple times instead of listing and saving the options correctly. Thanks in advance for your help!
Model information for Newsavedmaps
has_many :waypoints, :dependent => :destroy
accepts_nested_attributes_for :waypoints
Newsavedmap_controller
def new
#newsavedmap = Newsavedmap.new
waypoint = #newsavedmap.waypoints.build
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => #newsavedmap }
end
end
def edit
#newsavedmap = Newsavedmap.find(params[:id])
if #newsavedmap.itinerary.user_id == current_user.id
respond_to do |format|
format.html # edit.html.erb
format.xml { render :xml => #activity }
end
else
redirect_to '/'
end
end
Maptry View
<% form_for #newsavedmap, :html=>{:id=>'createaMap'} do |f| %>
<%= f.error_messages %>
<% f.fields_for :waypoint do |w| %>
<%= w.select :waypointaddress, options_for_select(Waypoint.find(:all, :conditions => {:newsavedmap_id => params[:newsavedmap_id]}).collect {|wp| [wp.waypointaddress, wp.waypointaddress] }), {:include_blank => true}, {:multiple => true, :class => "mobile-waypoints-remove", :id =>"waypoints"} %>
<% end %>
<% end %>
When I use the above code, my form works correctly, but submitting it gives me this error:
UnknownAttributeError (unknown attribute: waypoint)
When I change ":waypoint do |w|" to ":waypoints do |w|" in the view, the select field disappears when the user is creating a new record, and in the edit view, the select field appears several times (however many waypoints the user saved in the record.)
How can I get this form field to work properly?
EDIT 1
Here is my latest attempt. For new records, the select field does not appear. However, in the edit view, the select field appears multiple times. This is a Rails 2 application, FYI. Taking a cue from the comments, I used a collection_select approach (not collection_for_select because I couldn't find documentation for that.) Again, I appreciate your help!
<% f.fields_for :waypoints do |w| %>
<%= w.collection_select( :waypointaddress, #newsavedmap.waypoints, :waypointaddress, :waypointaddress, {:include_blank => true}, {:id =>"waypoints"} ) %>
<% end %>
Your form has the following problems.
Use f.fields_for :waypoints since the argument needs to match the name of the association.
Use collection_select rather than select, since you have an ActiveRecord model behind that field.
So, taking that into account, you could try this for your form:
<% form_for #newsavedmap, :html => { :id => 'createaMap' } do |f| %>
<%= f.error_messages %>
<% f.fields_for :waypoints do |w| %>
<%= w.collection_for_select :waypoint, :waypointaddress, #newsavedmap.waypoints, :waypointaddress, :waypointaddress, { :include_blank => true }, { :multiple => true, :class => "mobile-waypoints-remove", :id =>"waypoints" } %>
<% end %>
<% end %>
The API for collection_select is a bit tricky to get right. I usually need a few attempts as well. This previous SO question may help clear things up: Can someone explain collection_select to me in clear, simple terms?

Rails linking form submission to object

In a rails project I have two entities, Users and Institutions, they have a many-to-many relationship.
The views for them are set up to create new users and institutions but I want to have another view for linking the two.
In rails console all I have to do is
myuser.institutions << the_institution_i_just_created
The controller can do some of the work but how do I handle the submissions and the forms? I want to use a selection box so that the input is limited to the Institutions already in existence.
<select id="institution_selection" name="institution_sel">
<% selections = []
Institution.all.each do |institution|
pair = [institution.name, institution.id]
selections.concat([pair])
end
%>
<%= options_for_select(selections) %>
</select>
So the question in summary is how do I map this submission to an object so that in the controller I can do add it to the relation?
The solution was:
Alright, so this is the solution I came up with, I'm sure there is a better way to go about it and I'll continue to look into it but at least I got something close to what I was aiming for
def test
if !session[:user]
redirect_to users_path, notice: "Please login first"
end
if params[:institution]
#user = User.find(session[:user])
#institution = Institution.find(params[:institution][:id])
#user.institutions << #institution
redirect_to #user, notice: "Institution was successfully added "
end
end
and for the view
<%= form_tag("/users/test", :method => "post") do %>
<%= collection_select :institution, :id, Institution.all, :id, :name %>
<%= submit_tag("Search") %>
<% end %>
Use collection_select
<% from for #instancevar do |form| %>
<%= form.collection_select :institution_id, Institution.all, :id, :name %>
# Do other stuff....
<% end %>

Conditional form input field types

Rails 2.3.11
I would like to use a text_field to input data under one condition, otherwise using a selection box. Right now, my code looks like this:
views/posters/new.html.erb
<% form_for #poster, :html => {:multipart => true} do |f| %>
<%= f.error_messages %>
<p>
<%= f.label :image %> - We're not going to enlarge it for you, so please upload the biggest copy you can!<br />
<%= f.file_field :image %><br />
</p>
<p>
<% if current_user.admin? && params[:event_id] && !current_user.events.find_by_id(params[:event_id]) && Event.find_by_id(params[:event_id]) %>
<%= f.label "Event ID" %><br />
<%= f.text_field :event_id, :value => params[:event_id] %>
<% else %>
<%= f.label :event_id %><br />
<%= f.select :event_id, #events, :selected => params[:event_id].to_i %>
<% end %>
</p>
<p>
<%= f.submit 'Create' %>
</p>
<% end %>
controllers/posters_controller.rb
def new
#poster = Poster.new
current_user ||= User.find_by_id session[:user_id]
#events = [["Don't attach to an event", '']]
current_user.events.each {|event| #events << [event.title, event.id]}
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => #poster }
end
end
Error message: http://cl1p.net/halp
How can I use two different types of form input fields for the same parameter (but each under a different condition, not simultaneously)?
Update: I think the problem stems from the issue of Rails putting the previously-submitted information back into their respective input fields. This explains why no tantrum is thrown when a file that passes all the validation tests (that is, a PNG less than 3 MB), but breaks down when nothing (or anything that doesn't meet that condition) is attached.
Firstly, I don't understand why you are manually setting the values on the text box and select box. Usually, Rails does this for you, but without the form definition, I can't tell if you really need to do this.
Even then, given the information, I think it's safe to say that what you need is this:
f.text_field :event_id
...
f.select :event_id, #events
This ought to work for what you intend to do. I'm not sure what it has to do with the submitting of a file, but yes you are right about the previously-submitted part. The unexpected nil stems from this:
params[:event_id].to_i
Unless you are setting this parameter entry manually inside your controller, you will not be able to cast it to an integer if it is nil. If you go with using the basic form helper calls this goes away.

How do I set default search conditions with Searchlogic?

I've got a search form on this page:
http://staging-checkpointtracker.aptanacloud.com/events
If you select a State from the dropdown you get zero results because you didn't select one or more Event Division (checkboxes).
What I want is to default the checkboxes to "checked" when the page first loads...to display Events in all Divisions...but I want changes made by the user to be reflected when they filter.
Here's the index method in my Events controller:
def index
#search = Event.search(params[:search])
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => #events }
end
end
Here's my search form:
<% form_for #search do |f| %>
<div>
<%= f.label :state_is, "State" %> <%= f.select :state_is, ['AK','AL','AR','AZ','CA','CO','CT','DC','DE','FL','GA','HI','IA','ID','IL','IN','KS','KY','LA','MA','MD','ME','MI','MN','MO','MS','MT','NC','ND','NE','NH','NJ','NM','NV','NY','OH','OK','OR','PA','RI','SC','SD','TN','TX','UT','VA','VT','WA','WI','WV','WY'], :include_blank => true %>
</div>
<div>
<%= f.check_box :division_like_any, {:name => "search[:division_like_any][]"}, "Sprint", :checked => true %> Sprint (2+ hours)<br/>
<%= f.check_box :division_like_any, {:name => "search[:division_like_any][]"}, "Sport" %> Sport (12+ hours)<br/>
<%= f.check_box :division_like_any, {:name => "search[:division_like_any][]"}, "Adventure" %> Adventure (18+ hours)<br/>
<%= f.check_box :division_like_any, {:name => "search[:division_like_any][]"}, "Expedition" %> Expedition (48+ hours)<br/>
</div>
<%= f.submit "Find Events" %>
<%= link_to 'Clear', '/events' %>
<% end %>
There are a few ways to do this, I think the easiest/quickest way is:
#search = Event.search(params[:search] || Event::DEFAULT_SEARCH_PARAMETERS)
In event.rb
class Event < A:RB
DEFAULT_SEARCH_PARAMETERS = {:state_is => 'NY', :foo => 'bar'} # set your defaults here
end
Having said that, I'm not sure how that's going to work with the checkboxes. You could also consider one of the following options:
Have a SearchSetting model which contains all the searchable parameters (you can also persist this for users if they can save searches, or just leave it disconnected). This will make your form much simpler.
Add an All Divisions checkbox which is checked by default. A little bit of javascript to manage the state of the checkboxes and a custom search method.

Multiple forms for the same model in a single page

On the front page of my rap lyrics explanation site, there's a place where users can try explaining a challenging line:
alt text http://dl.dropbox.com/u/2792776/screenshots/2010-02-06_1620.png
Here's the partial I use to generate this:
<div class="stand_alone annotation" data-id="<%= annotation.id %>">
<%= song_link(annotation.song, :class => :title) %>
<span class="needs_exegesis"><%= annotation.referent.strip.gsub(/\n/, "\n <br />") %></span>
<% form_for Feedback.new(:annotation_id => annotation.id, :created_by_id => current_user.try(:id), :email_address => current_user.try(:email)), :url => feedback_index_path, :live_validations => true do |f| %>
<%= f.hidden_field :annotation_id %>
<%= f.hidden_field :created_by_id %>
<p style="margin-top: 1em">
<%= f.text_area :body, :rows => 4, :style => 'width:96%', :example_text => "Enter your explanation" %>
</p>
<p>
<% if current_user %>
<%= f.hidden_field :email_address %>
<% else %>
<%= f.text_field :email_address, :example_text => "Your email address" %>
<% end %>
<%= f.submit "Submit", :class => :button, :style => 'margin-left: .1em;' %>
</p>
<% end %>
</div>
However, putting more than one of these on a single page is problematic because Rails automatically gives each form an ID of new_feedback, and each field an ID like feedback_body (leading to name collisions)
Obviously I could add something like :id => '' to the form and all its fields, but this seems a tad repetitive. What's the best way to do this?
If you don't want to change your input names or your model structure, you can use the id option to make your form ID unique and the namespace option to make your input IDs unique:
<%= form_for Feedback.new(...),
id: "annotation_#{annotation.id}_feedback"
namespace: "annotation_#{annotation.id}" do |f| %>
That way our form ID is unique, i.e. annotation_2_feedback and this will also add a prefix, e.g. annotation_2_, to every input created through f.
Did you consider nested_attributes for rails models? Instead of having multiple new feedback forms where each is tied to an annotation, you could have multiple edit annotation forms where each annotation includes fields for a new feedback. The id's of the generated forms would include the annotations id such as edit_annotation_16.
The annotation model would have a relationship to its feedbacks and will also accept nested attributes for them.
class Annotation < ActiveRecord::Base
has_many :feedbacks
accepts_nested_attributes_for :feedbacks
end
class Feedback < ActiveRecord::Base
belongs_to :annotation
end
You could then add as many forms as you want, one for each annotation. For example, this is what I tried:
<% form_for #a do |form| %>
Lyrics: <br />
<%= form.text_field :lyrics %><br />
<% form.fields_for :feedbacks do |feedback| %>
Feedback: <br/>
<%= feedback.text_field :response %><br />
<% end %>
<%= form.submit "Submit" %>
<% end %>
<% form_for #b do |form| %>
Lyrics: <br />
<%= form.text_field :lyrics %><br />
<% form.fields_for :feedbacks do |feedback| %>
Feedback: <br/>
<%= feedback.text_field :response %><br />
<% end %>
<%= form.submit "Submit" %>
<% end %>
And the quick and dirty controller for the above edit view:
class AnnotationsController < ApplicationController
def edit
#a = Annotation.find(1)
#a.feedbacks.build
#b = Annotation.find(2)
#b.feedbacks.build
end
def update
#annotation = Annotation.find(params[:id])
#annotation.update_attributes(params[:annotation])
#annotation.save!
render :index
end
end
I had this same issue on a site I'm currently working on and went with the solution you mention at the bottom. It's not repetitive if you generate the ID programmatically and put the whole form in a partial. For example, on my site, I have multiple "entries" per page, each of which has two voting forms, one to vote up and one to vote down. The record ID for each entry is appended to the DOM ID of its vote forms to make it unique, like so (just shows the vote up button, the vote down button is similar):
<% form_for [entry, Vote.new], :html => { :id => 'new_up_vote_' + entry.id.to_s } do |f| -%>
<%= f.hidden_field :up_vote, :value => 1, :id => 'vote_up_vote_' + entry.id.to_s %>
<%= image_submit_tag('/images/icon_vote_up.png', :id => 'vote_up_vote_submit' + entry.id.to_s, :class => 'vote-button vote-up-button') %>
<% end -%>
I also had the same issue but wanted a more extensible solution than adding the ID to each field. Since we're already using the form_for ... |f| notation the trick is to change the name of the model and you get a new HTML ID prefix.
Using a variant of this method: http://www.dzone.com/snippets/create-classes-runtime (I removed the &block stuff)
I create a new model that is an exact copy of the model I want a second form for on the same page. Then use that new model to render the new form.
If the first form is using
#address = Address.new
then
create_class('AddressNew', Address)
#address_new = AddressNew.new
Your ID prefix will be 'address_new_' instead of 'address_' for the second form of the same model. When you read the form params you can create an Address model to put the values into.
For those stumbling here, looking for the solution for Rails 3.2 app, look at this question instead:
Rails: Using form_for multiple times (DOM ids)

Resources