Repopulating Rails form for an array of objects - ruby-on-rails

I want to create multiple user objects using one form. I have a form which looks like this:
= form_for #users, :url => "batch_add" do |f|
- #users.each_with_index do |user, index|
= fields_for "I-DONT-KNOW WHAT-GOES-HERE" do |u|
= render :partial => "user_fields", :locals => {:i => index}
I have two methods in my Users controller as follows. The first method (batch_add_new) is called when the form is first loaded (GET), and the second method (batch_add_create) is called when the form is submitted (POST).
def batch_add_new
#users = [User.new]
end
def batch_add_create
#users = []
i = 0
while(!params["user_" + i.to_s].nil?)
u = User.new(params["user_" + i.to_s])
#users << u
i = i + 1
end
end
if #users.all?(&:valid?)
#users.all?(&:save)
flash[:notice] = "Users have been sucessfully created!"
redirect_to "/some/path"
else
render :batch_add_new
end
When I submit my form, I get params like this (if I had tried to add two users):
{...,"user_0"=>{"role_id"=>"1","name"=>"X"},
"user_1"={"role_id"=>"2","name"=>"Y"}, ...}
What should I pass into fields_for such that this form will work properly and the fields will re-populate on failed validation? I have tried passing in users[], user as suggested in Multiple objects in a Rails form but can't seem to get the syntax right.

I solved this by re-factoring my form to represent a single object model, which accepts nested attributes for user (as suggested in the link I posted in the question [here]). I end up with an extra model in my database (and extra files for controller/views), but the controller code is much cleaner and I am able to make more use of rails magic.

Related

nested_form shows extra fields after error messages

I'm using simple_nested_form_for to build a form with nested fields. Fields are added dynamically
When rendering the form with errors (via create) the nested fields go wrong.
The same nested fields are shown multiple times and with the wrong index values in the name elements.
For example the FormBuilder index in the nested field is initially a random number such as 1454793731550. After re-rendering they simply become normal increments 0-n.
Why is the FormBuilder index initially a random number?
Any suggestion what could be going on here?
def new
#transaction = current_company.transactions.build
#transaction.subtransactions.build
end
def create
#transaction = current_company.transactions.new(transaction_params)
if #transaction.save
redirect_to dashboard_url
else
#transaction.subtransactions.build
render :action => 'new'
end
The index is the child_index of the nested fields. This is simply a way for Rails to individually identify the various field names of the HTML form elements:
<%= f.fields_for :association do |a| %>
<%= a.text_field :x %> #-> "child_index" id will either be sequential (0,1,2)
<% end %>
The child_index doesn't matter. As long as it's unique, it should be passed to the controller as follows:
params: {
association_attributes: {
0: { x: "y", z: "0" },
1: { ... }
}
}
A trick often used is to set the child_index to Time.now.to_i, which allows you to add new fields out of scope:
<%= f.fields_for :association, child_index: Time.now.to_i do |a| %>
In regards your new action with the likely issue is that your subtransactions object is being built each time (irrespective of whether the instance is populated with previous data).
We've had this issue before, and I believe that we solved it with a conditional:
def new
#transaction = current_company.transactions.build
#transaction.subtransactions.build unless #transaction.errors.any?
This should maintain the object's integrity through the submission process. IE if an error occurs, I believe Rails stores the associated object in memory (like it does with the parent).

I want to use one controller and html.erb files for my dynamic table. How I will do it in Ruby On Rails?

I stored all the tablename I've created to Menu table. And every time I add the table in Menu, it will automatically create a link under Menu list
see below.
I want each table in Menu to have a Listing, New, Edit, and Delete.
see below.
I have a controller prj_menus_controller, I will just pass the id of the table from Menu table.
here is the code for index and new in my controller.
Class PrjMenusController < ApplicationController
def index
#prj_menus = Menu.find(params[:id]).tablename.singularize.classify.constantize.all
end
def new
#prj_menu = Menu.find(params[:id]).tablename.singularize.classify.constantize.new
end
def create
#prj_menu = Menu.find(params[:id]).tablename.singularize.classify.constantize.new(prj_menu_params)
if #prj_menu.save
redirect_to :action => 'index'
else
render :new
end
end
private
def prj_menu_params
params.require("HERE IS MY PROBLEM").permit(:name)
end
end
and in my
new.html.erb
<%= simple_form_for (#prj_menu),:url => prj_menus_path, :method => :post do |f| %>
<%= f.input :name %>
<%= f.submit 'Save', class: 'btn btn-primary' %>
<%= link_to "Cancel", :back, {:class=>"btn btn-default"} %>
<% end %>
I can get the list in my index.html.erb, it is working. My problem is that I don't know how to get all params when I click the submit in new.html.erb. I got this hash
{"sample1_table"=>{"name"=>"test 6"}, "commit"=>"Save","controller"=>"prj_menus", "action"=>"create"}
It is correct but I don't know what to put in my controller. I tried this params.require(["#{#prj_menu}"]).permit(:name), it creates new record but params[:name] does not save.
I am still a noob to Ruby On Rails and I don't know what to search for this.
I think you are mostly confused on what parameter whitelisting does and how parameters are passed from the form to the controller.
I does not really matter if the name of the form hash matches the name of the database table. It just does in most cases since that makes the most sense. It's simply representative of the REST interface of your app.
Let's say you have a action which creates Pets:
POST /pets
And in our form we have a bunch of inputs like so:
<input name="pet[name]">
Rails will map create a params[:pet] hash { name: 'Spot' }. But we want to save the pets as an Animal.
class PetsController < ApplicationController
def new
#pet = Animal.new()
end
def create
#pet = Animal.new(pet_params)
if #pet.save
# ...
end
def pet_params
params.require(:pet).permit(:name)
end
end
Animal does not care what the params key is, it just gets a hash. But we also need to tell simple_form what parameter key we want to use since it looks at the model_name attribute.
simple_form_for(#pet, as: :pet)
Gives us pet[name] instead of animal[name].
I don't get why you are so adamant about making things so difficult for yourself though unless you are creating a database administration tool in the vein of PHP_MyAdmin. And even that case you don't even want to be altering the schema of the app database at runtime.
You are going to run into huge problems when it comes to creating effective queries for getting all the menus.

Calling specific instances of a user's objects on their show page

I'm trying to list a user's wanted ads in their show page when they access /users/:id.
In my controller I have:
def show
#user = User.find(params[:id])
#wanted_ads = WantedAd.where(:user_id => params[:id])
And in my show.html.erb I have:
<%= #wanted_ads %>
Binding.pry says #wanted_ads is nil. On the actual page I get #<ActiveRecord::Relation:0x007fa3b5a93408>. This is seemingly a simple thing I'm trying to do -- what am I missing?
The where function returns a ActiveRecord::Relation.
So, you can call first to get the first element, last to get the last one or all to get all elements stored in an array called #wanted_ads.
#wanted_ads = WantedAd.where(:user_id => params[:id]).all
You can then go through this array and choose the attributes you want to pass to the view for each element.
Just a tip:
You should have in your User model an ActiveRecord relation, like this:
has_many :wanted_ads
And in your WantedAd model, like this:
belongs_to :user
And with this, you have a relation of one-to-may.
Then, you can do this:
def show
#user = User.includes(:wanted_ads).find(params[:id])
end
And then, in your view:
<% #user.wanted_ads.each do |wanted_ad| %>
<%# do something %>
<% end %>
where returns a collection of objects, not just a single object. So depending on what's returned you'll want to either call first to get the single instance that ought to return, or call each to iterate over the wanted ads and display them on your page.
You're seeing an instance of ActiveRecord::Relation in pry because of the underlying query mechanism that lazy loads the results. More details can be found here.
Assuming your #wanted_ads is not nil and you want to loop through all the wanted ads...
<% #wanted_ads.each do |wanted_ad| %>
<%= wanted_ad.some_attribute_of_wanted_ad %>
<% end %>
I would also suggest you be aware of SQL injection with the following code in your controller.
#wanted_ads = WantedAd.where(:user_id => params[:id])
As it should be
#wanted_ads = WantedAd.where(":user_id => ?", params[:id])

fields_for in rails 3 issue

Converting another app from Rails 2 to Rails 3 - in one of my views I have this code:
<%fields_for "order[commission_attributes][]", commission do |f|%>
This is driven from the controller action below:
def edit
#order = Order.find(params[:id],:include=>[{:orderitems=>:item_schedules}, :memberids ],:order=>"orderitems.updated_at DESC")
#active_order_iems = []
#inactive_order_items = []
#order.orderitems.each do |oi|
oi_active = nil
oi.item_schedules.each do |is|
if (is.end_date > Date.today)
oi_active =true
break
end
end
#active_order_iems << oi if !oi_active.nil?
#inactive_order_items << oi if oi_active.nil?
end
#commissions = #order.commissions.find(:all)
#ordertypes = Ordertype.find(:all, :order=>"description")
#salesreps = Salesrep.find(:all, :order=>"fullname")
#customers = Customer.find(:all, :order=>"company_name")
#memberids = Memberid.find_all_by_customer_id(#order.customer_id)
#products = Product.find(:all, :include=>[:items],:order=>["products.description"])
#partners = Partner.find(:all, :order=>"company_name")
end
def get_reps
#salesreps = Salesrep.find(:all,:order=>"fullname")
#commission = Commission.new
render :partial=>'commission'
end
In my 'order' model I then have this:
def commission_attributes=(commission_attributes)
commission_attributes.each do |attributes|
if attributes[:id].blank?
commissions.build(attributes)
else
commission = commissions.detect{|t| t.id == attributes[:id].to_i}
commission.attributes = attributes
end
end
end
This works in rails 2, however in rails 3 I get the error below:
#order[commission_attributes]' is not allowed as an instance variable name</code></pre>
I can't seem to work out what the problem is, or how to work around this issue.. any help is very appreciated.
Andrew
The fields_for method only takes objects or symbols. It's hard to give a specific solution without more code but something like this might work:
<%= form_for #order do |form| %>
...
<%= fields_for #order.commission do |commission_form| %>
Read up on it here: http://apidock.com/rails/v3.0.0/ActionView/Helpers/FormHelper/fields_for
Another alternative is
<%= f.fields_for :commission, commission do |commission_form| %>
This should cause then submit to trigger commission_attributes= instead of messing with another model directly, and by supplying the object you can populate the form with said object.
But this is just another method worth mentioning. Here's the doc fields_for#Nested Attributes

collection.build of nested attribute through a remote_link does not create params when submitting form

I have the following model:
class Activity < ActiveRecord::Base
has_many :clientships, :dependent => :destroy, :after_add => :default_client_info
accepts_nested_attributes_for :clientships, :allow_destroy => true
end
In my controller, if I perform the following
def new
#activity = IndividualActivity.new(params[:activity])
#activity.clientships.build(:client => Client.first)
...
end
and then save the form, it creates the relevant params and submits successfully.
However, if I chose to call the following through a remote link
#activity.clientships.build(:client => Client.last)
the view is updated with the new clientship record but when I submit the form, the params[:activity] is not created for the second nested attribute. (Why not!?)
This is the view:
%h1 Create a new Activity
- form_for #activity do |f|
%div
= render "activities/client_selector", :f => f
%div
= f.submit "Save!"
Here is the remote_link's controller action
def add_client
#activity = IndividualActivity.new(session[:individual_activity])
# Refresh client
#activity.clientships.build(:client => Client.find(params[:client_id]))
respond_to do |format|
format.js
end
end
This is the add_client.html.js:
page.replace_html "selected_clients", :partial => 'activities/clients'
This is the activities/clients partial:
- form_for #activity do |f|
- f.fields_for :clientships do |client_f|
%tr
%td= client_f.hidden_field :client_id
%td= client_f.object.client.full_name
Does anyone know how I can troubleshoot this further? I seem to have come to a dead-end with my debugging... One thing to note, there is a double use of the following form_for used in new.html.haml and the activities/clients partial (is this problematic?)
- form_for #activity do |f|
I am on rails v2.3.5
Thanks
You ask about debugging, so the first step may be looking at the server log (log/development.log).
There you should see the "params" hash.
Maybe your params contain "activity"=>{"client_id"=>..} instead of "client_id"=>.. ?
Also look at the generated HTML page - use a Firebug or just use a "view source" method of your browser. Look, especially, for input names.
If everything looks OK, put a few debug calls in your action, and look at the development.log for some database activity - do the SQL queries look like they are doing what you want?
In your question there is no 'save' method. The 'build' method does NOT save the created record. Maybe this is your problem?
def add_client
logger.debug "Creating Activity"
#activity = IndividualActivity.new(session[:individual_activity])
logger.debug "Building clientship"
# Refresh client
#activity.clientships.build(:client => Client.find(params[:client_id]))
logger.debug "#activity = #{#activity.inspect}"
# Maybe you were missing this part of code?
logger.debug "Saving #activity"
#activity.save! # use a ! to easily see any problems with saving.
# Remove in production and add a proper if
logger.debug "Saved. #activity = #{#activity.inspect}"
respond_to do |format|
format.js
end
end
You should create a functional test (in case you haven't already) and ensure that if you send proper parameters, your action works as intended.
The test will narrow your search. If the test fails, you know you have a problem in the action. If the test is OK, you need to ensure the parameters are sent properly, and you probably have the problem in your view.
UPDATE:
You said you have TWO forms on the page. This may be the problem, since only one form may be sent at a time. Otherwise it would need to work in a way which can send two requests in one request.
First thing (useful in all similar problems): validate whether your page has correct HTML structure - for example http://validator.w3.org would be a good start. Try to make the code validate. I know that some people treat a "green" status as a unachievable mastery, but just it's really not so hard. With valid code you may be sure that the browser really understands what you mean.
Second: Place all your inputs in a single form. You have problems with nested attributes. For start, try to manually insert inputs with name like <input name="activity[clientship_attributes][0][name]" value="John"/>, and for existing clientships ensure that there is an input with name = activity[clientship_attributes][0][id].
This is the way nested attributes are handled.
Your view may create such fields automagically. This construction should be what you need: (it worked in one of my old project in rails 2.x, I have just replaced the names with ones you use)
<% form_for(#activity) do |f| %>
<p><%= f.text_field :activity_something %></p>
<% #activity.clientships.each do |clientship| %>
<% f.fields_for :clientships, clientship do |cform| %>
<p><%= cform.text_field :name %></p>
<p><%= cform.text_fiels :something %></p>
<% end %>
<% end %>
<% end %>
If you really want to use a partial there, don't create a new form in the partial. Use only the parts of above code.
To pass a variable to the partial, use :locals attribute in the place where you call render :partial:
<%= render :partial => 'clientship', :locals => {:form => f} %>
Then, in your partial, you may use a local variable form where you would use f outside of the partial. You may, of course, map the variables to the same name: :locals => {:f => f}

Resources