One thing that I quickly noticed about Ruby on Rails is that you can do a lot more with less code. This is one of the benefits of working in a dynamic language like Ruby that does not have as many restrictions as a static typed language. The end result is that you have smaller methods, more descriptive code, fewer chances to create bugs, and it becomes easier to get things done. Let me show you what I mean.
Let’s say that I have a database table that looks like this:
create table Users ( id int, name varchar(100), user_status_id int, created_at datetime, updated_at datetime )
My Ruby class looks like this:
class User < ActiveRecord::Base belongs_to :user_status has_many :roles, :through => :user_roles validates_length_of :name, :maximum => 100 named_scope :active, :conditions => ["user_status.id = ?", UserStatus::ACTIVE] end
My class only has 6 lines, but it does an awful lot. Let’s go through it line by line:
class User < ActiveRecord::Base
Because I have a table in the database called "Users", I now have the following methods on my User class:
- id, id=, name, name=, user_status_id, user_status_id=, created_at, created_at=, updated_at, updated_at=
- find, find_by_id, find_by_name, find_by_user_status_id, find_by_name_and_user_status_id, find_by_id_and_name_and_user_status_id, and any other combination of columns I might want to query on
Because I derive from ActiveRecord::Base, I inherit a lot of data access methods like save, delete, destroy, and many more.
All I've done so far is made my class declaration! Look at everything I'm getting for free.
My User object has a many-to-one relationship to the User_Status table (and it's corresponding UserStatus object). I now have the "user_status" and "user_status=" methods on my object. The user status will be lazy loaded, so it won't do any database queries to get the user status until I actually need it.
has_many :roles, :through => :user_roles
My User object has a many-to-many relationship with the roles table, and in the database there is a user_roles table that serves as a mapping table. I now have a "roles" method on my User class that will give me the list of roles, and I can add new roles to the collection without having to worry about creating anything for the mapping table.
validates_length_of :name, :maximum => 100
Name is required and cannot be longer than 100 characters.
named_scope :active, :conditions => ["user_status.id = ?", UserStatus::ACTIVE]
Now I can write "User.active", which will return all active users. This is equivalent to User.find_by_user_status_id(UserStatus::ACTIVE), but now I've abstracted away that concept of an active user so that other classes don't have to know how the internals work.
OK, well, every language has to have some ceremony.
I haven't created any methods yet! Already my object has all of the data access methods that it needs, it will validate the object before it saves, it knows about related objects, and I abstracted away the concept of an active user.
Note that not only have I been able to do all of this with very few lines of code, the code that I did write was very simple and readable. Since the code is so simple, I'm not going to write unit tests for it either (the tests would basically be the same one line of code as the implementation code in most cases).
All of this means that I can do things much easier than before. In .NET, I would've had to create finder methods by hand. I would've had to add all of the properties that match up to my database columns by hand. I don't know how I would duplicate named_scope in a .NET ORM, maybe it's possible somehow. I've never found a way to do it. Maybe I would have to write a stored procedure.
To sum it up, all of the boring crap is done for me and I can pretty much immediately start writing code that provides business value, which is what we're getting paid to do. No ADO.NET, no more mapping my database table to my entity object, no more modifying several files just to create the same kinds of methods that I have to create for every new entity object that I create.
All of this is helping me to achieve what I've always wanted in software development: the ability to write code that says what I want to do and does it without having to do lots of typing.