Thursday, May 23, 2013

Experimenting Rails Notification Module

Rails ActiveSupport::Notification module offers a mechanism to launch certain behavior when certain events occur. A common instance is to send an email to user after they finished signing up. Here we will do something much easier to show you how Notification module works. The basic idea is we'll simulate a man (which we will call it mess maker) pouring water into a pitcher. Each time he will pour certain amount of water into the pitcher, and when the pitcher can no longer hold any more water, a warning is fired telling him that the water is overflowing.

So first of all, we need to create a Pitcher class and a MessMaker class.
class Pitcher
  attr_accessor :content

  VOLUME = 100

  def initialize
   @content = 0
  end

  def check_overflow(name)
   ActiveSupport::Notifications.instrument("overflow", :name => name) if content > VOLUME  end
end

class MessMaker
  attr_accessor :name

  def pour_water(pitcher, amount)
   pitcher.content += amount
   pitcher.check_overflow(name)
  end

  ActiveSupport::Notifications.subscribe("overflow") do |name, start, finsih, id, payload|     puts 'Hey, ' + payload[:name].to_s + '. Stop pouring water!'
  end
end

As we can see, each Pitcher instance has a instance variable called content which is initialized to be 0 and recalculated as the mess maker pours more water into it. When the content reaches the VOLUME, an event called "overflow" is instrumented.  Right after "overflow" event is instrumented, the block in the Notification.subscribe gets executed, asking the mess maker to stop pouring more water. Note that subscribing code is not necessarily under the MessMaker class, you can put it wherever that makes sense as long as that chunk of code is loaded when you launch the application.

Now let's do some experiments in rails console
1.9.3-p392 :001 > mm = MessMaker.new
 => # 
1.9.3-p392 :002 > mm.name = 'david'
 => "david" 
1.9.3-p392 :003 > p = Pitcher.new
 => # 
1.9.3-p392 :004 > mm.pour_water(p, 30)
 => nil 
1.9.3-p392 :005 > p.content
 => 30 
1.9.3-p392 :006 > mm.pour_water(p, 30)
 => nil 
1.9.3-p392 :007 > p.content
 => 60 
1.9.3-p392 :008 > mm.pour_water(p, 30)
 => nil 
1.9.3-p392 :009 > mm.pour_water(p, 30)
Hey, david. Stop pouring water!
 => nil 




Sunday, May 19, 2013

Rails Single Table Inheritance

Updated at Sep 21, 2014
Rails Version 4.1.1

Why single table inheritance?
In Rails,we usually have one database table for each of our model. That's very straightforward. But how do we deal with classes (or models) that inherit from other classes. Do we need to create database tables for each of them. The answer is it depends. If all the subclasses are very similar to each other or even identical (attribute wise), then creating individual tables for each of them might not be a good idea. Luckily, we have single table inheritance in Rails. Implied by its name, single table inheritance has only one table for the base class and all the subclasses. This table must meet the following requirement:

  • Contains the attributes of all the classes involved in single table inheritance
  • Contain a column called "type" to identify the class of each record in it
  • Allow null for each of the attribute that do not appear in all classes
What's the benefit of it?
The benefit of single table inheritance is very obvious: you got only one table. You give up data purity to gain more performance. Model wise, however, you still get the flexibility: each class can have their unique behaviors (methods).

In the following I'm going to show you a simple example of how to use single table inheritance. Let's say we have a base class Student, which is the super class of the class TA (teaching assistant) and class RA (research assistant). First of all, we need to find out all the attributes these three classes have. The base class Student has student_id, firstname, lastname, department, major. TA has an extra attribute called course_number, indicating the course TA assists in teaching. RA has an extra attribute called topic, indicating the topic s/he is researching on. As we've mentioned, all the six attributes have to appear in the table student, as well as the type attribute.

Create the migrate for Student model
class CreateStudent < ActiveRecord::Migration
  def change
    create_table :students do |t|
      t.integer :student_id, :null => false
      t.string :firstname, :null => false
      t.string :lastname, :null => false
      t.integer :department_id, :null => false
      t.string :major, :null => false
      t.string :type, :null => false
      t.integer :course_number
      t.string :topic
    end
  end
end

Now let's create the hierarchy of our single table inheritance

student.rb
class Student
end
ta.rb
class Ta < Student
  def grade 
    puts 'I can grade'
  end
end
ra.rb
class Ra < Student
  def research 
    puts 'I can do research'
  end
end

Now let's launch the console and do some experiments.
david = Ra.create(:student_id => 1, :firstname=>'david', :lastname=>'zheng', :major=>'cs', :department_id=>1, :topic=> 'Database')
mike = Ta.create(:student_id => '002', :firstname=>'mike', :lastname=>'lu', :major=>'cs', :department_id=>1, :course_number =>'CS511')
All the records we created using subclass RA and TA will go into the table students.
david.research
I can do research
mike.grade
I can grade
Objects of subclasses can only call their own methods.
dave = Student.find_by_first_name('david')
dave.class.name
=> "RA"
The object uses the "type" column to determine which class it belongs to.