How do I specify the selected option if two of these options have the same id? I have this form input
<%= f.input :receiver_id,
label: "Client",
collection: receivers_keys_and_values,
as: :grouped_select,
group_method: :last %>
and this method to create the select options
def receivers_keys_and_values
[
["Client", Client.all.map { |c| [c.name, c.id] }],
["Program", Program.all.map { |p| [p.name, p.id] }]
]
end
The issue I have is that a client ID could be the same as a Program ID. Therefore when two ID are the same the selected one is always the Program one.
How could I specify something like?
selected: ["Client"][id]
or
selected: ["Program"][id]
This way ids of the selected elements will be different for programs and clients:
def receivers_keys_and_values
[
["Client", Client.all.map { |c| [c.name, "client_#{c.id}"] }],
["Program", Program.all.map { |p| [p.name, "program_#{p.id}"] }]
]
end
You'll also have to update form handling code in order to be able to parse input like "program_123".
You can also go fancy and refactor the code a bit:
def receivers_keys_and_values
[Client, Program].map do |type|
type.all.map { |entity| [entity.name, dom_id(entity) }
end
end
but I'm not sure if it's clearer (should produce the same result though). Up to you.
Related
I have a simple table which displays all the users and its attributes id,name,age,status,location. I want to filter on age and status.
Id name age status location
1 xz 22 single ca
2 yy 23 married ma
I am using filterrific plugin and able to display the list and the filter dropdown.
My user.rb
filterrific(
available_filters: [
:with_status,
:with_age
]
)
scope :with_age, lambda { |age|
where(age: [*age])
}
scope :with_status, lambda { |status|
where(status: [*status])
}
def self.options_for_select
order('LOWER(status)').map { |e| [e.status] }.uniq
end
def self.options_for_select2
order('LOWER(age)').map { |e| [e.age] }.uniq
end
The controller index looks like
def index
#filterrific = initialize_filterrific(
User,
params[:filterrific],
select_options: {
with_status: User.options_for_select,
with_clearance_batch_id: User.options_for_select2
},
default_filter_params: {},
available_filters: [],
) or return
#users = #filterrific.find
respond_to do |format|
format.html
format.js
end
end
the index.html.erb looks like
<%= form_for_filterrific #filterrific do |f| %>
<div>
age
<%= f.select(
:with_age,
#filterrific.select_options[:with_age],
{ include_blank: '- Any -' },
{class: 'form-control' }
) %>
</div>
<div>
status
<%= f.select(
:with_status,
#filterrific.select_options[:with_status],
{ include_blank: '- Any -' },
) %>
</div>
<% end %>
<%= render(
partial: 'browse_users/list',
locals: { users: #users }
) %>
When I go to the page I am able to see all the users. Now when I filter nothing happens. I still see all the users. Not sure what is happening. I have feeling that my scope filter is not getting applied.
To me this did the trick:
In app/assets/javascripts/application.js, I've put the line "//= require turbolinks" after "//= require filterrific/filterrific-jquery" (earlier it was before it)
After doing the above, filtering or sorting happened still only when I hit refresh in the browser, and I didn't find a solution to that, so I rather added a form submit button as mentioned in "Disable AJAX auto form submits" in the documentation (http://filterrific.clearcove.ca/pages/action_view_api.html). This way you should still click a button or hit Enter, but at least filtering / sorting works.
One more thing: I had also a problem when wanting to sort according to a field in another table (following a belongs_to foreign key relationship). If I did it as mentioned in the source of the sample app in github (https://github.com/jhund/filterrific_demo/blob/master/app/models/student.rb):
"order("LOWER(countries.name) #{ direction }").includes(:country)"
I got an SQL error. When I have changed it to something like
"joins(:country).order("LOWER(countries.name) #{ direction }")"
it solved the problem.
Super simple, dumb thing, which I can't figure out for more, than an hour now:
def user_params
params.require(:user).permit(customer_ids: []) # pg array column
end
My form:
= f.select :customer_ids,
options_from_collection_for_select(customers, 'id', 'name', user.customer_ids),
{ include_blank: 'Select customer', multiple: true, size: 15 },
class: 'form-control'
And while updating user I'm getting
Unpermitted parameter: customer_ids
How the heck in the world is that possible?
Parameters: {"utf8"=>"✓", "authenticity_token"=>"oCkUEi2pNajM0ydHUH2w6iYIq5eKjfCY5ig9U2qDTXxMqECCgQ2Dn9YtqkMqXlTmLl5q/OO8x23o/P50SnmgUg==", "user"=>{"customer_ids"=>"84"}, "commit"=>"Assign selected customer to user", "id"=>"2"}
Your form isn't sending in the customer_ids parameters as an array.
"user"=>{"customer_ids"=>"84"}
This is why. It should be (notice the square brackets):
"user"=>{"customer_ids"=>"[84]"}
If you declare the param as an array, it should be posted as an array. This is likely an issue in your form.
Usually, I would use checkboxes for something like this, but this depends on your user interface. Here's something similar I have done in the past.
= f.collection_check_boxes :customers, customers, :id, :name do |cb|
= cb.label
span.pull-right = cb.check_box
Look at the collection form helpers in Rails. A multiselect should work, but I have not used one this way.
Try changing your select tag like this
= f.select(:customer_ids, customers.collect { |c| [ c.name, c.id ] },
{ prompt: "Select Customer"},
{ multiple: true, size: 5, class: 'form-control' })
In my Rails app I have this select box:
<%= f.select :invoice_id, current_user.invoices.numbers_ordered %>
This is my Invoice class:
class Invoice < ActiveRecord::Base
belongs_to :user
def self.numbers_ordered
order(:number).map { |i| [ i.number, i.id ] }
end
...
end
How can I add a data-attribute to each option without changing too much of my existing code?
Thanks for any help.
You can try something like this:
def self.numbers_ordered
order(:number).map { |i| [ i.number, i.id, :'data-attribute' => i.some_data ] }
end
select uses options_for_select inside and takes all the parameters for options_for_select.
See Rails API for select, options_for_select, and the comment in options_for_select
This explains it very well: https://stackoverflow.com/a/9076805/2036529
In your view, you can use:
<%= f.select :invoice_id, options_for_select(current_user.invoices.numbers_ordered) %>
and in your Invoice class, change the method to following:
def self.numbers_ordered
order(:number).map { |i| [ i.number, i.id, {'data-customAttr': "#{i.attr_1} -- #{i.attr_2}" ] }
end
I have multiple identical collection selects inside a single form. I prefer this over a multiple select list for aesthetic and UX reasons. I have to use a terrible kludge to make everything work right, and I'm wondering if there is a more elegant way to do this:
From the view:
<% 3.times do |i| %>
<%= collection_select("selected_item_" + i.to_s.to_s, :name, #items, :name, :name, { :include_blank => true }, { id: "selected_item_" + i.to_s }) %>
<% end %>
From the controller:
ItemContainer = Struct.new(:name)
3.times do |i|
param = ('selected_item_' + i.to_s).to_sym
instance_variable = '#' + param_name
if params[param] && !params[param].empty?
#selected_items << params[param][:name]
instance_variable_set(instance_variable, ItemContainer.new(params[param][:name]))
end
end
#selected_channels.each.... # do what I need to with these selections
Most of these gymnastics are needed to ensure that the item is still selected if the page is refreshed. If there were some way to force collection select to use arrays, that would be the answer, but I couldn't make that work.
If I understang right you're looking for selegt_tag method (docs: http://api.rubyonrails.org/classes/ActionView/Helpers/FormTagHelper.html#method-i-select_tag)
You can write something like this
select_tag "people[]", options_from_collection_for_select(#people, "id", "name")
select_tag "people[]", options_from_collection_for_select(#people, "id", "name")
and it youd output two selects for people, which would be sent as an array on submit.
if you use the [] naming in your collection_select calls then the params will send over the data as an array
I am a bit confused as to the usage of collection_select here as it doesn't seem like you are using a model object? this example using select_tag - might be able to come up with something more appropriate to your issue if the model structures were known
# run this in the loop
# set selected_value to appropriate value if needed to pre-populate the form
<%= select_tag('name[]',
options_from_collection_for_select(#items, 'name', 'name', selected_value),
{ include_blank: true }
)
%>
in controller update/create action
# this works because the select tag 'name' is named with [] suffix
# but you have to ensure it is set to empty array if none are passed, usually only issue with checkboxes
names = params[:name] || []
names.each do |name|
puts name
end
side note: you can use string interpolation with ruby double quotes in places of + for string concatenation
<%= collection_select("selected_item_#{i}",
:name,
#items,
:name,
:name,
{ include_blank: true },
{ id: "selected_item_#{i}" }
)
%>
see also: http://apidock.com/rails/v3.2.13/ActionView/Helpers/FormOptionsHelper/options_from_collection_for_select
I have a form select statement, like this:
= f.select :country_id, #countries.map{ |c| [c.name, c.id] }
Which results in this code:
...
<option value="1">Andorra</option>
<option value="2">Argentina</option>
...
But I want to add a custom HTML attribute to my options, like this:
...
<option value="1" currency_code="XXX">Andorra</option>
<option value="2" currency_code="YYY">Argentina</option>
...
Rails CAN add custom attributes to select options, using the existing options_for_select helper. You almost had it right in the code in your question. Using html5 data-attributes:
<%= f.select :country_id, options_for_select(
#countries.map{ |c| [c.name, c.id, {'data-currency_code'=>c.currency_code}] }) %>
Adding an initial selection:
<%= f.select :country_id, options_for_select(
#countries.map{ |c| [c.name, c.id, {'data-currency_code'=>c.currency_code}] },
selected_key = f.object.country_id) %>
If you need grouped options, you can use the grouped_options_for_select helper, like this (if #continents is an array of continent objects, each having a countries method):
<%= f.select :country_id, grouped_options_for_select(
#continents.map{ |group| [group.name, group.countries.
map{ |c| [c.name, c.id, {'data-currency_code'=>c.currency_code}] } ] },
selected_key = f.object.country_id) %>
Credit should go to paul # pogodan who posted about finding this not in the docs, but by reading the rails source. https://web.archive.org/web/20130128223827/http://www.pogodan.com/blog/2011/02/24/custom-html-attributes-in-options-for-select
You could do this as follows:
= f.select :country_id, #countries.map{ |c| [c.name, c.id, { 'data-currency-code' => c.currency_code} ] }
This is not possible directly with Rails, and you'll have to create your own helper to create the custom attributes. That said, there are probably two different ways to accomplish what you want:
(1) Using a custom attribute name in HTML5. In HTML5 you are allowed to have custom attribute names, but they have to be pre-pended with 'data-'. These custom attributes will not get submitted with your form, but they can be used to access your elements in Javascript. If you want to accomplish this, I would recommend creating a helper that generates options like this:
<option value="1" data-currecy-code="XXX">Andorra</option>
(2) Using values with custom splitting to submit additional data. If you actually want to submit the currency-code, I would recommend creating your select box like this:
= f.select :country_id, #countries.map{ |c| [c.name, "#{c.id}:#{c.currency_code}"] }
This should generate HTML that looks like this:
<option value="1:XXX">Andorra</option>
<option value="2:YYY">Argentina</option>
Which you can then parse in your controller:
#id, #currency_code = params[:country_id].split(':')
The extra attributes hash is only supported in Rails 3.
If you're on Rails 2.x, and want to override options_for_select
I basically just copied the Rails 3 code. You need to override these 3 methods:
def options_for_select(container, selected = nil)
return container if String === container
container = container.to_a if Hash === container
selected, disabled = extract_selected_and_disabled(selected)
options_for_select = container.inject([]) do |options, element|
html_attributes = option_html_attributes(element)
text, value = option_text_and_value(element)
selected_attribute = ' selected="selected"' if option_value_selected?(value, selected)
disabled_attribute = ' disabled="disabled"' if disabled && option_value_selected?(value, disabled)
options << %(<option value="#{html_escape(value.to_s)}"#{selected_attribute}#{disabled_attribute}#{html_attributes}>#{html_escape(text.to_s)}</option>)
end
options_for_select.join("\n").html_safe
end
def option_text_and_value(option)
# Options are [text, value] pairs or strings used for both.
case
when Array === option
option = option.reject { |e| Hash === e }
[option.first, option.last]
when !option.is_a?(String) && option.respond_to?(:first) && option.respond_to?(:last)
[option.first, option.last]
else
[option, option]
end
end
def option_html_attributes(element)
return "" unless Array === element
html_attributes = []
element.select { |e| Hash === e }.reduce({}, :merge).each do |k, v|
html_attributes << " #{k}=\"#{ERB::Util.html_escape(v.to_s)}\""
end
html_attributes.join
end
Kinda messy but it's an option. I place this code in a helper module called RailsOverrides which I then include in ApplicationHelper. You can also do a plugin/gem if you prefer.
One gotcha is that to take advantage of these methods you must always invoke options_for_select directly. Shortcuts like
select("post", "person_id", Person.all.collect {|p| [ p.name, p.id, {"data-stuff"=>"html5"} ] })
will yield the old results. Instead it should be:
select("post", "person_id", options_for_select(Person.all.collect {|p| [ p.name, p.id, {"data-stuff"=>"html5"} ] }))
Again not a great solution, but it might be worth it to get to the ever so useful data-attribute.
I ran into this issue as well and created the "enhanced_select" Ruby Gem to solve this problem. You can find it here:
https://github.com/bkuhlmann/enhanced_select