Simple - getting record id instead of desired attribute - ruby-on-rails

This is a really simple question but I am new. I am trying to create a dropdown menu with values populated from the model. However, instead of displaying the city names, I am getting the record id's like: 0x007fee0b7442c0 (not sure if these are called id's, I think there is another term).
Controller:
#cities = City.find(:all, select: "name")
View:
<%= f.select(:city, #cities) %>
What am I doing wrong?

If you want just the name attribute from the database then do:
#cities = City.pluck(:name)
# => ["Sydney", "Melbourne", "Canberra"]

Try: select(object, method, choices, options = {}, html_options = {})
#cities = City.select(:name)
<%= f.select(:city, #cities.collect {|p| [ p.name, p.name ] }) %>

There is a guide to using collection_select here
http://www.fmhcc.com.au/ruby/rails/using-collection_select-in-rails/

You can also improve on #cities = City.find(:all, select: "name") by instead doing
#cities = City.pluck(:name)

If you want to display city name in the select box, and you want to pass the city id as a parameter on form submit, use this
f.select :city, #cities.map {|c| [ c.name, c.id ] }

Related

Rails how to fetch data from model specified in form select?

Let's say I have this form select :
<%= form_with(model: task, local: true) do |form| %>
<%= form.select(:person_id, Person.all.collect {|c| [c.person_name, c.id]}, {prompt: "Select a person"}, {class: 'col-md-12'}) %>
In my task controller, in my create def, I'd want to be able to fetch the person_name but it turns out I can't. For example, I can fetch the id by doing #task.person_id, but I can't do #task.person_name. How would I achieve this? I'm thinking about maybe options_for_select or maybe collection_select, but I'm very unsure.
Anyone has an idea? Thank you :)
What if you change collect to select
may that helps
#autoComplete = Prom.select('promname').collect { |p| p.promname }
Maybe you can access the person name by
#task.person.person_name
or if you want to get person name from task,
add this in task.rb
def person_name
self.person.person_name if !self.person_id.nil?
end
then you can use #task.person_name in your controller
Turns out I fixed it by doing this in my def create of the controller :
name = #task.person_id
#person = Person.find(name)

Search through .select().group().distinct possible?

I want to create a search select that works on unique names through grouping...
The goal is to find duplicates to then use as a search parameter.
I want to find LineItem's :store_title's that match so I can create a select drop down for a way to search through LineItem's that match the specific :store_title.
Example:
LineItem DB:
line_item.title = "Hello"
line_item.title = "Hello"
line_item.title = "Okay"
line_item.title = "Bar"
I want to have a drop down select_tag in a search for the following:
[select]
"Hello"
"Okay"
"Bar"
And append all results that match LineItem.title of those which is selected.
I tried a few ways:
I so far have been able to get the unique drop down select field to "work" but not sure if it is the right way because it creates an array:
Controller:
#vendor_line_items = LineItem.where(vendor_id: #vendor.id).select(:store_title).group(:store_title).distinct
if params[:search]
#orders = Order.line_item_search(params[:search]).joins(:line_items).where(line_items: {vendor_id: #vendor.id})
end
ORders Model:
def self.line_item_search(search)
scope = joins(:line_items)
line_items = scope.where(line_items: { id: LineItem.where(store_title: "#{search.downcase}") })
line_items
end
View:
<%= form_tag vendor_orders_path, :method => 'get' do %>
<%= collection_select(:search, params[:search], #vendor_line_items, :store_title, :store_title, {}, {class: "form-control-sm col-5"})%>
<%= submit_tag "Search", :name => nil, class: "btn btn-primary btn-sm" %>
<% end %>
Error:
Undefined method 'downcase' for ["store_title"]:Array:
Can I alter my model to allow the array, or should i be finding the unique store titles another way?
If you just want to downcase the strings in that array, you could do something like this:
line_items = scope.where(line_items: { id: LineItem.where(store_title: search.map(&:downcase)) })
That will provide a list of downcased strings to your query.
I tried finding a way to remove the brackets before searching so the brackets were not present in the search parameters but couldn't figure it out.
Doing...:
line_items = scope.where(line_items: { id: LineItem.where(store_title: "#{search.join(', ')}") })
Solved it. This removes the brackets before searching the DB and works.
**
UPDATE:
**
View for response and answer to my issue:
Rails search form through arrays doesn't work on second search attempt

Multiple identical collection_select tags in a form in Rails

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

Rails - how to return just text of an ActiveRecord record

I have a model of cities and one of the attributes is the city 'name'.
I've done a helper method to return 'n' number of cities and the implementation is below:
helper method:
def list_cities(start, stop)
cities = City.find(:all, order: "name asc", limit: stop-start, select: "name")
cities.each do |city|
"<li> #{city.name} </li>"
end
end
view code:
<%= list_cities(1,22) %>
However, it returns the following in the view:
[#<City name: "Abilene">]
How do I get just the text of the city name and get rid of the rest of the query?
The problem is that your method list_cities does not return the string you think it does. It just returns an array of city objects because that's what the method each does. It looks like you're mixing controller and view logic into a helper. What I'd do is:
Set a #cities instance variable in the controller:
#cities = City.find(:all, order: "name asc", limit: stop-start, select: "name")
In the view:
<% #cities.each do |city| %>
<li><%= city.name %></li>
<% end %>
This way you keep your controller and view logic separated.
What Erez answered is the best way to approach this issue. But I'd like to share what you can do to your method to achieve what you want with minimal changes. You need to change your helper to the following
def list_cities(start, stop)
cities = City.all(order: 'name ASC', limit: stop - start, select: 'name')
cities.map { |city| "<li> #{city.name} </li>" }.join.html_safe
end
The other changes are just small refactors to reduce code. Without knowing the rails version you're using, I didn't change the query but if you're using 3.2, you should have access to pluck which is faster since it won't create ActiveRecord objects.
Try:
def list_cities(start, stop)
content = ''
cities = City.find(:all, order: "name asc", limit: stop-start, select: "name")
cities.each do |city|
content << "<li> #{city.name} </li>\n"
end
content
end

ruby on rails f.select options with custom attributes

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

Resources