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.

Table 13-2.

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)


Strings

Numbers

Date and Time

Arrays

Hashes

Files and Directories

Code Blocks and Iteration

Objects and Classes8

Modules and Namespaces

Reflection and Metaprogramming

XML and HTML

Graphics and Other File Formats

Databases and Persistence

Internet Services

Web Development Ruby on Rails

Web Services and Distributed Programming

Testing, Debugging, Optimizing, and Documenting

Packaging and Distributing Software

Automating Tasks with Rake

Multitasking and Multithreading

User Interface

Extending Ruby with Other Languages

System Administration



Ruby Cookbook
Ruby Cookbook (Cookbooks (OReilly))
ISBN: 0596523696
EAN: 2147483647
Year: N/A
Pages: 399

Flylib.com © 2008-2020.
If you may any questions please contact us: flylib@qtcs.net