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.
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.
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.
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.
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.
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