ActiveRecord is a Ruby library written by the creators of Ruby on Rails to do database integration. Instead of writing SQL, the developer works with normal Ruby object access which ActiveRecord sees, compiles into SQL, queries for, and parses, ultimately returning your query as a Ruby object.
The line below is equivalent to "SELECT * FROM users;".
The return value is an Array of User instances. With these User instances, more information can be extracted programmatically. For example
jay = User.find_by_name("Jay Phillips")
jay.extension
jay.administrator?
jay.calls.count
Notice how we're searching the database in the example above. The method find_by_name() actually doesn't exist on User. ActiveRecord uses a feature of Ruby which effectively captures all methods that begin with "find_by_" and assumes what follows to be a column name by which the invoker wanted to search.
Assigning the User record returned by our find statement to variable 'jay' loads all the columns into the jay object. Now we can access them via methods that are named for the column names (Active Record does this automagically).
jay.extension => "12345"
jay.password => "blah"
(we're using the => syntax to show what would be returned by these methods. While coding, you would just refer to jay.extension, etc)
Because this "extension" field is probably a VARCHAR field in the database, Ruby converts the response into a String.
The method administrator?() shows additional behavior added to the User class. In this case, the programmer who created User may have an obscure way of determining whether an arbitrary user is an administrator, so she created this administrator?() method which wraps that complexity and simply returns a true or false. Because User is just a class and all records in its table are just User instances, we can add custom methods to the class definition, and access them for any User instance.
The last line of the example above is equally interesting. Because database table usually have intertwined relationships, a User may have many Call objects. Each Call object exists as a record within the "calls" table with a column referencing an id of a record in "users" to which the individual call belongs. ActiveRecord intuitively handles these associations by offering this "calls" method which returns a special Ruby Array-like object which contains all Call objects from the database whose foreign key was found to be jay's primary key. The method count() make the query something like:
SELECT COUNT(*) FROM calls WHERE user_id=5;
Performance note: for accessing other call details, you may want to pre-fetch the calls from the database when you load the User object. This would be done like so:
User.find_by_name("Jay Phillips, :include => :calls )
By abstracting database access into Ruby objects, ActiveRecord is classified as an Object Relational Mapper.
A main benefit of ORM's like Active Record is that they usually support a wide variety of databases, giving the developer the option to use a preferred database. A secondary benefit derives from the popularity of SQL: an ORM can be used to interface with legacy databases, allowing developers to develop with modern tools, while eliminating the time-consuming task of massaging and porting data to a new database.
Note: if you are not familiar with object-oriented programming, please brush up on the basic concepts of objects, classes, and methods. The RubyPrimer is a good place to start, and the links referenced therein.
AR Concepts
Active Record uses what it calls "models" to abstract the data from the relational database into Ruby objects. A model is simply a subclass of the ActiveRecord::Base class. Each of these models is populated with methods from two sources: AR's reading of the database, and the actual model code. WHAT YOU SAY??! Yes, it is true, ActiveRecord actually looks at the database, and allows you to easily access the data based on the column name, as shown in the examples above.
Configuring AR from scratch is pretty simple, so long as one keeps in mind AR's preference for convention over configuration. If you want to create a User model, your table should be named "users". Multi-word model names should be CamelCase in the class definition, and underscored in the database. Here are a few examples:
The third example is of particular interest, since you will observe that AR is handling pluralization properly.
Here is a basic example of an AR model definition:
That's it! So long as your database tables are named correctly, you now have full CReate, Update, Delete (CRUD) capability on the users table, accessed through the User class.
Here's an example of how to create a new User:
aleks = User.new
aleks.extension = "99999"
aleks.name = "Aleks Clark"
aleks.save
The first line creates a new instance of the User class. This does a few things for us, automatically populating fields with defaults, such as the 'id' column, and any other columns you have set with default values in your database. The next two lines assign values to the extension() and name() methods, and the last line saves the new User instance to the database. It is important to note that nothing exists in the database until save() is first called.
We can also edit User information:
aleks = User.find_by_name("Aleks Clark")
aleks.extension = "0"
aleks.save
Again, it is important to note that no db changes occur until save() is called.
AR Relationships
Active Record provides a few methods of interconnecting sets of data, which are referred to as "relationships." These relationships use foreign keys to reference other sets or pieces of data, although one limitation of Active Record is that it is not able to take advantage of the properties of true SQL foreign keys, due to interoperability problems between types of SQL databases.
has_one/many & belongs_to
This is the most basic relationship, and is what would be used in the examples above to map Calls to Users. Here's the model code for both models:
Note the correct pluralization followed in the relationship definitions. The : used before "calls" and "user" denotes those variables as symbols. If you want or need to know more about symbols, see the RubyPrimer.
To correctly configure this relationship, the calls table should have a column named "user_id", set to the integer data type, and both model definitions should state the relationship. We all know it takes two to tango ;) Now, when you use the method 'jay.calls()', AR will search the calls table for all records containing the id of the User instance jay in the user_id column, and return them in an Array.
An association between calls and a user can be done in one of two ways:
jaycall = jay.calls.new()
...
jaycall.save()
or
jaycall = Call.new()
jaycall.user_id = jay.id
...
jaycall.save()
Another relationship is closely tied to has_many/belongs_to, the has_one/belongs_to relationship. This is rarely used, but it is fairly simple. As its name suggests, it is simply a relationship with only one instance on each side, like so:
The reason this relationship is so rarely used is that it usually is not good design. A 1:1 relationship could just as easily be duplicated by adding columns to the users table, although legacy data might require this relationship.
polymorph style
A polymorphic association is one that allows a relationship between one class and many classes, not simply between one instance of a class and many instances of another class. You could use this association to connect an Address to several different classes:
class Address < ActiveRecord::Base
belongs_to :addressable, :polymorphic => true
end
class Company < ActiveRecord::Base
has_many :addresses, :as => :addressable
end
class User < ActiveRecord::Base
has_many :addresses, :as => :addressable
end
In this example, the Address model declares an interface called "addressable", and also sets that interface to be polymorphic. The interface name is arbitrary, but it is good practice to name it something that "makes sense" as much as possible. The Company and User models use this interface via the ":as => :addressable" statement.
To properly configure this relationship, your addresses table should contain two columns, addressable_id, an integer data type, and addressable_type, a varchar. The best way to create an associated address would be like so:
mycompany.addresses.new()
...
mycompany.addresses.save()
aleks.addresses.new()
...
aleks.addresses.save()
has_many :through
has_many :through is a more powerful relationship, allowing many-to-many relationships, which are useful for tagging, among other things. As its name implies, has_many :through uses an intermediate model to bind many-to-many. Here's some sample model code:
class Post < ActiveRecord::Base
has_many :tags, :through => :taggings
end
class Tagging < ActiveRecord::Base
belongs_to :post
belongs_to :tag
end
class Tag < ActiveRecord::Base
has_many :posts, :through => :taggings
end
This model code allows a single Post instance to be associated with many different tags, and a single Tag instance to be associated with many different posts. The sweet part of this is that AR will handle this relationship for you, all you have to do is this:
post = Post.find(:first)
tag = post.tags.new()
tag.title = "cows"
tag.save
And now we have tagged our first post with the cows tag! But what if we want to tag another post with the cows tag?
post = Post.find_by_title("Dairy Farmer Strike Curdles Public Opinion")
tag = Tag.find_by_title("cows")
post.tags << tag
Voila! The << operator is being used on the post.tags Array, and essentially means "tack this tag onto the list of tags" in this case.
polymorph style
Now it's time to get complicated. What if you had several models you wanted to tag? And you wanted to pull up all the models for a certain tag? Polymorphism to the rescue! Consider the following:
class Post < ActiveRecord::Base
has_many :tags, :through => :taggings, :as => :taggable
end
class Video < ActiveRecord::Base
has_many :tags, :through => taggings, :as => :taggable
end
class Tagging < ActiveRecord::Base
belongs_to :taggable, :polymorphic => true
belongs_to :tag
end
class Tag < ActiveRecord::Base
has_many :posts, :through => :taggings
end
Hmm, this is cool! EXCEPT, we've got some limitations...this associated will allow you to access post.tags and video.tags, but sadly, HMT is limited! You can't access tags.taggables! Scary indeed. However, there are two ways around this: has_many_polymorphs and this crazy workaround: http://blog.hasmanythrough.com/2006/4/3/polymorphic-through
has_many_polymorphs
has_many_polymorphs allows all kinds of crazy stuff, and is a gem. Please see this page: http://blog.evanweaver.com/files/doc/fauna/has_many_polymorphs/files/README.html
HMP is a fairly complex piece of work, so their exhaustive documentation is your best reference.
has_and_belongs_to_many
This is an old, crippled version of has_many :through, except instead of an actual join model that can have additional data associated with it, it uses a simple table that only contains the foreign keys of the associated objects. It also has even less polymorphic support than has_many :through. Be careful, as this relationship shows up in a lot of old rails tutorials...
Finding things!
AR's find mechanisms are very powerful, and you will probably find yourself referring more to the api docs as you progress, but here are two of the most essential find methods. For the full caboodle, go here: http://www.noobkit.com/activerecord-activerecord-base-find
:all, :first
These do what they seem to.
Call.find_by_user_id(1, :all) #find all calls with user_id of 1
User.find(:first) #find first user
find_by
The examples in the intro already demonstrate AR's find_by_(method) capabilities, but here are some more examples:
User.find_by_name("Aleks Clark") # find user with name value "Aleks Clark"
User.find(1) # find user with id of 1
User.find_by_phone_number("1234567890") # note the underscores
:conditions
The :conditions array in find consists of an SQL fragment, here's the basic syntax:
Call.find(:all, :conditions => ["length > ?", 300]) #find all calls longer than 5 minutes (assuming length contains seconds)
Yay. As you can see, the first element in :conditions is our SQL fragment, and the second element is the bit that's going to be swapped in. This second element can be any Ruby object with a to_s method, although using things like Arrays is probably not a good idea ;) But what if we want two or more values?
Call.find(:all, :conditions => ["length > ? AND length < ?", bottom_limit, upper_limit])
Simply use more ?'s, and remember that they will be called in order, from left to right.
Conclusion
That should be enough to get you started using ActiveRecord. For more detailed documentation, you can check out http://www.noobkit.com a sexy API doc browser, or your local rdocs.
Comments (0)
You don't have permission to comment on this page.