Rails Overview: Model

This overview of Rails has become a little more sporadic than I had anticipated but hopefully I’ll be able to get some momentum going again now! In my introductory post I covered the basics of what Rails is and how easy it was to set up a development environment. The Rails programming model is built around the Model-View-Controller architectural pattern and in this post I’ll cover the first of those aspects: the model.

In Rails, the data being represented by the model is almost always that held by a database. Adapters are available for all of popular databases from MySQL (as used for this project) through to DB2 (the latter is being covered in a current developerWorks series). A simple configuration file in the YAML format is used to specify the connectivity properties for the database. (In fact, separate databases can be configured for test, development and production environments, and selected by an environment variable passed to Ruby.)

The Rails library that performs object-relational mapping is known as Active Record and this follows the standard model of tables mapping to classes, rows to objects and columns to object attributes. One of they key tenets of Rails is the idea of convention over configuration and this is nowhere more apparent that in the mapping. Let’s take as an example, the notion of a competitor in my orienteering entries application. Generating a model class is as simple typing ruby script/generate model Competitor. This generates a class that looks like the following:

The convention is that this maps to a table where the camel-case of the class name has been replaced by underscore separators and then pluralized. So, in this case, a table named competitors. The table should have a primary key column named id. Retrieving an instance by primary key is then as simple as competitor = Competitor.find(123). The names of other columns map directly to the attributes of the class e.g. competitor.forename. (Java developers should note that this code is not accessing the attribute directly but rather implicitly via an accessor method. J2EE developers should also note that there is no generated code for these attributes and accessors – it is all done dynamically.) You can also do some funky stuff with the dynamically generated finders e.g. competitor = Competitor.find_by_forename_and_surname('David', 'Currie').

Rails doesn’t attempt to hide the SQL nature of the backed, just remove the need to use it in the simple cases. So, for example, the previous snippet could equally have been written as competitor = Competitor.find(:first, conditions => "forename = 'David' and surname = 'Currie'") or even drop down to raw SQL with find_by_sql. You can also return collections of objects e.g. Competitor.find(:all, conditions => "surname = 'Currie'"). One of the many good things about Active Record is that, if used correctly, it will automatically screen parameters passed to queries against SQL injection attacks.

Other lifecycle operations are equally simple. The code competitor = Competitor.new creates a new object on which I can later call competitor.save to write it to the database, or I can use the create operation to combine the construction and insert. Removing an entry is as simple as calling competitor.delete. All of this and so far we have just generated two lines of code and written none.

One-to-one and one-to-many associations are handled by columns named after the class to which the object is associated prefixed by _id. So, for example, the competitors table might have a column named entry_id which would contain the primary key of a row in the entries table (and, yes, Ruby does know that entries is the plural of entry). For many-to-many associations, an additional table exists with the two table names joined by an underscore. Unfortunately it isn’t possible for Rails to automatically divine these relationships which is where the has_one, has_many, belongs_to and has_and_belongs_to_many declarations come in. For the example given above, the Entry and Competitor classes would need to be modified as follows:

This would then enable me to call competitor.entry and entry.competitors to navigate the relationship and methods such as entry.competitors < < competitor and entry.competitors.delete(competitor) to modify the relationships.

Another area where Active Record comes to the rescue is validation. Validation is performed before a create or save and there are a raft of helper methods as demonstrated by the following snippet:

Alternatively, you can implement your own validate, validate_on_create or validate_on_update methods if you need to get really creative. Also, remember that these classes are just standard Ruby classes so you can add your own methods to perform whatever other processing is appropriate to the model object.

I’ve only just scratched the surface of the capabilities of Active Record here but this isn’t meant to be an exhaustive tutorial, just give you a flavour of what makes Rails so popular. For example, in more complex scenarios you can rely on configuration if convention doesn’t work for you, you can modify the accessors to change the way data is read from and written to the database, and you can create classes to automate the creation and migration between different table structures.

I’ll finish with one frustration that I had with the way relationships are handled. Take the case where I want to find all of the the competitors named Currie for a particular event. I’d like to be able to do something along the lines of event.competitors(conditions => "surname='Currie'") whereas, unless I’m missing something, what you have to do is Competitor.find(:all, conditions => ["event_id = ? and surname = 'Currie'", event.id]). This becomes particularly tedious when trying to navigate your way through a complex set of relationships.

3 Responses to “Rails Overview: Model”

  1. Hi David,

    event.competitors.find(:all, :conditions => “surname = ‘Currie'”) should do the trick.
    You were almost there!

    Cheers,
    Alastair.

  2. Dave says:

    Superb – thanks Alastair. Looks like I was missing something! And as with most things, obvious in hindsight.

  3. […] covered the Model and Controller aspects of Rails in previous posts, that really means it has to be View next. […]