If you are a complete beginner to Rails, you will surely have been fascinated by the beauty of Rails that how it creates relations for you, how it creates a table in the database by just writing a migration for it, and similarly other things that do a lot for you but take only a few lines of code. Well, fascination does come at a price, and you will notice that there is code that doesn’t make any sense for a beginner. So, in this article, we shall try to break down that code, and explain it with an understanding of Ruby.

First, how does code for validation work in Ruby:

class User < ActiveRecord::Base 
  validates :email, presence: true
end

So you may be wondering how does it work. Well, in Ruby as well as in Rails, there is a keyword that always exists no matter where you are in your code, and that is: self . So if you are just in a class, and you write puts self , it is gonna print the value/name of the class. But as you get into an instance method, self immediately starts reflecting the current instance, so if you do puts self , it is gonna print the value of the instance.

class User
  puts "The value of self: #{self}"
end

If you load this class by requiring it in IRB console, you will see the following output.

The value of self: User

But if you print the value of self in an instance method, you will get the following output:

class User
def full_name
  puts "The value of self: #{self}" end
end
User.new.full_name
The value of self: #<User:0x007fdcb2a14f50>

So here is the summary: If you call a method inside the class definition and do not use any prefix
with it, it will be called on the self keyword.

So that makes writing validates :email, presence: true equivalent to self.validates :email, presence: true , and sure enough, you can explicitly write the keyword self , and your code will work with no errors:

class User < ActiveRecord::Base 
  self.validates :email, presence: true
end

Now, this makes sense. validates is simply a class method that we invoke, and pass different arguments in it.
Moving on to how does the code for migration work in Rails generically, let’s have a look at it:

class CreateUsers < ActiveRecord::Migration 
  def change
    create_table :users do |t| 
      t.string :first_name 
      t.string :last_name 
      t.integer :age
    end 
  end
end

Now, since create_table is being written in change that is an instance method, so we can surely write self.create_table instead of create_table , but since self refers to the current instance, so leaving off the keyword self makes more sense.

Following is how create_table has been defined in Rails:

def create_table(table_name, options = {})
  td = create_table_definition table_name, options[:temporary], options[:op
tions], options[:as]
  if options[:id] != false && !options[:as] 
    pk = options.fetch(:primary_key) do
      Base.get_primary_key table_name.to_s.singularize 
    end
    td.primary_key pk, options.fetch(:id, :primary_key), options 
  end
  
  yield td if block_given?
  
  if options[:force] && table_exists?(table_name) 
    drop_table(table_name, options)
  end

  result = execute schema_creation.accept td
  td.indexes.each_pair { |c, o| add_index(table_name, c, o) } unless suppor ts_indexes_in_create?
  result
end

You can also notice this thing that the first argument that it takes is table_name , and then you can pass different options like id: false that creates a table without id.

yield td if block_given? written in the definition of create_table tells us that it takes a block, and sure enough, it does.

So now, you can understand that how writing a line has_many :books works in Rails. Having this kind of core knowledge of Ruby surely helps you be more confident in writing and debugging Rails code. If you would like to have further info on how does ActiveRecord do all the magic, you can head over to the book: Metaprogramming Ruby 2: Program Like the Ruby Pros.

If there is something else you would like us to explore, let us know in comments section below, and never forget to have fun while learning!

Leave a Reply

Your email address will not be published. Required fields are marked *