Adding Taggability with a Database Mixin

Problem

Without writing a lot of code, you want to make one of your database tables " taggable"make it possible to add short strings describing a particular item in the table.

Solution

Og comes complete with a tagging mixin. Just call is Taggable on every class you want to be taggable. Og will create all the necessary tables.

Heres the BlogPost class from Recipe 13.12, only this time its Taggable. Og automatically creates a Tag class and the necessary database tables:

	require cookbook_dbconnect
	require og
	require glue/taggable

	class BlogPost
	 is Taggable
	 property :title, :content, String
	end
	og_connect

	# Now we can play around with tags.
	post = BlogPost.new
	post.title = Some more facts about video games
	post.tag([editorial, games])
	
	BlogPost.find_with_tags(games).each { |puts| puts post.title }
	# Some more facts about video games

	Tag.find_by_name(editorial).blog_posts.each { |post| puts post.title }
	# Some more facts about video games

To get this feature in ActiveRecord, youll need to install the acts_as_taggable gem, and you must create the database tables yourself. Here are the tables necessary to add tags to the ActiveRecord BlogPost class (first described in Recipe 13.11): a generic tags table and a join table connecting it to blog_posts.

	DROP TABLE IF EXISTS tags;
	CREATE TABLE tags (
	 id INT(11) NOT NULL AUTO_INCREMENT,
	 name VARCHAR(32),
	 PRIMARY KEY (id)
	) ENGINE=InnoDB;

	DROP TABLE IF EXISTS tags_blog_posts;
	CREATE TABLE tags_blog_posts (
	 tag_id INT(11),
	 blog_post_id INT(11)
	) ENGINE=InnoDB;

Note that the join table violates the normal ActiveRecord naming rule. Its called tags_blog_posts, even though alphabetical ordering of its component tables would make it blog_posts_tags. ActiveRecord does this so all of your applications tags_join tables will show up together in a sorted list. If you want to call the table blog_posts_tags instead, youll need to pass the name as the :join_table parameter when you call the acts_as_ taggable decorator below.

Heres the ActiveRecord code that makes BlogPost taggable. If you ran the previous example, run this one in a new irb session so that you can define a new BlogPost class.

	require cookbook_dbconnect
	require 	aggable
	activerecord_connect

	class Tag < ActiveRecord::Base
	end

	class BlogPost < ActiveRecord::Base
	 acts_as_taggable
	end

	# Now we can play around with tags.
	post = BlogPost.create(:title => Some more facts about inflation.)
	post.tag([editorial, economics])

	BlogPost.find_tagged_with(:any=>editorial).each { |post| puts post.title }
	# Some more facts about inflation.

Discussion

A mixin class like Enumerable is an easy way to add a lot of functionality to an existing class without writing much code. Database mixins work the same way: you can add new objects and relationships to your data model without having to write a lot of database code. Of course, youll still need to decide how to incorporate tags into your user interface.

The Og and ActiveRecord tagging mixins work the same way, although the Og mixin hides the details. In addition to your original database table (the one you want to tag), you need a table that contains tags, and a join table connecting the tags to the tagged. Whether you use Og or ActiveRecord, the database schema looks something like Figure 13-1.

Figure 13-1. BlogPosts are associated with Tags through a join table


The tagging mixin saves you from having to write code for managing the tag table, and the original tables relationship with it.

But there are two ways to tag something, and weve only covered one. You add tags to BlogPost if you want one set of tags for each blog post, probably set by the author of the post. The tags act as canonical categories. What if you want to create a tag system where everyone has their own set of tags for blog posts? Instead of a single system imposed by the authors, every user gets to define a categorization system that makes sense to them.

When you do this, the application doesn tag a blog post itself. It tags one persons relationship to a blog post. The schema looks something like Figure 13-2.

Figure 13-2. When tags are per-user, the join table associates BlogPosts, Tags, and People


Lets implement per-user tagging in ActiveRecord. Instead of making the tags_blog_posts table connect a blog post directly to a tag, well have it connect a tag, a blog post, and a person.

	DROP TABLE IF EXISTS tags_blog_posts;
	CREATE TABLE tags_blog_posts (
	 tag_id INT(11),
	 blog_post_id INT(11),
	 created_by_id INT(11)
	) ENGINE=InnoDB;

Heres the Ruby code. First, some setup weve seen before:

	require cookbook_dbconnect
	require  
taggable
	activerecord_connect

	class Tag < ActiveRecord::Base
	end

	class Person < ActiveRecord::Base
	end

When each blog post had one set of tags, we called acts_as_taggable with no arguments, and the BlogPost class was associated directly with the Tag class. This time, we tell acts_as_taggable that BlogPost objects are associated with Tag through the TagBlogPost class:

	# ActiveRecord will automatically define the TagBlogPost class when
	# we reference it.
	class BlogPost < ActiveRecord::Base
	 acts_as_taggable :join_class_name => TagBlogPost
	end

Now we tell TagBlogPost that its associated with the Person class: every TagBlogPost represents one persons opinions about a single blog post:

	# Specify that a TagBlogPost is associated with a specific user.
	class TagBlogPost
	 belongs_to :created_by, :class_name => Person,
	 :foreign_key => created_by_id
	end

Now each Person can have their own set of tags on each BlogPost:

	post = BlogPost.create(:title => My visit to the steel mill.)
	alice = Person.create(:name=>"Alice")
	post.tag([	ravelogue, metal, interesting],
	 :attributes => { :created_by => alice })

	alices_interests = BlogPost.find_tagged_with(:all => interesting,
	 :condition => "tags_people.created_by_id = #{alice.id}")
	alices_interests.each { |article| puts article.title }
	# My visit to the steel mill.

Og and ActiveRecord each come with several common mixins. For instance, you can use a mixin to model parent-child relationships between tables (Og is Hierarchical, ActiveRecord acts_as_tree and acts_as_nested_set), or to treat the rows of a table as an ordered lists (Og is Orderable, ActiveRecord acts_as_list). These can save you a lot of time.

See Also

  • The built-in ActiveRecord mixins are all in the ActiveRecord::Acts module; see the generated documentation at http://rubyonrails.org/api/
  • The taggable reference for ActiveRecord (http://taggable.rubyforge.org/)


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