Rails simple_from: specify model id in input id - ruby-on-rails

Simple Form generates by default id for inputs in form some this way id="#{model_name}_#{attr_name}".
I need to include id of model in this input id, because i have several forms of models on single page (nested). So id would be like id="#{model_name}_#{model_ID}_#{attr_name}"
By example
= simple_form_for([#site, supply]) do |f|
= f.input :name
...
Generates form like it:
<form ... id="edit_supply_4">
<input ... id="supply_name" ... >
But i need this:
<form ... id="edit_supply_4">
<input ... id="supply_4_name" ... >
How to do this?

Passing a input_html hash allows you to customise the attributes of the input:
= simple_form_for([#site, supply]) do |f|
= f.input :name, :input_html => {
:id => "#{#site.model_name}_#{#site.id}_name"
}

Related

Is there a way to get the `id` of a nested_form field helper element?

I am trying to create the for attribute for a label in a nested form (Using nested_form). Is there a way to get the id of a corresponding f.checkbox?
HAML:
= label_tag '???', "Set as On", class: primary_btn
= f.check_box :is_on
Additional Information:
The current Model structure is like Post has many Images with field is_on
So I would like to create a nested field group like:
<label class="primary_btn" for="post_images_attributes_0_is_on">Set as primary</label>
<input id="post_images_attributes_0_is_on" name="post[images_attributes][0][is_on]" style="display:none;" type="checkbox" value="1">
The trick is to use fields_for. It gives you a "scoped" form builder instance which creates inputs for the nested fields.
= form_for (:post) do |f|
# ... a bunch of fields
= f.fields_for :images do |builder|
= builder.label :is_on, "Set as primary"
= builder.check_box :is_on
However your solution has some real gotchas:
Every time you change the primary image you need to update all the post.images to ensure that only one image has the is_on flag.
You need to do primary_image = post.images.where(is_on: true)
It won't work if the image can belong to many posts.
A better solution is create a special relation to the primary image on Post.
class Post < ActiveRecord::Base
has_many :images
has_one :primary_image, class_name: 'Image'
end
This would store the primary image as an integer in posts.primary_image_id instead of as a boolean flag on images.
We can use collection_select to get select tag to display the primary image attribute.
= form_for (#post) do |f|
# ... a bunch of fields
= f.fields_for :images do |builder|
# ...
= f.collection_select(:primary_image, #post.images, :id, :name)
This admittedly is not really optimal from a user experience perspective. A solution which requires javascript would be to have a hidden field for :primary_image and update its value when the user clicks the checkbox. If you are unsure how to do this please create a new question since its out of the scope of your original question.

html not working in haml/ror form_for

I am new to ror. How do I give required attribute for an input[not the required label that is provided by ruby]. When I write this,
= form.text_field :title, {html:{required: "required"}}
browser shows this,
< input html="{:required=>"required"}" id="key_date_title"
name="key_date[title]" size="30" type="text">
If you look at text_field helper. It requires 3 arguments: object_name, method and options = {}. You can pass in any standard HTML attributes directly in options argument.
= form.text_field :title, required: true

Adding a value-dependent data attribute to a simple_form checkbox collection

I'm generating a list of checkboxes for a single collection like so:
= f.input :parts, as:check_boxes, collection: #parts_list
I want some checkboxes in the collection to disappear/reappear depending on the value of a select widget higher up in the form. (e.g. choosing "Tracker Robot" from the Robot select means that the "Legs" part checkbox disappears and the "Wheels" checkbox appears, etc.)
What I'd like to do is attach a computed data attribute to each individual Part checkbox, with the attribute value listing the Robots that can use that Part; then some JS will do the work of hiding/showing the checkboxes. However, I don't know how I can generate those data attributes using simple_form.
I would normally create a custom "parts" input, but there seems to be a problem with making custom collection inputs; it looks for a named method (collection_parts) inside form_builder.rb, which won't exist, and if I try and extend the FormBuilder it sends me down a major rabbit hole.
I could write some JS to load the data attrs into the generated HTML, but then I have to generate custom JS based on my Rails data, and that feels like the wrong way to do it.
Let's assume that the form is for Order model and you are changing the parts collection based on the value of a field called region.
Update the form view. Specify the id for form, region field and parts field.
= simple_form_for(#order, :html => { :id => "order-form"}) do |f|
= f.input :region, :wrapper_html => { :id => "order-form-region", |
"data-parts-url" => parts_orders_path(:id => #order.id, :region => #order.region)} |
= f.input :parts, as: check_boxes, collection: #parts_list, |
:wrapper_html => { id' => 'parts-check-box-list'} |
Add a new action called parts in the route.rb file.
resources :orders do
collection do
get :parts
end
end
Add the new action to your controller
class OrdersController < ApplicationController
# expects id and region as parameters
def parts
#order = params[:id].present? ? Order.find(params[:id]) : Order.new
#parts_list = Part.where(:region => params[:region])
end
end
Add a helper
def parts_collection(order, parts_list)
"".tap do |pc|
# to generate the markup for collection we need a dummy form
simple_form_for(order) do |f|
pc << f.input(:parts, as: check_boxes, collection: parts_list,
:wrapper_html => {:id => 'parts-check-box-list'})
end
end
end
Add a js view for the action (orders/parts.js.erb)
$('#parts-check-box-list').replaceWith('<%= j(parts_collection(#order, #parts_list)) %>');
Register data change event handlers for region field in your application.js
$(document).ready(function() {
$('#order-form').on("change", "#order-form-region", function () {
// Access the data-parts-url set in the region field to submit JS request
$.getScript($(this).attr('data-parts-url'));
});
});
I think you can do it like this:
= f.input :parts do
= f.collection_check_boxes :parts, #parts_list, :id, :to_s, item_wrapper_tag: :label, item_wrapper_class: :checkbox do |b|
- b.check_box(data: { YOUR DATA ATTRIBUTES HERE }) + b.text
this may be simpler.
Assumptions
#robots - an array containing the list of robots
#parts - a hash containing a list of parts for each robot
Sample Code
# controller
#robots = %w[tracker nontracker]
#parts = { tracker: %w[wheels lcd resistor], nontracker: %w[lcd resistor] }
# view
= f.input :robots, as: :select, collection: #robots, input_html: { id: 'robot-select' }
#parts-list
:javascript
var parts = #{#parts.to_json};
$(document).ready(function() {
$('#robot-select').change(function() {
$('#parts-list').html('');
$(parts[$(this).val()]).each(function(index, text) {
$('#parts-list').append('<input type="checkbox" value=' + text + '>' + text + '</input>')
})
})
})
you can see this working if you clone https://github.com/jvnill/simple_form_search_app and go to /robots
Some input options in SimpleForm accept a lambda that gets called for every item in a collection:
f.input :role_ids, :collection => (1..10).to_a,
:label_method => :to_i, :value_method => :to_i,
:as => :check_boxes, :required=> true,
:disabled => ->(item){ item.even? }
but input_html doesn't seem to be one of them.
The solution is probably to create a custom SimpleForm collection input that applies the data attributes itself. Not as flexible perhaps, but I think this is the only way to go for now.
There's a tutorial page on GitHub that should get you started.

nested forms problems on edit, unwanted hidden fields

I'm working in Rails2, the helper creates a hidden_field which is not helping me...
I load the input like this:
- remote_form_for([:admin, #user]) do |f|
// [...]
- f.fields_for :account_user do |a|
= a.collection_select :id, #accounts, :id, :name}, { :style => "width:330px;"}
and I get a result like this:
<select style="width: 330px;" name="user[account_user_attributes][id]" id="user_account_user_attributes_id">
<option value="20">public</option>
<option value="21">Test Account</option>
<option value="73">ggg</option>
</select>
<input type="hidden" value="175" name="user[account_user_attributes][id]" id="user_account_user_attributes_id">
when I reach the controller my params hash is this:
(rdb:228) y params
--- !map:HashWithIndifferentAccess
commit: Save booker
_method: put
action: update
id: "50"
controller: admin/users
user: !map:HashWithIndifferentAccess
user_role: agent
password_confirmation: ""
username: ERIK
account_user_attributes: !map:HashWithIndifferentAccess
id: "175"
password: ""
email: e#visrez.com
The value 175 in the hash refers to #user.account_user.id, the old value, but I don't have the #user.account.id of the new selection.
What am I doing wrong?
Thank you
EDIT
I commented out the collection_select, and the hidden_field was still there, therefore... I need to change the title.
If I comment out f.fields_for the hidden_field disappears.
As pointed out by #jaydel, this is (very) probably the reason why I don't get the right value in the hash. - And the quest goes on...
Actually it's not my code, so now I'm checking for any overriding, as looks like it's not the standard behavior
So we found out that the hidden field was originated by
- remote_form_for([:admin, #user]) do |f|
- f.fields_for :account_user do |a| # <------- here
= a.collection_select :id, #accounts, :id, :name
A better look to the api unvealed the problem, and here is the correct use of fields_for:
- remote_form_for([:admin, #user]) do |f|
- f.fields_for #user.account_user do |a|
= a.collection_select :id, #accounts, :id, :name
So the method instead of the symbol.
Note: This code answers the question, but in my case I don't really need any fields_for in this relationship.
This is just a guess, but you're using user[account_user_attributes][id] in two places: the select itself, and the hidden input below it...is that something you put in?
When I use collection_select I don't see the hidden input in the source...
If this is indeed something you are putting in, it's possible that the hidden input is what's being picked up, rather than what the user selected in the dropdown.

Ruby on Rails: Submitting an array in a form

I have a model that has an attribute that is an Array. What's the proper way for me to populate that attribute from a form submission?
I know having a form input with a field whose name includes brackets creates a hash from the input. Should I just be taking that and stepping through it in the controller to massage it into an array?
Example to make it less abstract:
class Article
serialize :links, Array
end
The links variable takes the form of a an array of URLs, i.e. [["http://www.google.com"], ["http://stackoverflow.com"]]
When I use something like the following in my form, it creates a hash:
<%= hidden_field_tag "article[links][#{url}]", :track, :value => nil %>
The resultant hash looks like this:
"links" => {"http://www.google.com" => "", "http://stackoverflow.com" => ""}
If I don't include the url in the name of the link, additional values clobber each other:
<%= hidden_field_tag "article[links]", :track, :value => url %>
The result looks like this: "links" => "http://stackoverflow.com"
If your html form has input fields with empty square brackets, then they will be turned into an array inside params in the controller.
# Eg multiple input fields all with the same name:
<input type="textbox" name="course[track_codes][]" ...>
# will become the Array
params["course"]["track_codes"]
# with an element for each of the input fields with the same name
Added:
Note that the rails helpers are not setup to do the array trick auto-magically. So you may have to create the name attributes manually. Also, checkboxes have their own issues if using the rails helpers since the checkbox helpers create additional hidden fields to handle the unchecked case.
= simple_form_for #article do |f|
= f.input_field :name, multiple: true
= f.input_field :name, multiple: true
= f.submit
TL;DR version of HTML [] convention:
Array:
<input type="textbox" name="course[track_codes][]", value="a">
<input type="textbox" name="course[track_codes][]", value="b">
<input type="textbox" name="course[track_codes][]", value="c">
Params received:
{ course: { track_codes: ['a', 'b', 'c'] } }
Hash
<input type="textbox" name="course[track_codes][x]", value="a">
<input type="textbox" name="course[track_codes][y]", value="b">
<input type="textbox" name="course[track_codes][z]", value="c">
Params received:
{ course: { track_codes: { x: 'a', y: 'b', z: 'c' } }
I've also found out that if pass your input helper like this you will get an array of courses each one with its own attributes.
# Eg multiple input fields all with the same name:
<input type="textbox" name="course[][track_codes]" ...>
# will become the Array
params["course"]
# where you can get the values of all your attributes like this:
params["course"].each do |course|
course["track_codes"]
end
I just set up a solution using jquery taginput:
http://xoxco.com/projects/code/tagsinput/
I wrote a custom simple_form extension
# for use with: http://xoxco.com/projects/code/tagsinput/
class TagInput < SimpleForm::Inputs::Base
def input
#builder.text_field(attribute_name, input_html_options.merge(value: object.value.join(',')))
end
end
A coffeescrpt snippet:
$('input.tag').tagsInput()
And a tweak to my controller, which sadly has to be slightly specific:
#user = User.find(params[:id])
attrs = params[:user]
if #user.some_field.is_a? Array
attrs[:some_field] = attrs[:some_field].split(',')
end
I had a similar issue, but wanted to let the user input a series of comma separated elements as the value for the array.
My migration uses rails new ability (or is it postrges' new ability?) to have an array as the column type
add_column :articles, :links, :string, array: true, default: []
the form can then take this input
<%= text_field_tag "article[links][]", #article.links %>
and it means the controller can operate pretty smoothly as follows
def create
split_links
Article.create(article_params)
end
private
def split_links
params[:article][:links] = params[:article][:links].first.split(",").map(&:strip)
end
params.require(:article).permit(links: [])
Now the user can input as many links as they like, and the form behaves properly on both create and update. And I can still use the strong params.
For those who use simple form, you may consider this solution. Basically need to set up your own input and use it as :array. Then you would need to handle input in your controller level.
#inside lib/utitilies
class ArrayInput < SimpleForm::Inputs::Base
def input
#builder.text_field(attribute_name, input_html_options.merge!({value: object.premium_keyword.join(',')}))
end
end
#inside view/_form
...
= f.input :premium_keyword, as: :array, label: 'Premium Keyword (case insensitive, comma seperated)'
#inside controller
def update
pkw = params[:restaurant][:premium_keyword]
if pkw.present?
pkw = pkw.split(", ")
params[:restaurant][:premium_keyword] = pkw
end
if #restaurant.update_attributes(params[:restaurant])
redirect_to admin_city_restaurants_path, flash: { success: "You have successfully edited a restaurant"}
else
render :edit
end
end
In your case just change :premium_keyword to the your array field
I had some trouble editing the array after implementing this for my new.html.erb, so I'll drop my solution to that problem here:
Edit a model property of type array with Rails form?

Resources