Using Object Relational Mapping with Og
Credit: Mauro Cicio
Problem
You want to store data in a database, without having to use SQL to create or access the database.
Solution
Use the Og (ObjectGraph) library, available as the og gem. Where ActiveRecord has a database-centric approach to object-relational mapping, Og is Ruby-centric. With ActiveRecord, you define the database schema ahead of time and have the library figure out what the Ruby objects should look like. With Og, you define the Ruby objects and let the library take care of creating the database schema.
The only restriction Og imposes on your class definitions is that you must use special versions of the decorator methods for adding attribute accessors. For instance, instead of calling attribute to define accessor methods, you call property.
Here we define a basic schema for a weblog program, like that defined in Recipe 13.11:
require cookbook_dbconnect require og class BlogPost property :title, :content, String end class Comment property :author, :content, String belongs_to : og_post, BlogPost end # Now that Comments been defined, add a reference to it in BlogPost. class BlogPost has_many :comments, Comment end
After defining the schema, we call the og_connect method defined in the chapter introduction. Og automatically creates any necessary database tables:
og_connect # Og uses the Mysql store. # Created table ogcomment. # Created table ogblogpost.
Now we can create a blog post and some comments:
post = BlogPost.new post.title = "First post" post.content = "Here are some pictures of our iguana." post.save! [["Alice", "Thats one cute iguana!"], ["Bob", "Thank you, Alice!"]].each do |author, content| comment = Comment.new comment.blog_post = post comment.author = author comment.content = content comment.save! end
As with ActiveRecord, we can query the tables, relate blog posts to their comments, and relate comments back to their blog posts:
post = BlogPost.first puts %{#{post.comments.size} comments for "#{post.title}"} # 2 comments for "First post" post.comments.each do |comment| puts "Comment author: #{comment.author}" puts "Comment: #{comment.content}" end # Comment author: Alice # Comment: Thats one cute iguana! # Comment author: Bob # Comment: Thank you, Alice! puts %{The first comment was made on "#{Comment.first.blog_post.title}"} # The first comment was made on "First post"
Discussion
Like the ActiveRecord library, Og implements Martin Fowlers Active Record Pattern. While ActiveRecord does this by making all classes derive from the base class ActiveRecord::Base, Og does it by using custom attribute accessors instead of the traditional Ruby accessors. In this example, Comment and BlogPost are POR (Plain Old Ruby) classes, with accessor methods like author and author=, but those methods were defined with Og decorators instead of the standard Ruby decorators. This table shows the mapping between the two sets of decorators.
Standard Ruby accessors | Og accessors |
---|---|
attribute | roperty |
attr_accessor | prop_accessor |
attr_reader | prop_reader |
attr_writer | prop_writer |
Each of the Og decorator methods takes a Ruby class as its last argument: String, Integer, or the like. Og uses this to define the type of the corresponding database row. You can also specify Object as a field type, and Og will transparently store YAML representations of arbitrary Ruby objects in the corresponding database field.
ActiveRecord defines all kinds of conventions about how you e supposed to name your database tables and fields. Og doesn care: it names database tables and fields that correspond to the names you use in your Ruby code.
Just as with ActiveRecord, relationships between Og tables are defined within Ruby code, using decorator methods. The API is almost exactly the same as ActiveRecords. In the Solution section, we saw how to create a one-to-many relationship between blog posts and comments: by calling belongs_to in Comment and has_many in BlogPost. This relationship makes it possible to simply call BlogPost#comments and get an array of comments on a post.
Og defines two more decorator methods for describing relationships between tables. One of them is the has_one association, which is rarely used: if theres a one-to-one relationship between the rows in two tables, then you should probably just merge the tables.
The other decorator is many_to_many, which lets you to join two different tables with an intermediate join table. This lets you create many-to-many relationships, common in (to take one example) permissioning systems.
For an example of many_to_many, lets make our blog a collaborative effort. Well add a User class that holds the posts authors names, and fix it so that each blog post can have multiple authors. Of course, each author can also contribute to multiple posts, so weve got a many-to-many relationship between users and blog posts. Og needs to know the class definition in order to create the necessary database tables, so the following code snippet should appear before the og_connect invocation in your program:
class Person property :name, String many_to_many :posts, BlogPost end
The many_to_many decorator tells Og to create a table to store the people, and a join table to map authors to their blog posts. It also defines methods that navigate the join table, as well see in a moment.
Of course, the many-to-many relationship goes both ways: BlogPost has a many-to-many relationship to Person. So add a many_to_many call to the definition of BlogPost (this, too, must show up before your og_connect call):
class BlogPost many_to_many :authors, Person end
With these relationships in place, its easy to find blog posts for an author, and authors for a blog post:
og_connect # Retroactively make Bob and Carol the collaborative authors of our # first blog post. [Bob, Carol].each do |name| p = Person.new p.name = name p.save end Person.find_by_name(Bob).add_post(post) Person.find_by_name(Carol).add_post(post) author = Person.first puts "#{author.name} has made #{author.posts.size} blog post(s)." # Bob has made 1 blog post(s). puts %{The blog post "#{post.title}" has #{post.authors.size} author(s).} # The blog post "First post" has 2 author(s).
To add an anonymous BlogPost on the fly, use the add_post method as follows:
author.add_post(BlogPost.create_with({ :title => Second post, :content => We have some cats as well. } ))
Since Person posts returns an array-like object, you can iterate over it to find all the blog posts to which a given user contributed:
author.posts.each do |post| puts %{#{author.name}s blog post "#{post.title}" has #{post.comments.size} comments.} end # Bobs blog post "First post" has 2 comments. # Bobs blog post "Second post" has 0 comments.
If you want to delete an object from the database, you can use the delete method available to all Og database objects:
BlogPost.first.delete
Deleting a blog post will automatically remove all the comments associated with that blog post. This automatic deletion (i.e., cascade deletion) is not always a good idea. For instance, we don want the authors of a blog post to be deleted when the post itself is deleted! We can avoid the cascade deletion by passing false in as an argument to the delete method:
BlogPost.first.delete(false)
If you want some associated objects (like comments) to get cascade-deleted, and other objects (like authors) to be left alone, the best strategy is to implement the cascade yourself, in post-delete hooks.
See Also
- The Active Record pattern is described in Patterns of Enterprise Application Architecture by Martin Fowler (Addison-Wesley)
Категории