nested_form shows extra fields after error messages - ruby-on-rails

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).

Related

Rails only pass params that have changed on edit submit

I have a edit form that prepopulates with the current values. Its a custom edit screen (not the default one that rails uses) and what Im using it for is for users to submit changes that will get voted on and might eventually get applied to the record. However, in the time it takes to be voted on something else might have changed and I dont want to overwrite the changes if they didnt submit a change.
EDIT: Changing to my more specific case so hopefully answers will work for it...
I have the following tables: Recipes, RecipeIngredients, RecipeSteps, RecipeChanges. On the show view of my recipes it displays all the ingredients/steps and there is a tab that then changes just the ingredients/steps to forms as to allow the user to submit changes. I dont want these changes applied though. Instead Im trying to create a voting system where people can vote on them. So what I have decided on is to convert the parameters from the form into a json string and save it in the RecipeChanges table under a single column (instead of using two table for ingredient changes and step changes). Heres the code for the form (html removed to make it easier to see the rails stuff):
<%= form_for #recipe, url: recipe_recipe_changes_path(#recipe), html: {method: "post"}, remote: true do |f| %>
<%= f.fields_for :recipe_ingredients, f.object.recipe_ingredients.order(:order) do |ff| %>
<%= ff.hidden_field :order, class: "position" %>
<%= ff.text_field :ingredient, placeholder: "Add Ingredient (e.g. 3 cups cooked rice)" %>
<label><%= ff.check_box :_destroy %>Remove</label>
<% end %>
<%= f.fields_for :recipe_steps do |ff| %>
<%= ff.hidden_field :order, class: "position"%>
<%= ff.text_area :step %>
<label><%= ff.check_box :_destroy %>Remove</label>
<% end %>
<%= submit_tag "Submit", class: "button" %>
<% end %>
So this sends a recipe object to my RecipeChange controller and there I handle the params to save them as the json string like so:
def create
#change = RecipeChange.new
#change.recipe_id = params[:recipe_id]
#change.new_recipe = recipe_change_params.to_json
#if #change.save
#add alert for successfully adding
#else
# add code for error handling
#end
end
This works like I want except for it saves all the ingredients/steps and I would like to only save what they have changed. I had two thoughts on how to do this but not sure how to accomplish it.
Check if the fields have changed when they click the submit button and only send the ones that have been edited (not sure if possible)
In the controller grab the original recipe (I have the id so that would be easy) and loop through the ingredients/steps and compare them and remove any that are identical....this is the method I think would be better but not sure how to loop through the hashes to accomplish this
Have a look at ActiveModel::Dirty. http://api.rubyonrails.org/classes/ActiveModel/Dirty.html#method-i-changed
You can do something like:
changes = bag.changed_attributes and get a hash of that attributes that changed, and then save those with bag.update_attributes(changes), for example.
This is a bit old now but I've come across the same or similar scenario and wanted to share for others.
In my case I populate some nested form fields based on an existing object in my #new action. However, in my #create action I did not want to save these nested form params unless they were actually modified compared to the original existing object.
In this case, ActiveModel::Dirty would always be true as it would compare [nil, "value"].
I first tried to modify the params in my #create action and compare them to the original existing object similar to this discussion but this got messy and felt wrong.
I ended up saving all records then doing a cleanup with an instance method in my model that I call after save in my controller's #create action. Still feels a bit dirty but it's working.
Example:
# controllers/changes_controller.rb
# ChangeController#create
def create
# ... shortened for example ...
if #instance.save
#instance.remove_clean_changes
format.html
end
end
# models/change.rb
# Change#remove_clean_changes
# Loop over all original objects and compare the necessary attributes
# to the changes. If they match, they are clean and should be deleted.
def remove_clean_changes
original_objects = self.original_objects
changes = self.changes
original_objects.each do |original_object|
changes.each do |change|
change.destroy if (change.attribute_one == original_object.attribute_one &&
change.original_object_id == original_object.id)
end
end
end

Rails: simple 'Setting' model with key, value attributes

I’m new to rails and I’ve a pretty simple situation to solve but I cannot figure out how to proceed with it.
I want to create a simple ‘Setting’ model with key, value attributes. ‘SettingsController’ may contain 2 public methods only index and update. Only index action will have a view file with a form whose fields will represent each record of the ‘Settings’ table.
I want to be able to define some permitted keys (may be using some private method) and I want the form to create or update the record of relevant fields on submitting the form to update action.
Now, I don’t know exactly what code should I use in controller for index and update actions and in the index view file for the form which can create/update multiple records at the same time and can show updated values all the time. How do I proceed with it?
Update # 1:
I've managed to write some controller actions as below (based on some tutorial):
class Admin::SettingsController < ApplicationController
def index
#settings = Setting.all
end
def update
setting_params.each do |key, value|
Setting.where(key: key).first.update_attribute :value, value
end
redirect_to admin_settings_path, notice: "Settings saved."
end
private
def setting_params
params.require(:settings).permit(:site_title, :site_desc)
end
end
The form code in index view template is given below:
<h1>Settings</h1>
<%= form_tag admin_settings_path, method: "put" do %>
<p>
<label>Site Title:</label>
<%= text_field_tag "settings[site_title]" %>
</p>
<p>
<label>Site Description:</label>
<%= text_field_tag "settings[site_desc]" %>
</p>
<p>
<%= submit_tag "Save settings" %>
</p>
<% end %>
This forms saves the values correctly in the database but the saved values doesn't persist in form fields.
Maybe the problem is in the index action. Setting.all returns an array of Setting record, not a hash like { key1: value, key2: value } which I think you are trying to achieve. The form, therefore, displays data improperly. You can try this:
def index
#setting = {}
pairs = Setting.pluck(:key, :value)
pairs.each { |key, value| #setting[key] = value }
#setting
end

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.

form_for Model.new "f.error_messages"

the f.error_messages form builder helper has been depreciated in Rails 3.
How do I get the error messages for a form with the opening tag:
<%= form_for Model.new %>
(I'm using Model.new because I want to be able to load an undefined multiple number of these forms onto a single page)
You can't. Not with a form builder like this one.
Basically, error messages are stored inside the object you use to build a form. If you build a new one every time, you get a clean object without errors.
What you need to do is persist the object filled in by the user between requests. Typically this is done by creating a new object in controller:
#model = Model.new
The essence of this is, that new view uses #model to render a form. And the trick is to have a possibility to render the same view in other actions that also provide #model. That said, if you do something like this in create:
#model = Model.new(model_params)
if #model.save
# success
else
render :new
end
It can render new view, because it assigns #model too; in this case, it will contain errors with messages and other stuff. All this is inside #model.errors – which is always empty in new action.
It's not that different for multiple forms, bear in mind that you always submit only one. You may switch to rendering an array of forms, in that case you could have #models array:
#models = [form1, form2, form3]
In that case, if saving fails, assign that array again and either replace the form the user tried to fill in (if you can identify it), ot prepend/append that form with errors to that array.
#models[index_of_submitted_form] = form_from_user #replace
#models << form_from_user # append
#models.unshift form_from_user # prepend
In Rails 3, you can access errors using #model.errors
<%= form_for #model = Model.new do |f| %>
<% #model.errors.full_messages.each do |msg| %>
<p><%= msg %></p>
<% end %>
# ...
# ...
# ...
<% end %>

Repopulating Rails form for an array of objects

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.

Resources