I am trying to write a form for an array
<%= form_for #user, html: {multipart:true} do |f| %>
<%= render "shared/error_messages", object: f.object %>
<label for="user-amenities" class="top">Amenities</label>
<ul class="group" id="user-amenities">
<% User.amenities_list.each_with_index do |amenity, index| %>
<li class="checkbox-li">
<input type="checkbox" name="user_amenities_indicies[]" value="<%= index %>">
<%= amenity %>
</input>
</li>
</ul>
<% end %>
However I am not utilizing the |f| and it is not saving the options in the amenities_indices. Any idea on how to refactor this code to utilize the f so the user information can be saved?
Try simple_form https://github.com/plataformatec/simple_form/blob/master/README.md
What you're looking for is :collection and then :as
Code Block
The refactoring you seek is basically that you need to use the f. with all your inputs
Reason being that if you call form_for, it essentially means you're calling a huge code block, in which every attribute needs to be bound to your form object, in order to render with your params hash correctly
In short - your form_for renders an HTML form, keeping the names of the <input> elements in line with the requirement of your application to load the params. The problem you have is that omitting the f. call will keep those inputs outside the scope for your params, leading to the issue you're seeing.
--
Save
If you don't have any associative data (which I've described below), you'll want to include your inputs in the f. code block:
<%= form_for #user do |f| %>
<% User.amenities_list.each_with_index do |amenity, index| %>
<%= f.check_box :user_amenities_indicies, index %>
<% end %>
<% end %>
This will pass the checked values of the user_amenities_indicies checkboxes through to your controller. This should work, and is the correct syntax for you
--
fields_for
A further addition is that I don't know whether you're trying to populate associative data or not here - but if you were trying to create data for another model, you'll want to use fields_for:
#app/controllers/users_controller.rb
Class UsersController < ApplicationController
def new
#user = User.new
#user.user_amenities.build
end
def create
#user = User.new(user_params)
#user.save
end
private
def user_params
params.require(:user).permit(:user, :params, user_amenities_attributes: [])
end
end
This will allow you to create a form using fields_for, like this:
<%= form_for #user do |f| %>
<%= f.fields_for :user_amenities do |ua| %>
# fields for user_amenities
<% end %>
<% end %>
Related
I have an edit form that is not a devise form (i have a devise edit on a different view) to edit a users details. However the form inputs only appear if the data is already there. So a user is unable to add new details to the form, as the inputs don't appear at all.
Is this happening as i'm not using a devise form view?
This is the code in my own edit file:
<%= render "devise/registrations/details", f: f, resource: #resource, addressable: #resource, default_location: nil %>
Then in the devise/registration/details i have this:
<%= f.simple_fields_for :address do |fields| %>
<%= render "address/fields", fields: fields, addressable: addressable, resource: resource %>
<% end %>
However, i think the inputs are not showing up as fields are blank in the iteration. However these fields are showing up in the actual devise/edit file, even if they are blank, just not in my new one.
When using fields_for and simple_fields_for (which is basically just a pimped up version of the former) you have to "seed" the inputs in order for them to appear.
Consider this simplified plain rails example:
class UsersController < ApplicationController
def new
#user = User.new
#user.build_address
end
# ...
end
<%= simple_form_for(#user) do |f| %>
<%= f.simple_fields_for(:address) do |af| %>
<%= af.input :street %>
<% end %>
<% end %>
If we remove the line #user.build_address there will be no nested inputs. Thats because simple_fields_for calls the method #address on #user and creates inputs for the record. If the association is one or many to many it would iterate through the association. If the method returns nil or empty there are no inputs to create - it would be like calling form_for with nil.
You can also pass a second argument to fields_for to manually specify the record object:
<%= simple_form_for(#user) do |f| %>
<%= f.simple_fields_for(:address, #user.address || #user.build_address) do |af| %>
<%= af.input :street %>
<% end %>
<% end %>
In Devise you can do this by passing a block to super or by overriding #build_resource.
class MyRegistationsController < Devise::RegistrationsController
def new
super { |resource| resource.build_address }
end
end
the following rails controller action, creates an array of records to be created:
#guests = []
#quantity.times do
#guests << Guest.new
end
Then the form invokes the array of records to be created in the following manner
<%= form_tag guests_path do %>
<% #guests.each do |guest| %>
<%= fields_for 'guests[]', guest do |f| %>
The goal is to render some fields only for the first of these records/
How can the index value of the first guest be invoked (various attempts such as if #guests[0] generate errors.
I think what you are looking for is each with index
<%= form_tag guests_path do %>
<% #guests.each_with_index do |guest,index| %>
# Do something with index
<%= fields_for 'guests[]', guest do |f| %>
My form gets passed a 'new' Quiz (not saved to the database). My form partial looks like this:
<%= form_for(#quiz) do |f| %>
<p>
<%= f.check_box(:answer1) %>
<%= f.check_box(:answer2) %>
<%= f.check_box(:answer3) %>
<%= f.check_box(:answer4) %>
<%= f.check_box(:answer5) %>
<%= f.check_box(:answer6) %>
<%= f.check_box(:answer7) %>
<%= f.check_box(:answer8) %>
</p>
<p>
<%= f.submit("Get my results!") %>
</p>
<% end %>
Here is my QuizzesController#create action:
def create
#results = Quiz.create(post_params) #from private method
if #results.save
redirect_to results_path
else
#error handle here
end
end
...which gets triggered when the user clicks 'get my results' on my quiz form. And the post_params method looks like this:
def post_params
params.require(:quiz).permit(:id, :user_id, :answer1, :answer2, :answer3, :answer4, :answer5, :answer6, :answer7, :answer8) #add other attributes here
end
My results/index.html.erb looks like this:
<div class="container">
<!-- Example row of columns -->
<div class="row">
<h1>Results</h1>
<p><%= #results.inspect %></p>
</div>
</div>
But that 'inspected' Quiz instance returns 'nil' for all the answers1, answers2 etc attributes. Any idea why that would be? Is there something I'm NOT doing to save the user's answers to the database?
The reason it shows nil is because you are not setting the variable.
After creating and saving, you redirect to results_path and the variable #results does not persist during a redirect. Without seeing the full code, I'll have to guess at your naming conventions but there are two ways to do this.
1) If you want to redirect to the index then in the code for your index action, you can set the variable:
#results = Quiz.last
This is easy to work with in development because you are the only user and this will always return the last quiz you created. Not so great in production.
2) The alternative is to redirect to the show action for that quiz.
def create
#results = Quiz.new(post_params)
if #results.save
redirect_to result_path(#results)
else
# error handle here
end
end
Again, I have had to guess that result_path is the correct path. Without seeing the full routes file, I cannot be sure but you can rename accordingly if necessary.
I'm making a form that creates more than one record for the user depending on how many items the user decides to check off in the form using checkboxes.
Currently, I'm running into an error where param is missing or the value is empty: itemrecord even though in the log, it appears that params are passing through:
{"utf8"=>"✓", "authenticity_token"=>"m2NMruoFRr6lpsuVMK9UthlY0bsJsPmf1LWce2uKaH4=", ":item_name"=>["Backpack", "Water filter"], "commit"=>"Go!"}
Model relationship is that a User has_many :inventories
Controller code:
def create
#itemrecord = #current_user.inventories.build
items_to_be_saved = []
inventory_params.each do |i|
items_to_be_saved << ({ :signup_id => #current_user.id, :item_name => i })
end
if Inventory.create items_to_be_saved
flash[:success] = "Thanks!"
redirect_to root_path
else
render new_inventory_path
end
end
def inventory_params
params.require(:itemrecord).permit(:item_name)
end
View code:
<%= form_for #itemrecord do |f| %>
<!-- In case you're wondering, the #wishlist below is basically a hash of categories of items and items. This hash is updated in the controller, and then used by multiple views to create the same table of items. -->
<% #wishlist.each do |category, list| %>
<div class="col-xs-2">
<div class="form-group box">
<h5> <%="#{category}"%> </h5>
<% list.each do |thing| %>
<%= check_box_tag ":item_name[]", "#{thing}" %>
</br>
<% end %>
</div>
</div>
<% end %>
<%= f.submit "Go!", class: "btn btn-primary btn-large btn-block" %>
</div>
<% end %>
By the way I also tried changing :item_name to :item_names to account for the array based on what else I read on SO, but that didn't fix it either
Take a look at your inventory_params function. You're saying that you require an itemrecord, and permit an item_name attribute. Observe:
def inventory_params
params.require(:itemrecord).permit(:item_name)
end
However, in the parameters being passed, there is no reference to an itemrecord object whatsoever, but there is a reference to item_name. A quick change to your inventory_params method, removing the :itemrecord requirement and instead requiring :item_name, will fix your issue.
def inventory_params
params.require(:item_name)
end
While this isn't necessarily the best way to go about doing this (I'd suggest reading up on your Active Record Form Helpers), it should solve your issue.
I want to edit multiple items of my model photo in one form. I am unsure of how to correctly present and POST this with a form, as well as how to gather the items in the update action in the controller.
This is what I want:
<form>
<input name="photos[1][title]" value="Photo with id 1" />
<input name="photos[2][title]" value="Photo with id 2" />
<input name="photos[3][title]" value="Custom title" />
</form>
The parameters are just an example, like I stated above: I am not sure of the best way to POST these values in this form.
In the controller I want to something like this:
#photos = Photo.find( params[photos] )
#photos.each do |photo|
photo.update_attributes!(params[:photos][photo] )
end
In Rails 4, just this
<%= form_tag photos_update_path do %>
<% #photos.each do |photo| %>
<%= fields_for "photos[]", photo do |pf| %>
<%= pf.text_field :caption %>
... other photo fields
UPDATE: This answer applies to Rails 2, or if you have special constraints that require custom logic. The easy cases are well addressed using fields_for as discussed elsewhere.
Rails isn't going to help you out a lot to do this. It goes against the standard view conventions, so you'll have to do workarounds in the view, the controller, even the routes. That's no fun.
The key resources on dealing with multi-model forms the Rails way are Stephen Chu's params-foo series, or if you're on Rails 2.3, check out Nested Object Forms
It becomes much easier if you define some kind of singular resource that you are editing, like a Photoset. A Photoset could be a real, ActiveRecord type of model or it can just be a facade that accepts data and throws errors as if it were an ActiveRecord model.
Now you can write a view form somewhat like this:
<%= form_for :photoset do |f|%>
<% f.object.photos.each do |photo| %>
<%= f.fields_for photo do |photo_form| %>
<%= photo_form.text_field :caption %>
<%= photo_form.label :caption %>
<%= photo_form.file_field :attached %>
<% end %>
<% end %>
<% end %>
Your model should validate each child Photo that comes in and aggregate their errors. You may want to check out a good article on how to include Validations in any class. It could look something like this:
class Photoset
include ActiveRecord::Validations
attr_accessor :photos
validate :all_photos_okay
def all_photos_okay
photos.each do |photo|
errors.add photo.errors unless photo.valid?
end
end
def save
photos.all?(&:save)
end
def photos=(incoming_data)
incoming_data.each do |incoming|
if incoming.respond_to? :attributes
#photos << incoming unless #photos.include? incoming
else
if incoming[:id]
target = #photos.select { |t| t.id == incoming[:id] }
end
if target
target.attributes = incoming
else
#photos << Photo.new incoming
end
end
end
end
def photos
# your photo-find logic here
#photos || Photo.find :all
end
end
By using a facade model for the Photoset, you can keep your controller and view logic simple and straightforward, reserving the most complex code for a dedicated model. This code probably won't run out of the box, but hopefully it will give you some ideas and point you in the right direction to resolve your question.
Rails does have a way to do this - I don't know when it was introduced, but it's basically described here: http://guides.rubyonrails.org/form_helpers.html#using-form-helpers
It took a bit of fiddling to alter the configuration properly for the case where there's no parent object, but this seems to be correct (it's basically the same as gamov's answer, but cleaner and doesn't allow for "new" records mixed in with the "update" records):
<%= form_tag photos_update_path do %>
<% #photos.each do |photo| %>
<%= fields_for "photos[#{photo.id}]", photo do |pf| %>
<%= pf.text_field :caption %>
... [other fields]
<% end %>
<% end %>
<% end %>
In your controller, you'll end up with a hash in params[:photos], where the keys are photo IDs, and the values are attribute hashes.
You can use "model name[]" syntax to represent multiple objects.
In view, use "photo[]" as a model name.
<% form_for "photo[]", :url => photos_update_path do |f| %>
<% for #photo in #photos %>
<%= render :partial => "photo_form", :locals => {f => f} %>
<%= submit_tag "Save"%>
<% end %>
<% end %>
This will populate input fields just like you described.
In your controller, you can do bulk updates.
def update
Photo.update(params[:photo].keys, params[:photo].values)
...
end
Indeed, as Turadg mentioned, Rack (Rails 3.0.5) fails if you mix new & existing records in Glen's answer.
You can work around this by making fields_for work manually:
<%= form_tag photos_update_path do %>
<% #photos.each_with_index do |photo,i| %>
<%= fields_for 'photos[#{i}]', photo do |pf| %>
<%= pf.hidden_field :id %>
... [other photo fields]
<% end %>
<% end %>
This is pretty ugly if you ask me, but it's the only way I found to edit multiple records while mixing new and existing records.
The trick here is that instead of having an array of records, the params hash gets a array of hashes (numbered with i, 0,1,2, etc) AND the id in the record hash. Rails will update the existing records accordingly and create the new ones.
One more note: You still need to process the new and existing records in the controller separately (check if :id.present?)