Thursday, February 21, 2013

Experimenting Rails Validation Helpers

(This experiment is performed using Ruby version 1.9.3 and Rails version 3.0.9)

This post is to experiment the Rails validation helpers. Rails validation helpers are just shortcut of some very common validations. For example, a username attribute of the User class must be no longer than 16 characters. How to use these validation helper is very straightforward: just pass in the attribute on which you want to validate and set up the configuration options. Here is how I experiment them. I create a User class, which has attributes username, firstname, lastname, age, birthday, language, balance, password and two instance variables terms and password_confirmation. The required validation on these attributes and instance variables are upon each save
- term must be true
- password and password_confirmation must be the same
- firstname and lastname must not contain spaces
- age must be greater than 17
- birthday format must be in the form "xx/xx/xx"
- language must be either "English" or "Chinese"
- username must be no longer than 16 characters
- balance must be greater than 0.0
- username must be unique

To achieve the validations above, I add the following validation helpers in my User class.
class User < ActiveRecord::Base
  validates_acceptance_of :terms, 
                          :message => 'Please accept the terms to proceed'  validates_confirmation_of :password, :password_confirmation
  validates_each :firstname, :lastname do |model, attr, value|
    if value.include?(' ')
      model.errors.add(attr, "User #{model.id} has a #{attr} which contains spaces")    end
  end
  validates_exclusion_of :age, 
                         :in => 1..17, 
                         :message => 'All users must be 18 year old or higher'  validates_format_of :birthday, 
                      :with => /^[0-9]{2}\/[0-9]{2}\/[0-9]{2}$/
  validates_inclusion_of :language,
                         :in => ['English', 'Chinese'], 
                         :message => 'should be either English or Chinese'  validates_length_of :username, 
                      :maximum => 16, 
                      :message => 'username too long'
  validates_numericality_of :balance, 
                            :greater_than => 0.0
  validates_presence_of :term, :password_confirmation
  validates_uniqueness_of :username
end

All these validations will be invoked when you call valid? or save! on any object of the User class. The error messages will be saved to a hash structure called errors of that object.

Note that validates_acceptance_of and validates_confirmation_of can be performed only if terms and password_confirmation are not nil, and by default only on save. So make sure terms and password_confirmation are not nil using validates_presence_of.

Another thing that is very important is validates_uniqueness_of doesn't really guarantee the unique value of a column. Use database-level constraints instead.

Friday, February 15, 2013

Experimenting Rails Callback Class

(This experiment is performed using Ruby version 1.9.3 and Rails version 3.0.9)

An ActiveRecord model object experiences different states during its life cycle. Typically, it starts with creation, experiences several modification, saved to database and finally gets destroyed. We can write callbacks (ruby methods) that will be triggered upon each state change of model objects.

Callbacks are just ruby methods. Then what's callback class? A Callback class is just a group of methods where you can put your callback handlers and thus they can be shared among different models.

Let's say we have an Dog class, which has an instance variable called favorite_food. The table dogs has an attribute called name. What we want is whenever an Dog object is initialized, values will be assigned to favorite_food and name.

class Dog
  attr_accessor :favorite_food
end

We need to create a callback class. Let's call it SharedCallbacks. And we need to define a method called after_initialize, which is the same name of the event we want it to handle.

class SharedCallbacks
  def after_initialize(model)
    model.favorite_food = 'meat'
    model.name = 'This is a dog'
  end
end

The parameter "model" is the object on which the callback method will have effect on, which in this case is any Dog object.

Back to Dog class, now what we need to do is to create a SharedCallbacks object and hook it with the after_initialize declaration.

class Dog
  attr_accessor :favorite_food
  after_initialize SharedCallbacks.new
end

So whenever a Dog object is initialized, an new SharedCallbacks object is created and the corresponding callback method, which is after_initialize in this case, is called using the Dog object as the single parameter. As a result, the Dog object's favorite_food and name are set to the desired values.

Now let's add another requirement, whenever a record is found in database, its name will be set to  'This is a cat'. What we need to do is define a after_find method in SharedCallbacks class and hook one of its object with the after_find callback declaration in Dog class.

class SharedCallbacks
  def after_initialize(model)
    model.favorite_food = 'meat'
    model.name = 'This is a dog'
  end

  def after_find(model)
    model.name = 'This is a cat'
  end
end

class Dog
  attr_accessor :favorite_food
  after_initialize SharedCallbacks.new
  after_find SharedCallbacks.new
end

If we retrieve a record from database and look into it, we're gonna find that its name is set to 'This is a dog', NOT 'This is a cat'. That's because the after_initialize callback is always invoked after after_find. In the book Agile Web Development with Rails, there is a complete order in which callbacks are invoked, if they are declared of course.

Monday, February 11, 2013

Experimenting autosave

(This experiment is performed using Ruby version 1.9.3 and Rails version 3.0.9)

When we define a one-to-one or one-to-many relationship in Rails, very likely we're gonna want the child object(s) to be saved to database when parent object is saved. For example, we have an Order class, which has a one-to-many relationship to an OrderLineItem class. After we initialize an order object and associate it with several order line item objects, we're gonna want to save the order object. Whether the related line item objects will be saved depends on how you specify the autosave option of the has_many relationship (it also exists in has_one and belongs_to relationships). Below is how Ruby on Rails documentation say about autosave.

:autosave
If true, always save the associated objects or destroy them if marked for destruction, when saving the parent object. If false, never save or destroy the associated objects. By default, only save associated objects that are new records.

Clear enough, right? If :autosave is defined to be false, no change will be applied to the child database when the parent object is saved, even though there are a bunch of child objects associated to the parent object. If defined to be true, all the changes will be applied to the child database when the parent object is saved, no matter these child objects are new or initialized from existing records in database. Finally if by default, only those new child objects will be saved to database when parent object is saved.

You can experiment it by using the following example code. Assume we have a order record in orders table and a order line item record in order_line_items table associated to that order record, and assume quantity, one of the attributes, of that order line item is 10.

Let's type in the following commands in console when autosave is set to be true and default respectively.

o = Order.first 
order.line_items.first.quantity = 5
o.save!


If autosave is set to true. Go to the database after these commands, you will find the quantity is updated to 5.
If autosave is set to default. Go to the database after these commands, you will find the quantity is still 10.


Let's type in a different set of commands in console when autosave is set to be default and false respectively.

o = Order.first
order.line_items << new_line_item # assume we have created a new line item object called new_line_item
o.save!


If autosave is set to default. Go to the database after these commands, you will find the new_line_item is saved to db.
If autosave is set to false. Go to the database after these commands, you will find the new_line_item is NOT saved to db.

Thursday, February 7, 2013

Ruby's Single Quote And Double Quote

As a Rails tester, I've been using Ruby's single quote and double quote exchangeably until I run into the following problem. After writing so many tests, I'm thinking about metaprogramming the testing as certain tests are the same for all the models. I wrote a test template file that will be loaded for each new test file I'm going to write. This template file is basically pure text, but I want it to be a little bit more dynamic. For example, I want it to generate today's date. So I write something like the following in the template file:

# Author
#    David on #{Date.today.strftime("%m/%d/%y")}
=>
# Author
#    David on #{Date.today.strftime("%m/%d/%y")} 


However, when I read this template file into my test file, these strings appear as exactly the same as they appear in the template, as oppose to showing today date. So I searched on the Internet and find that Ruby treats single quotes and double quotes really differently: the double quotes interpret the interpolation and the escaped characters within the string, while single quotes do not do these two things. So performance-wise, using double quote is gonna take longer if interpolation or escaped character is involved.

Now back to my problem, the reason that the date is not shown correctly is that the content of the template is read as being wrapped with single quotes. So how can we tell Ruby that we want the content as to be wrapped with double quotes? We can use the double quote mark "%Q{}". Everything within the delimiter will appear as if they are wrapped with double quotes (see below).

%Q(# Author
#    David on #{Date.today.strftime("%m/%d/%y")})
=>
# Author
#    David on 02/06/2013

Wednesday, February 6, 2013

Rails Polymorphic Association

Updated on March 26, 2014
Updated on Sep 21, 2014

Let's create a scenario where polymorphic association can be used. Assume an online store (like Amazon) keeps the data of different type of items in several different tables, such as books, laptops and cell_phones.These tables have different attributes. There is another table called orders to keep track of all the items that's been ordered by customers.

This orders table has a one-to-one relationship to all the other three tables, which means it must have three different foreign keys pointing to these three tables. This is a bad design because for each order record there are two columns that are not used. You might say why not create three different tables called order_books, order_laptops and order_cell_phones. But you will find these are three identical table serving the same functionality. So here is where polymorphic association comes in.

To use polymorphic association, you need to add another attribute called "type" (rails will automatically do it for you when you define a polymorphic association) to determine which table an order record is associated to, and thus only a single foreign key is needed instead of three. Below is how polymorphic association is defined using our example.



Instead of using a specific table, model Order use a universal name "item" to refer to the other three tables. If you look into the database, you will find an attribute called "type" in table orders. Using the "type" and the foreign key, the orders table is able to uniquely point to an item, whether it's a book, a laptop or a cell phone.