Can collection_select be used against yaml files? - ruby-on-rails

My Rails application currently uses collection_select to select lookup values for drop downs etc. This has two advantages:
The values are consistent
The id of the selected value is stored in the database, not the text value
For example:
edit.html.erb
<div class="field">
<%= f.label :course_type %><br />
<%= f.collection_select :course_type, Lookup.find(:all,:conditions => ["model_name = 'course' and field_name = 'course_type'"]), :id, :lookup_text, include_blank: false,:prompt => "Course Type" %>
</div>
course_controller.rb
private
def get_lookups
#course = Course.find(params[:id])
#course_type = Lookup.find(#course.course_type).lookup_text
show.html.erb
<b>Course type:</b>
<%= #course_type %>
My application will be multi-lingual, and Rails handles this by using locale files.
The question is: Is it possible (and sensible) to populate lookup values from yml files, rather than model/tables, and can this be easily extended to handle multiple languages? How could the above code be replaced with yml-based code?

One solution would be to keep translations in the DB, perhaps with our Traco lib. I suspect it would work with collection_select.
If you want to pull options from your translation YML files, I suggest options_for_select. All in all something like:
en.yml
en:
my_options:
one: "Option 1"
two: "Option 2"
View:
select_tag :foo, options_for_select(t("my_options").invert)
Rails i18n gives you a hash if you translate a non-leaf key, like "my_options". You need the invert because options_for_select expects the text before the value, and a translation hash is the other way around.

To translate your collection_select, you simply create a new model method (let's say, "name_translated") which returns your translation from the YAML file:
View:
<%= f.collection_select :product_id, Product.all, :id, :name_translated %>
Model:
class Product < ActiveRecord::Base
def name_translated
I18n.t(name)
end
end
YAML file:
en:
name1: "Hammer"
name2: "Plastic sheets"
name3: "Duct tape"

I use select:
<%= f.select :role, MAIN_CONFIG['manager_roles'].map { |s| [s.last, s.first] }, selected: #manager.role %>
And my yaml file main_config.yml:
manager_roles:
admin: 'Суперадмин'
partner_admin: 'Администратор'
manager: 'Менеджер'

Related

Rails ActiveRecord::AssociationTypeMismatch - ActsAsTaggableOn::Tag(#755220) expected, got "" which is an instance of String(#7280):

I'm using the acts-as-taggable-on gem to add tags on my business + service models in my Rails 6 app in order to enable users to find whatever service/business they're seeking more easily. A service will have food-specific tags available if the business is a restaurant, and more general ones if it's another type of business. Whenever I try to create either a new business or a new service, I'm getting the same error:
ActiveRecord::AssociationTypeMismatch - ActsAsTaggableOn::Tag(#755220) expected, got "" which is an instance of String(#7280):
app/controllers/services_controller.rb:12:in `create'
Here is the relevant part of my Service model code:
class Service < ApplicationRecord
belongs_to :business
acts_as_taggable_on :tags
acts_as_taggable_on :food_taggings, :service_taggings
Here's the relevant part of my new service form:
<%= simple_form_for [#business, #service] do |f|%>
<%= f.input :food_taggings, collection: Service.foodlist, input_html: {multiple: true, id: "food_tagging_new", class: "select2"}, label: "Please add some descriptive tags to the dish that you're offering so that local users could find it more easily" %>
<%= f.input :service_taggings, collection: Business.offerings, input_html: {multiple: true, id: "service_tagging_new", class: "select2"}, label: "Please add some descriptive tags to the service that you're offering so that local users could find it more easily" %>
<%= f.button :submit, 'Submit', class: 'btn btn-primary'%>
</div>
<% end %>
Here's the relevant part of my services controller code:
def create
#service = Service.new(service_params)
if #service.save
flash[:notice] = "This service was successfully added!"
redirect_to #service
else
render "new"
end
end
private
def service_params
params.require(:service).permit( :tag_list, tag_list: [], food_taggings: [], service_taggings: [] )
end
And here are the params that go along with the create new service request:
{"authenticity_token"=>"kld9sOSfro/nrINxQdKpXCZnxt6Cjb6TIw+jcjW5XmpUhvfm767dPXStOGB2vEBbckZvb87uKXlZo2KGjAo8vA==", "service"=>{"name"=>"", "description"=>"", "price_cents"=>"", "food"=>"0", "food_taggings"=>[""], "service_taggings"=>[""]}, "commit"=>"Submit", "controller"=>"services", "action"=>"create", "business_id"=>"5"}
How would I go about fixing this issue so that both of the models can get created successfully? I was already able to create some seeds for both without including any tags successfully, and I'm not sure exactly what Rails is expecting now?
To recap what acts_as_taggable_on is doing, say you declare a single taggable attribute:
class Service < ApplicationRecord
acts_as_taggable_on :tags
end
The gem does two things:
It dynamically creates an association (i.e., has_many/belongs_to) between your class and ActsAsTaggableOn::Tag, which is how the gem models tags you define. When you access tags on a instance of Service, you get an array of these Tag objects like you would with any has_many association.
It also creates a friendly convenience wrapper called tag_list (note: singluarized), which is the main way the gem expects you to interact with tags. Calling this will do the work of querying the associated Tag objects and return you a nice array of strings. Or you can assign it an array of strings, which get parsed into Tag objects.
In your form and controller, you are using the raw association references (food_taggings and service_taggings). Thus when your form POSTs, Rails properly raises an error because it is expecting those parameters to be arrays of Tag objects not arrays of strings.
If you change your form to use the convenience wrapper names for the form fields, the gem will properly parse the array of strings in your params and create the associated objects:
<%= simple_form_for [#business, #service] do |f|%>
<%= f.input :food_tagging_list, ... %>
<%= f.input :service_tagging_list, ... %>
...
<% end %>
Don't forget to alter your permitted parameters on the controller as well:
def service_params
params.require(:service).permit(tag_list: [],
food_tagging_list: [],
service_tagging_list: [])
end
Try adding include_blank: false in your form input options. I think Rails gives you this error because it will always send "" (empty string) from the taggings form.
<%= f.input :food_taggings,
collection: Service.foodlist,
input_html: {
multiple: true,
id: "food_tagging_new",
class: "select2"
},
include_blank: false # Add this
label: "Please add some descriptive tags to the dish that you're offering so that local users could find it more easily"
%>

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

Form_for select field, concatenating data into the display string

I am trying to use a form_for collection_select to display some select field options of account types.
Its occurred to me that it would be easier for the user if they could see the price of the type in each select option
this is my currently not-working code:
<%= a.collection_select :account_type, AccountType.all, :id, (:name+" - "+number_to_currency(:price)) %>
how can i concatenate the values so that (:name+" - "+number_to_currency(:price)) will actually work and not throw an error?
See the documentation:
http://api.rubyonrails.org/classes/ActionView/Helpers/FormOptionsHelper.html#method-i-collection_select
You can use the :text_method option to set the displayed text in the select dropdown.
In your AccountType model, define a method like this:
def name_with_price
"#{name} - $#{price}"
end
Then, in your view, you can use:
<%= a.collection_select :account_type, nil, AccountType.all, :id, :name_with_price %>

How to have a drop down <select> field in a rails form?

I am creating a scaffold -
rails g scaffold Contact email:string email_provider:string
but I want the email provider to be a drop down (with gmail/yahoo/msn as options) and not a text field. How can I do this ?
You can take a look at the Rails documentation . Anyways , in your form :
<%= f.collection_select :provider_id, Provider.order(:name),:id,:name, include_blank: true %>
As you can guess , you should predefine email-providers in another model -Provider , to have where to select them from .
Or for custom options
<%= f.select :desired_attribute, ['option1', 'option2']%>
You create the collection in the Contact controller -
app/controllers/contacts_controller.erb
Adding
#providers = Provider.all.by_name
to the new, create and edit methods, using a scope for the by_name in the Provider model - app/models/provider.rb - for the ordering by name
scope by_name order(:name)
Then in the view - app/views/contacts/_form.html.erb - you use
<%= f.collection_select :provider_id, #providers, :id, :name, include_blank: true %>
For rails forms, I also strongly recommend you look at a form builder like simple_form - https://github.com/plataformatec/simple_form - which will do all the heavy lifting.
This is a long way round, but if you have not yet implemented then you can originally create your models this way. The method below describes altering an existing database.
1) Create a new model for the email providers:
$ rails g model provider name
2) This will create your model with a name string and timestamps. It also creates the migration which we need to add to the schema with:
$ rake db:migrate
3) Add a migration to add the providers ID into the Contact:
$ rails g migration AddProviderRefToContacts provider:references
4) Go over the migration file to check it look OK, and migrate that too:
$ rake db:migrate
5) Okay, now we have a provider_id, we no longer need the original email_provider string:
$ rails g migration RemoveEmailProviderFromContacts
6) Inside the migration file, add the change which will look something like:
class RemoveEmailProviderFromContacts < ActiveRecord::Migration
def change
remove_column :contacts, :email_provider
end
end
7) Once that is done, migrate the change:
$ rake db:migrate
8) Let's take this moment to update our models:
Contact: belongs_to :provider
Provider: has_many :contacts
9) Then, we set up the drop down logic in the _form.html.erb partial in the views:
<div class="field">
<%= f.label :provider %><br>
<%= f.collection_select :provider_id, Provider.all, :id, :name %>
</div>
10) Finally, we need to add the provders themselves. One way top do that would be to use the seed file:
Provider.destroy_all
gmail = Provider.create!(name: "gmail")
yahoo = Provider.create!(name: "yahoo")
msn = Provider.create!(name: "msn")
$ rake db:seed
<%= f.select :email_provider, ["gmail","yahoo","msn"]%>
Please have a look here
Either you can use rails tag Or use plain HTML tags
Rails tag
<%= select("Contact", "email_provider", Contact::PROVIDERS, {:include_blank => true}) %>
*above line of code would become HTML code(HTML Tag), find it below *
HTML tag
<select name="Contact[email_provider]">
<option></option>
<option>yahoo</option>
<option>gmail</option>
<option>msn</option>
</select>
Rails drop down using has_many association for article and category:
has_many :articles
belongs_to :category
<%= form.select :category_id,Category.all.pluck(:name,:id),{prompt:'select'},{class: "form-control"}%>
In your model,
class Contact
self.email_providers = %w[Gmail Yahoo MSN]
validates :email_provider, :inclusion => email_providers
end
In your form,
<%= f.select :email_provider,
options_for_select(Contact.email_providers, #contact.email_provider) %>
the second arg of the options_for_select will have any current email_provider selected.
I wanted to display one thing (human readable) but store another (an integer id).
Small example
Here's a small example that helped:
<%= form.select(:attribute_name, {cat: 5, dog: 3} )%>
The {cat: 5, dog: 3} will display "cat" and "dog", but save 5 and 3.
Real world example
Here's the actual use case. It displays the names of sellers (that humans can read), but saves the sellers' id (an integer):
<div class="field">
<%= form.label :seller_id %>
<%= form.select :seller_id, seller_names_and_ids(), {include_blank: true}, {required: true, class: "form-control"} %>
</div>
And the helper is defined as:
def seller_names_and_ids
# We want this to produce a hash of keys (the thing to display) and values (the thing to save,
# in thise case the seller_id integer)
sellers = Seller.all
h = {}
sellers.each do |seller|
thing_to_display = seller.name + " (" + seller.id.to_s + ")"
thing_to_save_in_db = seller.id
h.store(thing_to_display, thing_to_save_in_db)
end
h
end

ruby on rails how to use FormOptionHelpers to create dynamic drop down list

I have checked some tutorials but I got confused by the parameters in this method
collection_select (object, attribute, collection, value_method, text_method, options = {}, html_options ={})
I have a map model includes: :area, :system, :file
and I want to read :area from database to a drop down list, and let user choose one
I already did #map = Map.all in the view
what the method should be?
especially the parameter "attribute". In a lot tutorials, people put "id" here. But I don't know what "id" is, and in my situation I don't need any other value, just the "area".
Im not exactly sure what you are asking here but if you are trying to make a dropdown selection for use in an html form will this example help you at all?
<% nations = {'United States of America' => 'USA', 'Canada' => 'Canada', 'Mexico' => 'Mexico', 'United Kingdom'=> 'UK'} %>
<% list = nations.sort %>
<%= f.select :country, list, %>
Here nations is a hash of countries then list becomes the sorted copy of that hash. An html dropdown is then created as a part of the form "f". ":country" is the part of the model that the data is connected to while list is the options to populate the dropdown with
It's not clear from your question what the model is that's being populated with the area.
Typically, collection_select is used between related models.
eg.
class Category < ActiveRecord::Base
has_many :products
end
class Product < ActiveRecord::Base
belongs_to :category
end
When selecting the 'category' for a product, your view would have something like:
<%= f.collection_select(:category_id, :id, Category.all, :name, include_blank: true) %>
What this does is specify the Product.category_id as the attribute being populated with the value of Category.id. The values come from the Category.all collection, and with Category.name being the item displayed in the select. The last (optional) parameter says to include a blank entry.
Something like the following is probably what you need:
<%= f.collection_select(:map_id, :id, #map, :area) %>
However, if the model you're trying to populate has an area attribute (instead of an ID linking to the map), you might need to use:
<%= f.collection_select(:area, :area, #map, :area) %>
This specifies that the area attribute of the receiving table will be populated with Map's area attribute, which is also being used as the "description" in the select.

Resources