forked from mbleigh/acts-as-taggable-on
-
Notifications
You must be signed in to change notification settings - Fork 4
A tagging plugin for Rails applications that allows for custom tagging along dynamic contexts. Fork is to denormalize and optimize for speed (mainly for keeping lock times to a minimum)
License
FormulaMonks/is_taggable
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
is_taggable =============================================================================== This plugin is almost entirely based on acts-as-taggable-on by Michael Bleigh + contributors. is_taggable supports the awesome contextual tagging of acts-as-taggable-on, but with a couple substantial changes to the underlying architecture. Architecture Differences =============================================================================== We had a couple key issues with the underlying architecture in acts-as-taggable-on which we felt deserved a forking into a new direction. There are two main architectural differences between is_taggable and acts-as-taggable-on: 1) is_taggable does *not* use a normalized data model -- there is one table "taggings" and not two tables "taggings" and "tags". This means that tags are duplicated in the taggings table. The reason behind this was that we could not find a reason justifying the join that couldn't be easily overcome with unique indexing and grouped selects. 2) (This is the big one) -- is_taggable skips validations and callbacks on the Tagging model when saving. This totally breaks the normal AR behavior and the behavior used by all other AR tagging plugins. The reason for this is that the taggings are updated with a multi-insert -- which also means this plugin can only be used on databases which support multi-insert (MySQL, Postgres, Oracle, ...). The reason we use a multi-insert is because we ran into massive problems with large numbers of writes on taggings. Take this scenario: I upload a photo and fill in my tags when I upload it. I want people to find it so I tag it with about 20 different keywords. In order to save the tags on my photo the original plugin would have to (at least): 2) do 20 SELECTs on the taggings table for validates_uniqueness_of 4) do some number (at most 20) of INSERTs on the tags table to save the tags 3) do 20 INSERTs on the taggings table to save the taggings So best case 20 INSERTs, 20 SELECTs -- worst case 40 INSERTs, 20 SELECTs. Now get a few users adding lots of tags on things concurrently and you can see writes quickly becoming a problem, and as the number of tags I'm adding grows, the problem gets worse. Individual INSERTs are fast, but once you have concurrency you have lock waits and the the problem gets massively compounded. By using a multi-insert, a non-normalized table, and a "manual" validation (do all the validates_uniqueness_of checks at once) we can get it down to: 2) do 1 SELECT to check for duplicated taggings 3) do 1 multi-INSERT to INSERT all the taggings Furthermore, no matter how many tags your inserting, it's always 1 SELECT and 1 INSERT. Another thing to consider is the impact of updating tags. In the original plugin this was done as a loop causing multiple DELETEs followed by multiple INSERTs. We've taken this down to 1 DELETE followed by 1 multi-INSERT. Compatibility =============================================================================== is_taggable requires that your underlying database support multi-INSERT statements (i.e. INSERT INTO taggings (tag) VALUES ('foo', 'bar', 'baz')). Most "major" databases do -- including MySQL, PostgreSQL and Oracle. It has only been tested with Rails 2.1+ and makes use of named_scope (introduced in Rails 2.1). Installation =============================================================================== GemPlugin ------------------------------------------------------------------------------- Rails 2.1+ introduces gem dependencies, to use them add this line to environment.rb: config.gem "citrusbyte-is_taggable", :source => "http://gems.github.com", :lib => "is_taggable" Then run "rake gems:install" to install the gem. Plugin ------------------------------------------------------------------------------- script/plugin install git://github.com/citrusbyte/is_taggable.git Gem ------------------------------------------------------------------------------- gem install citrusbyte-is_taggable --source http://gems.github.com Post Installation ------------------------------------------------------------------------------- 1. script/generate is_taggable_migration 2. rake db/migrate Testing =============================================================================== is_taggable uses RSpec for its test coverage, if you're using RSpec type: rake spec:plugins Examples (all stolen from acts-as-taggable-on docs) =============================================================================== class User < ActiveRecord::Base is_taggable :tags, :skills, :interests end @user = User.new(:name => "Bobby") @user.tag_list = "awesome, slick, hefty" # this should be familiar @user.skill_list = "joking, clowning, boxing" # but you can do it for any context! @user.skill_list # => ["joking","clowning","boxing"] as TagList @user.save @user.tags # => [<Tag name:"awesome">,<Tag name:"slick">,<Tag name:"hefty">] @user.skills # => [<Tag name:"joking">,<Tag name:"clowning">,<Tag name:"boxing">] # The old way User.find_tagged_with("awesome", :on => :tags) # => [@user] User.find_tagged_with("awesome", :on => :skills) # => [] # The better way (utilizes named_scope) User.tagged_with("awesome", :on => :tags) # => [@user] User.tagged_with("awesome", :on => :skills) # => [] @frankie = User.create(:name => "Frankie", :skill_list => "joking, flying, eating") User.skill_counts # => [<Tag name="joking" count=2>,<Tag name="clowning" count=1>...] @frankie.skill_counts Finding Tagged Objects ====================== is_taggable utilizes Rails 2.1's named_scope to create an association for tags. This way you can mix and match to filter down your results, and it also improves compatibility with the will_paginate gem: class User < ActiveRecord::Base is_taggable :tags named_scope :by_join_date, :order => "created_at DESC" end User.tagged_with("awesome").by_date User.tagged_with("awesome").by_date.paginate(:page => params[:page], :per_page => 20) Relationships ============= You can find objects of the same type based on similar tags on certain contexts. Also, objects will be returned in descending order based on the total number of matched tags. @bobby = User.find_by_name("Bobby") @bobby.skill_list # => ["jogging", "diving"] @frankie = User.find_by_name("Frankie") @frankie.skill_list # => ["hacking"] @tom = User.find_by_name("Tom") @tom.skill_list # => ["hacking", "jogging", "diving"] @tom.find_related_skills # => [<User name="Bobby">,<User name="Frankie">] @bobby.find_related_skills # => [<User name="Tom">] @frankie.find_related_skills # => [<User name="Tom">] Dynamic Tag Contexts ==================== In addition to the generated tag contexts in the definition, it is also possible to allow for dynamic tag contexts (this could be user generated tag contexts!) @user = User.new(:name => "Bobby") @user.set_tag_list_on(:customs, "same, as, tag, list") @user.tag_list_on(:customs) # => ["same","as","tag","list"] @user.save @user.tags_on(:customs) # => [<Tag name='same'>,...] @user.tag_counts_on(:customs) User.find_tagged_with("same", :on => :customs) # => [@user] Tag Ownership ============= Tags can have owners: class User < ActiveRecord::Base is_tagger end class Photo < ActiveRecord::Base is_taggable :locations end @some_user.tag(@some_photo, :with => "paris, normandy", :on => :locations) @some_user.owned_taggings @some_user.owned_tags @some_photo.locations_from(@some_user) Caveats =============================================================================== 1) Your underlying database *must* support multi-INSERT 2) You probably need Rails 2.1+, but seriously named_scope is so cool you need it anyways. 3) You cannot use callbacks/validations on the Tagging model (you can still use them like normal on your Taggables and Taggers...) Contributors =============================================================================== is_taggable: * Ben Alavi & Michel Martens - Ruthless hackers of acts-as-taggable-on acts-as-taggable-on: * Michael Bleigh - Original Author * Brendan Lim - Related Objects * Pradeep Elankumaran - Taggers * Sinclair Bain - Patch King Patch Contributors ------------------------------------------------------------------------------- acts-as-taggable-on: * tristanzdunn - Related objects of other classes * azabaj - Fixed migrate down * Peter Cooper - named_scope fix * slainer68 - STI fix * harrylove - migration instructions and fix-ups * lawrencepit - cached tag work Resources =============================================================================== * GitHub - http://github.com/citrusbyte/is_taggable * Lighthouse - http://citrusbyte.lighthouseapp.com/projects/ is_taggable: Copyright (c) 2008 Citrusbyte, LLC, released under the MIT license acts-as-taggable-on: Copyright (c) 2007 Michael Bleigh (http://mbleigh.com/) and Intridea Inc. (http://intridea.com/), released under the MIT license
About
A tagging plugin for Rails applications that allows for custom tagging along dynamic contexts. Fork is to denormalize and optimize for speed (mainly for keeping lock times to a minimum)
Resources
License
Stars
Watchers
Forks
Releases
No releases published
Packages 0
No packages published
Languages
- Ruby 100.0%