I'm trying to take in an array of numbers through the use of a form. In my database I have the variable as:
t.integer "home_goal_min", default: [], array: true
In my form I have:
<%= f.label :minutes_of_home_team_goals %>
<%= f.fields_for 'home_goal_min[]', [] do |p| %>
<%= f.number_field :home_goal_min %>
<%= f.number_field :home_goal_min %>
<% end %>
In my controller I also added in the parameter as an array but this still hasn't solved my problem:
def result_params
params.require(:result).permit(:home_result, :away_result, {:home_goal_min => []}, {:away_goal_min => []})
end
However, when I use the form and enter data, I then proceed to check the database through the console but it still appears empty and I just get:
home_goal_min: []
I'm wondering what I need to do to get the numbers entered in the form to be saved in the database?
Also is there a quick way to have the form part for home_goal_min as a text field and allow the user to enter the numbers split by comma, for example as: "23,45,52" would populate home_goal_min with the array [23,45,52]
You have <%= f.number_field :home_goal_min %>
Shouldn't it be <%= p.number_field :home_goal_min %>?
Edit:
I don't think you can submit Arrays through forms without using javascript. Here's the simplest solution:
In the form:
<%= f.text_field :home_goal_mins_list, placeholder: "A comma-separated list of times of the goals" %>
In the model:
def home_goal_mins_list=(value)
self.home_goal_mins = value.split(",").map(&:strip)
end
def home_goal_mins_list
self.home_goal_mins.map(&:to_s).join(", ")
end
HOWEVER
If I were you, I would just make this data into its own table. Generally, it's a bad practice to use array fields unless your database is already storing a lot of unstructured data
Firstly, you can pass in array through a form.
It is done by appending [] to the end of the name of the inputs.
For example, your form contains:
<input name='home_goal_min[]' value='100'>
<input name='home_goal_min[]' value='200'>
<input name='home_goal_min[]' value='300'>
Upon form submission, your params will look like:
params[:home_goal_min] => ['100', '200', '300']
Docs here:
http://guides.rubyonrails.org/v3.2.9/form_helpers.html
Section 7.1 Basic Structures
However, reading through your situation, I don't think you need to pass in an array. As Ben noted in his answer, you can parse a single field before saving it. I would suggest doing it in the form object, as model has nothing to do with parsing form data.
Related
I have a form with several radio buttons. The parameter it submits is an array of hashes. In the server logs, the array of hashes doesn't seem to have a name (probably because I need to give it one). Consequently, I can't tell params.require(:availability).permit(:<HERE>) to permit it, since I don't know what name to put inside .permit().
I've tried tinkering with the private controller method do relax requirements there, but I figure that's a hack rather than a good solution.
Here is my code in the view that generates the array of hashes
<%= form_tag("/availabilities/") do |f| %>
<%= label_tag :availability %>
<% #hours.each do |hour| %>
<%= hour[:time_slot] %>
<%= radio_button_tag hour.to_s, available_in_words(hour[:time_slot_available?]) %>
<%= radio_button_tag hour.to_s, available_in_words(!hour[:time_slot_available?]) %></br>
<% end %> <!-- ends hour loop -->
<div><%= submit_tag 'Save' %></div>
<% end %> <!-- ends form -->
Note: what is being submitted via this form doesn't match the fields in the Availability model - it's more raw and will need work after submission before creating/updating records in Availability. I'm not sure if that affects things
What I know so far
I thought label_tag would give the form parameter a name by which it could be referenced in params.require(:availability) but that leads to error param is missing or the value is empty: availability
Note
This is what params looks like after submission:
<ActionController::Parameters {"authenticity_token"=>"KEuNcLzuAcQj6b+0oQ0FzjOE35f1Xq3MNNomzTnC9SCML9kaWVIFgphCgDRy5cHowxQ/N4kodNIXYCAwtCPGnA==", "{:time_slot=>2020-08-22 18:00:00 +1000, :time_slot_available?=>false}"=>"Unavailable", "{:time_slot=>2020-08-22 20:00:00 +1000, :time_slot_available?=>false}"=>"Available", "commit"=>"Save", "controller"=>"availabilities", "action"=>"create"} permitted: false>
Accessing "authenticity_token" is easy enough: params[:authenticity_token]. But I can't meaningfully access the input from the radio buttons
The name is the first parameter to almost all the FormTagHelper methods.
radio_button_tag(name, value, checked = false, options = {})
https://api.rubyonrails.org/v5.1.7/classes/ActionView/Helpers/FormTagHelper.html#method-i-radio_button_tag
In Rack (which Rails sits on top of) you can pass hashes through the parameters by using brackets:
foo[bar][a]=1&foo[bar][b]=2
Will result in:
{
foo: {
bar: {
a: 1,
b: 2
}
}
}
You can whitelist nested hashes by passing a hash to .permit:
params.require(:foo).permit(bar: [:a, :b])
You can pass arrays through the parameters by using empty brackets:
foo[bar][]=1&foo[bar][]=2
Which will result in:
{
foo: {
bar: [1,2]
}
}
You can then whitelist an array by:
params.require(:foo).permit(bar: [])
[] will allow an array of permitted scalar values.
I'm not sure exactly what #hour.to_s returns but you might need to ensure that it is something actually suited to be used as a key in a formdata key/value pair and does not mess with Rack's parameter parser.
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
A quick warning: I am pretty new to Rails, and my knowledge is somewhat cookie-cutter-esque. I know how to do certain things, but I lack that vital understanding of why they always work.
I currently have a User model that has in it a bunch of information, like address, email, etc. In addition, it also has a hash called visible. The keys in that hash are each of the pieces of information, and the value is either true or false for whether the user wishes that information to be publicly visible. While I'm not sure if this is the best way to go, I can't think of any other way other than making a whole ton of boolean variables for each bit of information. Finally, I serialize :visible for storage in the database
What I would like is in my edit view to have a checkbox beside each field of info that represents the visible attribute. After reading tons of other posts related to this topic and trying numerous variations of code, I always end up with some kind of an error. The code that looks most intuitively correct to me is as follows:
<%= form_for(#user, :id => "form-info-personal") do |f| %>
...
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.check_box :visible[:name] %>
But I get an error message saying that a Symbol cannot be parsed into an integer. I'm not sure where this parse is even trying to happen, unless its viewing :visible as an array and trying to use :name as an index.
I apologize in advance if this question is trivial/seemingly nonsensical/lacking vital information/etc. Any tips, suggestions, links, or what have you would be very appreciated, even if they're along the lines of "you're doing this fundamentally wrong, go back and do it this way".
-Nick
Rails 3.2 introduces a nice addition to ActiveRecord, which allows you to store arbitrary settings in a single field.
class User < ActiveRecord::Base
store :settings, accessors: [ :color, :homepage ]
end
u = User.new(color: 'black', homepage: '37signals.com')
u.color # Accessor stored attribute
u.settings[:country] = 'Denmark' # Any attribute, even if not specified with an accessor
So, your code could look like this:
# model
class User < ActiveRecord::Base
store :settings, accessors: [ :name_visible, :email_visible ]
end
# view
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.check_box :name_visible %>
Thanks to help from people earlier, I am getting a hang of how to serialize a nested hash into a single column. While I was able to generate the form fields at multiple levels and get values of the fields back in to a string, I am unable to retain the different levels for the hash.
My hash looks like the following:
My code looks like:
<% categoryvalue.each do |categoryproperty, categorypropertyvalue| %>
<div>
<%= f.fields_for :categories, categoryproperty do |categoryattribs| %>
<%= categoryattribs.label categoryproperty %> <br/>
<%= categoryattribs.text_field categoryproperty, :value => categorypropertyvalue %> <br/>
<% end %>
</div>
<% end %>
The final hash string in my example takes data for two categories and must look similar to the following string when it gets assigned to :categories should look like the following:
{"0" => {"Active"=>"yes", "totalproducts"=>"100", "segment"=>"Premium"}, "1" => "Active"=>"yes", "totalproducts"=>"190"}}
However, the string is coming in the following form:
{"Active"=>"yes", "totalproducts"=>"100", "segment"=>"Premium", "Active"=>"yes", "totalproducts"=>"190"}
Is there a way to differentiate the attributes of one category from another and have two separate hashes within the main hash? Right now everything gets flattened out to a single level. This is evident in how the id and names for input fields are generated. See the sample below:
<input id="product_categories_Active" name="product[categories][Active]" size="30" type="text" value="%" />
<input id="product_categories_Active" name="product[categories][Active]" size="30" type="text" value="lbs" />
What I actually want is product[categories][0][Active] and product[categories][1][Active].
Any suggestions on how to approach this?
Just in case someone stumbles on this question having a similar problem, I wanted to share the final solution.
You have to use text_field_tag, select_tag etc. instead of the fields_for helper. Use a loop (.each_with_index do |key, index|) to iterate through your hash. Then basically generate a fully qualified name for each field in the format "product_categories_0_active".
I will try to write a tutorial or blog on this once I am out of the woods on my project but I think most people will figure it out from here.
Hope it helps.
I have created a form that needs to show data from 2 tables (parent and child). When the form is submitted only the child fields are updated (the parent fields are meant to be display only). While the parent model fields are displayed these need to be protected from updates (preferably via the formbuilder, rather than via css).
FWIW this is a pretty common master/detail use case. However I have not been able to find any examples of this - most of the examples I've seen seem to be trivial/single model display/update where all displayed fields are updateable).
Any ideas/samples/suggestion/tutorials/examples of real world, multi model Rails forms would be helpful.
TIA
Just out of interest, why bother going through the motions of creating a multi-model form when you only want to update the child record? My advice would be keep your form simple, I.e. make it a child form and just display the data from the parent record. If needs be, you could even style that display to look like part of the form, although I think that may throw the user off.
If you really need to do what you are doing, I would still use CSS to disable/readonly the input fields and in your controller update action, only pass the attributes you want to update into the update_attributes method call.
Finally, maybe look into the attr_protected method to prevent the fields you may want protecting from accidental mass-assignment.
I agree with tsdbrown, I don't think a complex form is required. If you'd like to learn more about complex forms or you really have your heart set on using a complex form I'd recommend watching the Railscasts episodes (73 - 75).
As tsdbrown said before, you are adding a complexity layer to your forms that's not need. If all you want is to update a detail model, while showing some info of it's parent, you could just do so with something like:
Order number: <%= #line_item.order.number %>
Order date: <%= #line_item.order.order_date %>
<% form_for #line_item do |f| %>
<%= f.label :quantity %>
<%= f.text_edit :quantity %>
<% end %>
When you'd like to edit both, then you can research on the field_for and nested forms methods (the Railscasts suggestion mentioned before it's great).
Thx for the responses which helped resolve my problem/question. Just want to close this out in case it helps others in the future.
Turns out I had been getting an error trying to reference my parent data element (patients.lname) as it was being passed in an array of results, rather than as a single result. In my view controller I had:
#visit = Visit.all(:select => "visits.id, visits.visittype, visits.visitdate, events.patient_id, patients.lname",
:conditions => ["visits.id = ?",params[:id] ],
:joins => "INNER JOIN events on events.id = visits.event_id INNER JOIN patients on patients.id = events.patient_id" )
In my view I had (this was giving me an invalid reference as I was doing a find all above):
<h1>Editing visit for patient :
<%= #visit.lname %> # should be <%= #visit[0].lname %>
</h1>
Below is the improved (and simpler) version where I find the specific record I need (basically replacing find all with find first):
#visit = Visit.find(:first, :select => "visits.id, visits.visittype, visits.visitdate, events.patient_id, patients.lname",
:conditions => ["visits.id = ?",params[:id] ],
:joins => "INNER JOIN events on events.id = visits.event_id INNER JOIN patients on patients.id = events.patient_id" )
And in the view:
<% form_for(#visit, :builder => LabelFormBuilder) do |f| %>
<%= f.error_messages %>
Name: <%= #visit.lname %>
<%= f.text_field :visittype %>
<%= f.date_select :visitdate %>
<p>
<%= f.submit 'Update' %>
</p>
<% end %>
Sometimes it's hard to see the wood for the trees! Hope this helps someone else.