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

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

Related

Create dropdown menu signup page with devise

I would like to create a dropdown menu for a list of countries in my signup page with devise. I understand that I need to create a migration
rails g migration add_countries_to_user country:string
and then I have to use create the form in my view page
<%= f.select :countries, options_for_select(%w[Alfganistan, Albania, Algeria...]) %>
I would like to know if my form correct and where can I put the countries list in because it is not right to write 200+ countries in the view page right?
Thanks.
As suggested, you can use country_select. Or, you can do it on your own as:
Create an initializer which contains list of countries (or anything in particular you want) config/initializers/countries.yml
countries:
- Afghanistan
- United States
- ...
Load it in database by creating a rake task as:
lib/tasks/load_countries.rb
namespace :db do
desc "Loads countries in database"
task :load_countries => :environment do |t|
countries_list = YAML.load("#{Rails.root}/config/initializers/countries.yml")['countries']
countries.each do |country|
Country.find_or_create_by_name(country)
end
end
end
Whenever you add any countries in yml, you can populate it by invoking this rake task: rake db:load_countries.
Maintain a model Country :
class Country < ActiveRecord::Base
validates :name, presence: true, uniqueness: { case_insensitive: true }
end
I am considering that a user belongs_to 1 country above, and a country has_many users. In your view, :
f.select :country, options_from_collection_for_select(Country.all, :id, :name)
Note: I am using association approach above, since it will make it easier to make queries against this field in future, unlike saving an actual string in user.
Use the country_select gem.
# Gemfile
gem 'country_select'
form:
country_select("user", "country")
Apart from gem and Countries read from YML.
One more option is creating a method in your helper
File : app/helpers/country_helper.rb
def get_countries
{:1=>Africa,:2=>"America"}
end
In Views you can use this way
<%= options_from_collection_for_select(get_countries, :id, :name) %>
Look up rails cast #88 revised dynamic select menus. What you need is method call grouped_collection_select in which you will map out the item you need based on how they corresponded to one another
You could do this as a helper method. eg, in your users_helper.rb you could list the selections:
def country_options
[
['Afghanistan'],
['Albania'],
...
['Zimbabwe']
]
end
Then, your selector pulls from that helper method:
<%= f.select :country, options_for_select(country_options), { prompt: 'Choose Country' } %>

Can collection_select be used against yaml files?

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: 'Менеджер'

Rails Drop Down Menu based on new Model

I've been trying to work through this for a few days and can't get anything to work. I have been building my first app based on Michael Hartl's amazing tutorial: http://ruby.railstutorial.org/. Additionally, I have tried this tutorial, but the differences in my code and his prove to be too great for me to follow along.
Where my app differs from Michael Hurtl's is that I am trying to create a site where you can post your left over cans of paint (instead of microposts, AKA twitter). When I created the app, I had a column in the Paints model called "color_family". Now I am looking to change it from a text field to a drop down with predetermined values, e.g. "Reds", Oranges", "Yellows", Greens" etc.
I started out by generating a new scaffold:
rails generate scaffold Color_Family family:string
then I generated a migration:
rails generate migration AddColor_FamilyToPaints family_id:int
and migrated it all.
Then I created the associations
class ColorFamily < ActiveRecord::Base
has_many :paints
end
and
class Paint < ActiveRecord::Base
attr_accessible :family_id, :name, :hex, :location, :quantity, :additional_info
belongs_to :user
belongs_to :color_family
...
end
This is where I get lost, and any tutorial I try to follow breaks everything. Where do I define my predetermined list of color_families?
Is it even worth it for me to go through the creation of a new model? I previously tried this in the form field:
<%= form_for(#paint) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<div class="field">
<%= f.label :color_family %>
<%= select_tag(:color_family, options_for_select([['Red', 1],
['Orange', 2],
['Yellow', 3],
['Green', 4],
['Blue', 5],
['Purple', 6],
['Black', 7],
['Grey', 8],
['White', 9],
['Cream', 10],
['Brown', 12]])) %>
and while it created a dropdown for me, it never captured the info when I added a new paint.
Any help is greatly appreciated. Also, a link to a tutorial would probably do me the biggest help as I've very new to RoR and backend stuff in general.
I'm not sure if you are doing the reading version of the book, or the video. Personally, I recommend both! Absolutely amazing tutorial! One of the first things he does mention though, "Scaffold is not really for the real world" and you should consider this. When I'm doing projects, new old or just refactoring, I usually add everything by hand with the script/generate. The only "scaffold" I've ever used was the scaffold_controller because I was too lazy to do the controller by hand.
The short answer, you should have another model "Color" and the form should:
f.collection_select(:color_id, Color.find(:all), :id, :name, {:include_blank => 'Please Select A Color'})
And the ColorFamily should probably be a has_many_and_belongs_to_many Colors
If you could give me a run down of details associations supposed to be taking place, I can write up a small data modal for you.
Edit #1
You are needing a has_one :through relationship. The general concept will be...
Pivot tabel:
rails g migration ColorFamilyPaints paint_id:integer color_family_id:integer
Paint Class:
class Paint < ActiveRecord::Base
attr_accessible :family_id, :name, :hex, :location, :quantity, :additional_info,
:color_families_attributes # Need to add this in order for you to be able to post with drop down
belongs_to :user
...
# Creates the Relationship
has_one :color_families, :through => :color_family_paints
# Allows color families to be nested in the form.
accepts_nested_attributes_for :color_families, :allow_destroy => true
end
You'll notice a few changes. Addition to the attr_accessible, and the accepts_nested_attributes_for (you may need this, not sure with a has_one though). When you build the form, look at the ID/Name of the select box. If it ends in _attributes, use the accepts line. If not, you don't need it. Alter the :color_families_attributes to match the name of the select box.
Form HTML:
<%= form_for(#paint) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<div class="field">
<%= f.label :color_family %>
<%= f.collection_select(:color_family, ColorFamily.find(:all), :id, :name, {:include_blank => 'Please Select Color Family'}) %>
</div>
<% end %>
More information on associations # RoR website.

rails form - checkboxes, FK and datetime -> how to store those attributes in the db?

(rails 2.2.2)
I have 2 models, user and subscription. Each user can have one ore more subscriptions (= premium services). Below the attributes:
user: id, username, ...
subscription: id, user_id (FK), type, started_at, ended_at
The classes:
class User < ActiveRecord::Base
..
has_many :subscriptions, :dependent => :destroy
..
end
class Subscription < ActiveRecord::Base
belongs_to :user, :foreign_key => :user_id
end
Now I want to make the UI part where existing users can subscribe in their account for the premium services. Therefore I wanted to make a first simple version where the user can subscribe by clicking on a checkbox. This is what I get so far
<div class = 'wrapper'>
<%= render :partial => "my_account_leftbar" %>
<% form_for #subscription, :url => subscribe_user_path(current_user) do |f| %>
<div class="field">
<%= (f.check_box :type?) %> <!-- add '?'after the symbol, source: https://github.com/justinfrench/formtastic/issues/269 -->
</div>
<div class="actions">
<%= f.submit "Subscribe", :class => "button mr8" %>
</div>
<% end %>
</div>
Problems:
the app inserts a record into the db, but the attribute I defined in the form (type) has not been set (it should set '1' which stands for 'standard subscription') . How to get the app set this attribute?
how to set the FK? I assume that rails should set the FK automatically, is that assumption correct?
how to set the other values 'started_at' and 'ended_at? Those are datetime(timestamp) values...
Just run out of my beginner rails knowledge, any help really appreciated...
'Type' is a ruby on rails reserved word which should only be used when you are using Single Table Inheritance. You should rename your column name to something else.
I could solve the other questions 2 and 3 as well, wrapping it up:
insert the record: as stated in the answer from Wahaj, renaming the column 'type' into e.g. 'subscription_type' helped. I created a seperate migration as described here: How can I rename a database column in a Ruby on Rails migration?
storing the FK: updated the action in the controller. Instead of just writing
#subscription = Subscription.new(params[:subscription])
I wrote the following method to create a 'user's subscription'
#subscription = current_user.subscriptions.build(params[:subscription])
storing the 'started_at': added a method to the controller:
#subscription.update_attributes(:started_at => Time.zone.now)

Rails form not saving 'Type:' field

I generated a simple Post scaffold which has title:string body:text category:string. I later added type:string (and performed the migration) to the model and added the selection fields in new.html.erb and edit.html.erb. I also added validation for all these fields.
<%= f.label :type %>
<%= f.select :type, Post::TYPES, :prompt => "Select post type" %>
When I try and create a post it gives me:
"There were problems with the following fields:
Type can't be blank
Type is not included in the list"
Even though I DO make a selection. Am I missing something obvious here?
Select code from Post class:
TYPES = [
["Job", "job"],
["Volunteer", "vol"]
]
validates_presence_of :title, :body, :category, :type
validates_inclusion_of :category, :in => CATEGORIES.map {|disp, value| value}
validates_inclusion_of :type, :in => TYPES.map {|disp, value| value}
The type field is a reserved field used for single table inheritance(STI). You have to rename the field.
Refer to this article for more details
Edit: Changed the link to point to the article provide by Matchu.
If you really want to, you can use field called type in Rails 4 by setting inheritance_column to something else:
class Product < ActiveRecord::Base
self.inheritance_column = :ruby_type
end
In Rails 3 and below, use method set_inheritance_column instead.

Resources