Ruby for Rails Beginners
I've been doing this exercise for a few Rails beginners (or non-rubyists who glanced at Rails a bit before) and the general feedback is that they learn Rails but not Ruby, and this is new to them. So I suppose I should just write it down to save future effort.
If you already know Ruby you'd want to stop reading here.
"Computer, book me on the cheapest flight to Mexico for tomorrow!"
The unfortunate thing is, most folks' first contact with Rails will be some short code snippets like
class Comment < ActiveRecord::Base
belongs_to :user
end
class User < ActiveRecord::Base
has_many :comments
validates_uniqueness_of :username, :case_sensitive => false
end
Very succinct. Looks like english and could even appear friendly to non-programmers. It doesn't look "real" and has "toy" written all over it. The jaded programmer will see "config file", "no real syntax", "fragile", "abitrary subset", "haml" (zing!) or "not powerful" as if a guy in grey suit just demoed how he told his computer what to do verbally - "How sustainable can such fake syntax be?". And perhaps beginners might go, "Rails lets me write english-ish code! Wow wee!"
Let's start somewhere else
In mainstream OO languages like Java, you can't really write Java code anywhere you like. "Huh?" Yes, you just don't usually think about it this way. For example you can't simply insert Java code anywhere, say…
System.out.println("here?");
public class Hello {
public static void main(String [] argv) {
System.out.println("world!");
}
System.out.println("or here!");
}
It's not allowed and you'd get errors
$ javac Hello.java
Hello.java:1: class, interface, or enum expected
System.out.println("here?");
^
Hello.java:6: <identifier> expected
System.out.println("or here!");
^
Hello.java:6: illegal start of type
System.out.println("or here!");
^
3 errors
In Ruby, however, you can write Ruby in weird places:
puts("here?")
class Hello
def self.main(argv)
puts("world!")
end
puts("or here!")
end
Hello.main(ARGV)
Which runs like this instead
$ ruby hello.rb
here?
or here!
world!
So? Big deal
Taking another step back, let's look at this Ruby class
class Hello
def an_instance_method()
puts("This is inside an_instance_method")
end
def self.a_class_method()
puts("This is inside a_class_method")
end
end
If you're a programmer you'd have ascertained def
defines a method (or function). Now, the difference between def an_instance_method()
and def self.a_class_method()
is that a_class_method
is a class method (or Java programmers like to say "static method") and is used like this
Hello.a_class_method()
which prints This is inside a_class_method
whereas an_instance_method
is an instance method that you can call on instances of the Hello
class,
x = Hello.new()
x.an_instance_method()
So? Big deal
Say we define our Ruby class like this, with a puts
statement at the bottom
class Hello
def an_instance_method()
puts("This is inside an_instance_method")
end
def self.a_class_method()
puts("This is inside a_class_method")
end
puts(self)
end
Running it would produce
$ ruby hello.rb
Hello
Notice puts(self)
has printed the name of our class Hello
. This means we are referring to the Class which we're still in process of defining! And since we can refer to it, we can also use it (as much of it as we've defined so far)
class Hello
x = self.new()
puts(x)
def an_instance_method()
puts("This is inside an_instance_method")
end
x.an_instance_method()
def self.a_class_method()
puts("This is inside a_class_method")
end
self.a_class_method()
end
Running it would produce
$ ruby hello.rb
#<Hello:0x0000010084f778>
This is inside an_instance_method
This is inside a_class_method
Notice how instance x
obtains an instance method after the fact! Let's clean up our class: rename it as User
, and rename the class method to validates_uniqueness_of
, and add some arguments to the class method…
class User
def self.validates_uniqueness_of(what, options)
puts("This is inside validates_uniqueness_of #{what} and #{options}")
end
self.validates_uniqueness_of('username', Hash['case_sensitive',false])
end
The #{blah}
syntax is string interpolation, allowing Ruby code to run within a string, like "Five plus One is equal to #{5 + 1}"
. So, running this file would produce
$ ruby hello.rb
This is inside validates_uniqueness_of username and {"case_sensitive"=>false}
In Ruby, Hash objects (or associative arrays) can be defined literally as {'case_sensitive'=>false}
; (brackets) and {curly braces} are largely optional; self
is implied; You can also use :symbols
to denote things you'd usually use enums or constants for
class User
def self.validates_uniqueness_of(what, options)
puts "This is inside validates_uniqueness_of #{what} and #{options}"
end
validates_uniqueness_of :username, :case_sensitive => false
end
We can use the <
syntax to denote inheritance and define the class method elsewhere
class Base
def self.validates_uniqueness_of(what, options)
puts "This is inside validates_uniqueness_of #{what} and #{options}"
end
end
class User < Base
validates_uniqueness_of :username, :case_sensitive => false
end
We could stash more methods into our Base
class
class Base
def self.has_many(what)
# some code
end
def self.validates_uniqueness_of(what, options)
# some code
end
end
Or we could use Mixins
to organize them neatly into standalone modules and compose them together
module Relation
def has_many(what)
# some code
end
end
module Validation
def validates_uniqueness_of(what, options)
# some code
end
end
class Base
extend Relation
extend Validation
end
Either way, we can now have something that looks familiar
class User < Base
has_many :comments
validates_uniqueness_of :username, :case_sensitive => false
end
So you see, the toy looking code is not Rails-specific, nor is it some limited-capacity syntax for PPT & luring beginners.
Came for Rails? Stay for Ruby
And that's it. If this has piqued your interest, you might want to investigate how Ruby lets Rails get away with the syntax of config/routes.rb
and how Ruby makes has_many
and validates_uniqueness_of
possible to implement.