Thursday, August 29, 2013

Rails select helpers unraveled


I have observed that there is lot of confusions regarding select box or collection helpers in Rails. Rails API documentation is not very clear about which helper to use in which situation. In this blog, I have tried to simplify the select box helper APIs by giving examples and various scenarios. Majorily, there are two helpers which are helpful in creating a select boxes in Rails select & collection_select. Though other helpers in the documentation are still useful but these are adequate to achieve anything.

The syntax for the select is
select(object, method, choices, options = {}, html_options = {})

Let us start with the very basic select box:-
<%=select “post”, “person_id”, [“Person1”, “Person2”, “Person3”]%>

Output:

<select id="post_person_id" name="post[person_id]">
<option value="Person1">Person1</option>
<option value="Person2">Person2</option>
<option value="Person3">Person3</option>
</select>

 
Options for this Helper

 Include the blank option automatically

<%=select “post”, “person_id”, [“Person1”, “Person2”, “Person3”],  {:include_blank => true}%>

Output:

<select id="post_person_id" name="post[person_id]">
<option value=" "> </option>
</select>

 <%=select “post”, “person_id”, [“Person1”, “Person2”, “Person3”],  {:include_blank => “Please select”}%>

Output:

<select id="post_person_id" name="post[person_id]">
<option value=" ">Please select </option>
</select>

 
You can disable the options you want

<%=select “post”, “person_id”, [“Person1”, “Person2”, “Person3”],  {:include_blank => “Please select”, :disabled => [“Person1”,  “Person2”]}%>
 
Override the selected value

<%=select “post”, “person_id”, [“Person1”, “Person2”, “Person3”],  {:include_blank => “Please select”, :selected => “Person1”}%>

Output:

<select id="post_person_id" name="post[person_id]">
<option value="Person1" selected=”selected”>Person1</option>
<option value="Person2">Person2</option>
<option value="Person3">Person3</option>
</select>
 

HTML Options in the helper
Add HTML attributes
<%=select “post”, “person_id”, [“Person1”, “Person2”, “Person3”],  {:include_blank => “Please select”, :selected => “Person1”}, {:class => “html_class” }%>

Output:
<select id="post_person_id" name="post[person_id]" class=”html_class”>
<option value="Person1" selected=”selected”>Person1</option>
<option value="Person2">Person2</option>
<option value="Person3">Person3</option>
</select>


Other methods to specify Choices in the helper
<%=select “post”, “person_id”, {“1” => “Person1”, “2” => “Person2”, “3” => “Person3”}%>
Or
<%=select “post”, “person_id”, [ [“Person1“,  “1”], [“Person2”, “2”],[“Person3”, “3”]]%>

In case you have collection of objects
<%=select “post”, “person_id”,  Person.all.collect{ |p| [p.name, p.id]}%>
The collection_select helper is also very useful if you have collection of objects. For the above select it can written like this. It has a syntax like:-

collection_select(object, method, collection, value_method, text_method, options = {}, html_options = {})
So,
<%=collection_select “post”, “person_id”, Person.all,  :id,  :name%>


Grouped Select Boxes
In case you want to have grouped collection/select box there are two helpers which can help you create it grouped_collection_select and grouped_options_for_select
grouped_options _for_select is used along with select to group related options together.
<%
grouped_options = {“North America” => [[“United States”, “US”], “Canada”], “Europe” => [“Denmark”, “Germany”, “France”]}
%>
<%=select “post”, “person_id”, grouped_options_for_select(grouped_options), {:include_blank => “Please Select”%>

There are more options like :divider, :prompt which you can pass on to the grouped_options_for_select, whose details can be found here http://api.rubyonrails.org
 
Output:
<select id="post_person_id" name="post[person_id]">
<option value="">Please Select</option>
<optgroup label="Europe"><option value="Denmark">Denmark</option>
<option value="Germany">Germany</option>
<option value="France">France</option></optgroup>
<optgroup label="North Americal"><option value="US">United States</option>
<option value="Canada">Canada</option></optgroup>
</select>

grouped_collection_select helper is bit tricky to use and has syntax like this

grouped_collection_select(object, method, collection, group_method, group_label_method, option_key_method, option_value_method, options = {}, html_options = {})

This is what rails documentation has to say

Returns <select><optgroup> and <option> tags for the collection of existing return values of method for object‘s class. The value returned from calling methodon the instance object will be selected. If calling method returns nil, no selection is made without including :prompt or :include_blank in the options hash.

Parameters:

·         object - The instance of the class to be used for the select tag

·         method - The attribute of object corresponding to the select tag

·         collection - An array of objects representing the <optgroup> tags.

·         group_method - The name of a method which, when called on a member of collection, returns an array of child objects representing the <option> tags.

·         group_label_method - The name of a method which, when called on a member of collection, returns a string to be used as the label attribute for its<optgroup> tag.

·         option_key_method - The name of a method which, when called on a child object of a member of collection, returns a value to be used as the valueattribute for its <option> tag.

·         option_value_method - The name of a method which, when called on a child object of a member of collection, returns a value to be used as the contents of its <option> tag.

Example object structure for use with this method:

class Continent < ActiveRecord::Base
  has_many :countries
  # attribs: id, name
end
 
class Country < ActiveRecord::Base
  belongs_to :continent
  # attribs: id, name, continent_id
end
 
class City < ActiveRecord::Base
  belongs_to :country
  # attribs: id, name, country_id
end

Sample usage:

grouped_collection_select(:city, :country_id, @continents, :countries, :name, :id, :name)

Possible output:

<select name="city[country_id]">
  <optgroup label="Africa">
    <option value="1">South Africa</option>
    <option value="3">Somalia</option>
  </optgroup>
  <optgroup label="Europe">
    <option value="7" selected="selected">Denmark</option>
    <option value="2">Ireland</option>
  </optgroup>
</select>

Here, I have presented the form helpers which are not bonded to any model. For forms which are bound to models use them as

<%=form_for @person%>
<%=f.select :person_id ….%>
<%=f.collection_select :person_id ….%>
<%end%>

That is all from my side. Here I have tried to cover every possible scenario to create select boxes using rails helpers. Hope this will help rails developer find the best suited helper for them.

In case you have any questions/suggestions, you can connect to me at developer.vineet@gmail.com

Thursday, October 13, 2011

Ruby Equality And Object Comparison

Ruby has three main equality test methods, ==, eql? and equal?. These methods normally live in the Object class. Inside the Object class all there methods do exactly the same thing, they test if two objects are exactly the same object_id.

Just take this example:-

string1 = "abc"
class MyObject
end
object1 = MyObject.new
object2 = object1
object3 = MyObject.new
 
puts "Object 1 is == to object 2: #{object1 == object2}" #its true
puts "Object 1 is eql? to object 2: #{object1.eql? object2}" #its true
puts "Object 1 is equal? to object 2: #{object1.equal? object2}" #its true
puts "Object 1 is == to object 3: #{object1 == object3}"  #its false
puts "Object 1 is eql? to object 3: #{object1.eql? object3}" #its false
puts "Object 1 is equal? to object 3: #{object1.equal? object3}" #its false
 
Usually with Ruby we will tend to leave the equal? method alone
to always give us the ability to find out if two objects are
exactly the same. The eql? and == methods however are open to
be redefined in any way we like. You can redefine the == method
to give your objects custom behavior when it comes to equality testing.

 
class Shirt
  attr_reader :size
  def initialize size
    @size = size
  end
 
  def ==(another_shirt)
    self.size == another_array.size
  end
end
 
More than that, by defining the == method on your object, 
you also get the != method for free.
 
shirt1 = Shirt.new(10)
shirt2 = Shirt.new(11)
shirt3 = Shirt.new(10)
 
puts "Are shirt1 and shirt2 equal? #{shirt1 == shirt2}" #its false
puts "Are shirt1 and shirt3 equal? #{shirt1 == shirt3}" #its true
puts "Are shirt1 and shirt2 NOT equal? #{shirt1 != shirt2}" #its true
 
 
There is only one case that I see where the eql? method 
might come in handy. If you have to defined the <=> method, 
therefore giving your object an implicit == method, it may no longer 
make sense to redefine == separately. But if for some reason you need to 
keep the <=> equality behavior but still need even more custom 
equality behavior, you can always use the eql? method for this 
purpose. I can't really envisage a situation where you might want to do 
this, but the capability is certainly there. 
 
 


Friday, March 18, 2011

Add MySQL foreign key constraints in Rails migrations.


I have written a small module which let you add MySQL foreign key constraints in Rails migrations. This functionality is missing in Rails 2.*.

migration_helper.rb

module MigrationHelper
  #add foreign key constraint
  #Options can be :override_column => string, :referential_action => string
  #:override_column - Override column name. It is inferred from 'from_table'
  #:referential_action - can be any or concatenation of both strings.
  #'ON UPDATE [RESTRICT | CASCADE | SET NULL | NO ACTION]'
  #'ON DELETE [RESTRICT | CASCADE | SET NULL | NO ACTION]'
  def add_foreign_key(from_table, to_table, options={})
    if options[:override_column].blank?
      column_name = foreignkey_column_name(to_table)
    else
      column_name = options[:override_column]
    end
    
    constraint_name = foreignkey_name(from_table, to_table, column_name) 
    
    query = "alter table #{from_table}
              add constraint #{constraint_name}
              foreign key (#{column_name})
              references #{to_table}(id) "
    query = query + options[:referential_action].to_s unless options[:referential_action].blank?
              
    execute query
  end
  
  #removes foreign key
  #Options: :override_column
  #override_column - Override column name. See above.
  def remove_foreign_key(from_table, to_table, options={})
    if options[:override_column].blank?
      column_name = foreignkey_column_name(to_table)
    else
      column_name = options[:override_column]
    end
    
    constraint_name = foreignkey_name(from_table, to_table, column_name) 

    execute %{alter table #{from_table} drop foreign key #{constraint_name}}
  end
  
  private 
  def foreignkey_column_name(table)
     "#{table.to_s.singularize}_id"
  end

  def foreignkey_name(from_table, to_table, column_name)
    "fk_#{from_table}_#{column_name}_to_#{to_table}"
  end
end



Usage:-
  • Copy migration_helper to lib folder of your rails project.
  • Add the following line of code in rails initializer. If you are using RAILS 2.3.5 or above add this line toinitializers/new_rails_defaults.rb:-
ActiveRecord::Migration.extend(MigrationHelper)
  • Now, in migrations you can add foreign key constraints as:-
add_foreign_key(from_table, to_table, options={})
  • To remove foreign_Key remove_foreign_key(from_table, to_table, options={})
To see the available options refer comments in a file.
Feel free to give your feedback or give me a call for any queries.