ActsAsTaggableOn is the clear leader in tagging solutions in Rails. Unfortunately it does not appear to work well with Mongoid. For Mongo the clear leader for tagging solutions is to include an indexed array of strings as tags. There are several solutions that use this mechanism. Unfortunately, sometimes you actually do need a many-to-many table solution even in Mongo which happens to be the situation I somehow have found myself in.
Therefore, we are building a new solution to implement an ActsLikeTaggableOn
like solution using
Mongo. The general goal is to mimic the features and interface of ActsLikeTaggableOn as much as
feasible/possible.
This is not a direct port of ActsLikeTaggableOn
at this time for several reason, the main one being
time. Mongoid and ActiveRecord are enough different that the complications that would arise from forking
and trying to modify it to work with Mongoid do not seem insignificant.
Add this line to your application's Gemfile:
gem 'acts-as-taggable-on-mongoid'
And then execute:
$ bundle
Or install it yourself as:
$ gem install acts-as-taggable-on-mongoid
-
tag_list - a generic term for the list field in a
Taggable
object that is defined using theacts_as_taggable_on
method. Each tag_list is definied independentally and can have separate settings. -
tag_type, context - For whatever reason, these two terms are both used and are synonymous with each other.
context
is used in the Tag and Taggable tables to differentiate that a record is associated with a particular tag_list -
Taggable object - A database model object which can have tag_lists associated with it. All taggable objects can have multiple tag_lists in it.
-
Tagging table - A Table for storing the which Tags are associated with a Taggable object. The Tag details are denormalized into the Tagging table to allow efficient querying of Taggable objects without having to go through the Taggings table.
-
Tag table - A Table for storing the tags. Tags are used primarily to maintain a usage counter as the tag details are denormalized into the Tagging tagle
NOTE: Unlike the
ActsAsTaggableOn
gem, I group Tags by context. Tags with the same name for different contexts keep separate counts and are considered different Tags. It is simpler to combine Tags and their counts by name then it is to split them out. -
Tagger - A database object which is given credit for creating a Tagging. This object is an external model from this gem. Tagger objects can be specified as being a Tagger using the
acts_as_tagger
method which allows a Tagger object totag
Taggable objects. -
Owner - A database object which is given ownership of a Tag object. This object is an external model from this gem. By default when Tags are created, the owner of a Tag is the same as the Tagger for the Tagging.
The database structure is:
+----------+ +---------+ +-----+
| Taggable | -> | Tagging | <- | Tag |
+----------+ +---------+ +-----+
Setup
class User
include ::Mongoid::Document
acts_as_taggable # Alias for acts_as_taggable_on :tags
acts_as_taggable_on :skills, :interests
end
class UsersController < ApplicationController
def user_params
params.require(:user).permit(:name, :tag_list) ## Rails 4 strong params usage
end
end
@user = User.new(:name => "Bobby")
Add and remove a single tag
@user.tag_list.add("awesome") # add a single tag. alias for <<
@user.tag_list.remove("awesome") # remove a single tag
@user.save # save to persist tag_list
Add and remove multiple tags in an array
@user.tag_list.add("awesome", "slick")
@user.tag_list.remove("awesome", "slick")
@user.save
You can also add and remove tags in format of String. This would be convenient in some cases such as handling tag input param in a String.
Pay attention you need to add parse: true
as option in this case.
You may also want to take a look at delimiter in the string. The default
is comma ,
so you don't need to do anything here. However, if you want to
use a different delimiter you will have to change the delmiter on the
DefaultParser
or create a new Parser which splits the string using the
algorithm or method you want/need. See the GenericParser
class for details
on creating a new custom parser of your own design.
@user.tag_list.add("awesome, slick", parse: true)
@user.tag_list.remove("awesome, slick", parse: true)
You can also add and remove tags by direct assignment. By default, direct assignment will parse values passed into it. Note this will remove existing tags so use it with attention.
@user.tag_list = "awesome, slick, hefty"
@user.save
@user.reload
@user.tags
=> [#<ActsAsTaggableOnMongoid::Models::Tag id: 1, name: "awesome", taggings_count: 1>,
#<ActsAsTaggableOnMongoid::Models::Tag id: 2, name: "slick", taggings_count: 1>,
#<ActsAsTaggableOnMongoid::Models::Tag id: 3, name: "hefty", taggings_count: 1>]
With the defined context in model, you have multiple new methods at disposal
to manage and view the tags in the context. For example, with :skill
context
these methods are added to the model: skill_list
(and skill_list.add
, skill_list.remove
skill_list=
), skills
(plural), skill_counts
.
@user.skill_list = "joking, clowning, boxing"
@user.save
@user.reload
@user.skills
=> [#<ActsAsTaggableOnMongoid::Models::Tag id: 1, name: "joking", taggings_count: 1>,
#<ActsAsTaggableOnMongoid::Models::Tag id: 2, name: "clowning", taggings_count: 1>,
#<ActsAsTaggableOnMongoid::Models::Tag id: 3, name: "boxing", taggings_count: 1>]
@user.skill_list.add("coding")
@user.skill_list
# => ["joking", "clowning", "boxing", "coding"]
@another_user = User.new(:name => "Alice")
@another_user.skill_list.add("clowning")
@another_user.save
User.skill_counts
=> [#<ActsAsTaggableOnMongoid::Models::Tag id: 1, name: "joking", taggings_count: 1>,
#<ActsAsTaggableOnMongoid::Models::Tag id: 2, name: "clowning", taggings_count: 2>,
#<ActsAsTaggableOnMongoid::Models::Tag id: 3, name: "boxing", taggings_count: 1>]
To preserve the order in which tags are created use acts_as_ordered_taggable
:
class User < ActiveRecord::Base
# Alias for acts_as_ordered_taggable_on :tags
acts_as_ordered_taggable
acts_as_ordered_taggable_on :skills, :interests
end
@user = User.new(:name => "Bobby")
@user.tag_list = "east, south"
@user.save
@user.tag_list = "north, east, south, west"
@user.save
@user.reload
@user.tag_list # => ["north", "east", "south", "west"]
class MyTaggable
include ::Mongoid::Document
# See lib/acts_as_taggable_on_mongoid/taggable.rb for more details on options.
acts_as_taggable_on :my_tags,
:your_tags,
:other_tags,
:etc_tags,
parser: MyCustomParser,
preserve_tag_order: true,
cached_in_model: true,
force_lowercase: true,
force_parameterize: true,
remove_unused_tags: true,
tags_table: CustomTag,
taggings_table: CusomTagging,
default: "defalut, values"
end
acts_as_taggable_on
will define the following methods and relationships based on the values passed in
(only methods for the my_tags
tag_list are shown, but methods for each of the other tag_lists will be
created also.)
custom_taggings
- a relationship on aMyTaggable
object which will return allCustomTagging
s across all contexts/tag_types for a particular tagging. Usage:my_taggable = MyTaggable.find(taggable_id) my_taggable.custom_taggings.to_a => [#<CustomTagging context: "my_tags", taggable_id: my_taggable.id>, #<CustomTagging context: "your_tags", taggable_id: my_taggable.id>, ...]
base_custom_tags
- Returns aCriteria
for allCustomTag
s which are associated with anyMyTaggable
. Usage:my_taggable = MyTaggable.find(taggable_id) my_taggable.base_custom_tags.to_a => [#<CustomTag context: "my_tags", taggable_type: "MyTaggable">, #<CustomTag context: "your_tags", taggable_type: "MyTaggable">, ...]
my_tag_custom_taggings
- Returns allCustomTagging
s for a particularMyTaggable
object for the named context in the appropriate sorted order for that context (based on ifpreserve_tag_order
is true.) Usage:my_taggable = MyTaggable.find(taggable_id) my_taggable.my_tag_custom_taggings.to_a => [#<CustomTagging context: "my_tags", taggable_id: my_taggable.id>, #<CustomTagging context: "my_tags", taggable_id: my_taggable.id>, ...]
my_tags
- Returns allCustomTag
s for a particularMyTaggable
object for the named context sorted in the order that the tags were added to that object ifpreserve_tag_order
is true. NOTE: This is done through a mapping from thecustom_taggings
relationship and is therefore not very efficient. Usage:my_taggable = MyTaggable.find(taggable_id) my_taggable.my_tags.to_a => [#<CustomTag context: "my_tags", taggable_type: "MyTaggable">, #<CustomTag context: "my_tags", taggable_type: "MyTaggable">, ...]
my_tag_list
- Returns an array of all tag values that are associated with a context for aMyTaggable
object. The array will be a simple array of strings that are sorted in the appropriate order based on the value ofpreserve_tag_order
. Usage:my_taggable = MyTaggable.find(taggable_id) my_taggable.my_tag_list => ["tag value 1", "tag value 2", "tag value 3", ...] # This list will be sorted in the order that the values were added to the list if `preserve_tag_order` is true.
my_tag_list=
- Sets the list of tags for that context for a particularMyTaggable
object. Setting the value in this way will automatically parse the string. You can force the string to not be parsed or to use a particular parser by passing an array with the appropriate options (parse
andparser
). Ifpreserve_tag_order
is true then values will be removed and re-added as necessary to ensure that the tag order is preserved for the set. Changes to themy_tag_list
will not be persisted until after the object is saved just like any other field. Usage:# Set list and save - Sets the list of tags for the list my_taggable = MyTaggable.find(taggable_id) # preserve_tag_order = false my_taggable.my_tag_list = "tag value 3, tag value 2, tag value 1" my_taggable.save! my_taggable.reload.my_tag_list => ["tag value 3", "tag value 2", "tag value 1"]
# Use update_attributes - Sets the list of tags for the list as if assigned (i.e. parsing is assumed) # preserve_tag_order = false my_taggable.update_attributes! my_tag_list: "tag value 2, tag value 1, tag value 3" my_taggable.reload.my_tag_list => ["tag value 3", "tag value 2", "tag value 1"]
# Use update_attributes - Sets the list of tags for the list - keep the passed in order. # preserve_tag_order = true my_taggable.update_attributes! my_tag_list: "tag value 2, tag value 1, tag value 3" my_taggable.reload.my_tag_list => ["tag value 2", "tag value 1", "tag value 3"]
# Set list and save - disabling parsing. The string passed in i used as-is # preserve_tag_order = false my_taggable.my_tag_list = ["tag value 3, tag value 2, tag value 1", parse: false] my_taggable.save! my_taggable.reload.my_tag_list => ["tag value 3, tag value 2, tag value 1"]
# Set the list and save using a custom parser: # preserve_tag_order = false my_taggable.my_tag_list = ["tag value 2;tag value 1;tag value 3", parser: SemiColonParser] my_taggable.save! my_taggable.reload.my_tag_list => ["tag value 2", "tag value 1", "tag value 3"]
# Use add, remove, concat and << to change the list and save. # preserve_tag_order = false # parse is ONLY assumed true for assignment and default values. my_taggable.my_tag_list.add "1, 2", "3, 4", "5", parse: true # my_tag_list => ["tag value 2", "tag value 1", "tag value 3", "1", "2", "3", "4", "5"] my_taggable.my_tag_list.add "6, 7", "8, 9", "10" # my_tag_list => ["tag value 2", "tag value 1", "tag value 3", "1", "2", "3", "4", "5", "6, 7", "8, 9", "10"] my_taggable.my_tag_list.remove "2, 3", "4, 9", parse: true # my_tag_list => ["tag value 2", "tag value 1", "tag value 3", "1", "5", "6, 7", "8, 9", "10"] my_taggable.my_tag_list.remove "6, 7", "10", "not in list" # my_tag_list => ["tag value 2", "tag value 1", "tag value 3", "1", "5", "8, 9"] my_taggable.my_tag_list << ["11, 12", "13, 14", parse: true] # my_tag_list => ["tag value 2", "tag value 1", "tag value 3", "1", "5", "8, 9", "11", "12", "13", "14"] my_taggable.my_tag_list << ["15, 16", "17, 18"] # my_tag_list => ["tag value 2", "tag value 1", "tag value 3", "1", "5", "8, 9", "11", "12", "13", "14", "15, 16", "17, 18"] my_taggable.my_tag_list << "19" # my_tag_list => ["tag value 2", "tag value 1", "tag value 3", "1", "5", "8, 9", "11", "12", "13", "14", "15, 16", "17, 18", "19"] # NOTE: Unlike the other methods, passing options for concat is not supported. my_taggable.my_tag_list.concat ["20", "21"] # my_tag_list => ["tag value 2", "tag value 1", "tag value 3", "1", "5", "8, 9", "11", "12", "13", "14", "15, 16", "17, 18", "19", "20", "21"] # assignment will apply preferences: # force_lowercase = true my_taggable.my_tag_list = "Tag Value 1, Tag Value 2, Tag Value 3" # my_tag_list => ["tag value 3", "tag value 2", "tag value 1"] my_taggable.my_tag_list.remove "TAG VALUE 1" # my_tag_list => ["tag value 3", "tag value 2"] # force_parameterize = true my_taggable.my_tag_list = "tag value 1, tag value 2, tag value 3" # my_tag_list => ["tag-value-1", "tag-value-2", "tag-value-3"] my_taggable.my_tag_list.remove "tag value 1" # my_tag_list => ["tag-value-2", "tag-value-3"]
# Values are not stored until save is called. # preserve_tag_order = false my_taggable.my_tag_list = "tag value 3, tag value 2, tag value 1" my_taggable.reload.my_tag_list => []
all_my_tags_list
- Returns allCustomTag
s for the given context for anyMyTaggable
object. NOTE: UnlikeActsAsTaggableOn
, the Tags returned represent all Tags that have ever been used for that context for theMyTaggable
context. There is no guarantee that theCustomTag
s returned are currently being used by anyMyTaggable
object. Usage:my_taggable = MyTaggable.find(taggable_id) my_taggable.all_my_tags_list.to_a => [#<CustomTag context: "my_tags", taggable_type: "MyTaggable">, #<CustomTag context: "my_tags", taggable_type: "MyTaggable">, ...]
my_tag_list?
- Returns true if the list has been set to a value. Usage:my_taggable = MyTaggable.find(taggable_id) my_taggable.my_tag_list? => false my_taggable.my_tag_list = "tag value 1, tag_value 2" my_taggable.my_tag_list? => true
my_tag_list_change
- Returns the old and new values for a changed list. Usage:my_taggable = MyTaggable.create!(my_tag_list: "tag value 1, tag_value 2") my_taggable.my_tag_list = "tag value 2, tag value 3" my_taggable.my_tag_list_change => [["tag value 1", "tag value 2"], ["tag value 2", "tag value 2"]]
my_tag_list_changed?
- Returns the old and new values for a changed list. Usage:my_taggable = MyTaggable.create!(my_tag_list: "tag value 1, tag_value 2") my_taggable.my_tag_list_changed? => false my_taggable.my_tag_list = "tag value 2, tag value 3" my_taggable.my_tag_list_changed? => true
my_tag_list_will_change
- Tells the model that themy_tag_list
field will be changing. This method is primarily intended for internal use. Usage:my_taggable = MyTaggable.create!(my_tag_list: "tag value 1, tag_value 2") my_taggable.my_tag_list_will_change
my_tag_list_changed_from_default?
- Returns true if the value does not match the default for the field Usage:my_taggable = MyTaggable.create! my_taggable.my_tag_list_changed_from_default? => false my_taggable.my_tag_list = "tag value 2, tag value 3" my_taggable.my_tag_list_changed_from_default? => true
my_tag_list_was
- Returns the old values for a changed list. Usage:my_taggable = MyTaggable.create!(my_tag_list: "tag value 1, tag_value 2") my_taggable.my_tag_list = "tag value 2, tag value 3" my_taggable.my_tag_list_was => ["tag value 1", "tag value 2"]
reset_my_tag_list!
- Resets a changed value back to what it was before Usage:my_taggable = MyTaggable.create!(my_tag_list: "tag value 1, tag_value 2") my_taggable.my_tag_list = "tag value 2, tag value 3" my_taggable.reset_my_tag_list! => ["tag value 1", "tag value 2"]
reset_my_tag_list_to_default!
- Resets a changed value back to the default values. Usage:my_taggable = MyTaggable.create!(my_tag_list: "tag value 1, tag_value 2") my_taggable.my_tag_list = "tag value 2, tag value 3" my_taggable.reset_my_tag_list_to_default! => ["default", "values"]
You can find the most or least used tags by using:
ActsAsTaggableOnMongoid::Models::Tag.most_used
ActsAsTaggableOnMongoid::Models::Tag.least_used
You can also filter the results by passing the method a limit, however the default limit is 20.
ActsAsTaggableOnMongoid::Models::Tag.most_used(10)
ActsAsTaggableOnMongoid::Models::Tag.least_used(10)
Acts As Taggable On uses scopes to create an association for tags. This way you can mix and match to filter down your results.
class User < ActiveRecord::Base
acts_as_taggable_on :tags, :skills
scope :by_join_date, order("created_at DESC")
end
User.tagged_with("awesome").by_join_date
User.tagged_with("awesome").by_join_date.paginate(:page => params[:page], :per_page => 20)
# Find users that matches all given tags:
# NOTE: This only matches users that have the exact set of specified tags. If a user has additional tags, they are not returned.
User.tagged_with(["awesome", "cool"], :match_all => true)
# Find users with any of the specified tags:
User.tagged_with(["awesome", "cool"], :any => true)
# Find users that have not been tagged with awesome or cool:
User.tagged_with(["awesome", "cool"], :exclude => true)
# Find users with any of the tags based on context:
User.tagged_with(['awesome', 'cool'], :on => :tags, :any => true).tagged_with(['smart', 'shy'], :on => :skills, :any => true)
You can also use :wild => true
option along with :any
or :exclude
option. It will be looking for %awesome%
and %cool%
in SQL.
Tip: User.tagged_with([])
or User.tagged_with('')
will return []
, an empty set of records.
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">]
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.tagged_with("same", :on => :customs) # => [@user]
If you want to change how tags are parsed, you can define your own implementation:
class MyParser < ActsAsTaggableOnMongoid::GenericParser
def parse
tags.each_with_object do |tag, tag_list|
tag_list.add tag.split('|')
end
end
def to_s
tags.join("|")
end
end
Now you can use this parser, passing it as parameter:
@user = User.new(:name => "Bobby")
@user.tag_list = "east, south"
@user.tag_list.add("north|west", parser: MyParser)
@user.tag_list # => ["north", "east", "south", "west"]
# Or also:
@user.tag_list.parser = MyParser
@user.tag_list.add("north|west")
@user.tag_list # => ["north", "east", "south", "west"]
Or change it globally:
ActsAsTaggableOnMongoid.default_parser = MyParser
@user = User.new(:name => "Bobby")
@user.tag_list = "east|south"
@user.tag_list # => ["east", "south"]
Tags can have owners:
class User < ActiveRecord::Base
acts_as_tagger
end
class Photo < ActiveRecord::Base
acts_as_taggable_on :locations
end
@some_user.tag(@some_photo, :with => "paris, normandy", :on => :locations)
@some_user.owned_taggings
@some_user.owned_tags
Photo.tagged_with("paris", :on => :locations, :owned_by => @some_user)
@some_photo.locations_from(@some_user) # => ["paris", "normandy"]
@some_photo.tagger_location_lists[@some_user] # => [#<ActsAsTaggableOnMongoid::Models::Tag id: 1, name: "paris">...]
@some_photo.tagger_location_lists[nil] # => Ownerships equivalent to saying @some_photo.locations
@some_user.tag(@some_photo, :with => "paris, normandy", :on => :locations, :skip_save => true) #won't save @some_photo object
Note that by default tag_list
only returns tags whose taggings do not
have an owner. Continuing from the above example:
@some_photo.tag_list # => []
To retrieve all tags of an object (regardless of ownership) or if only
one owner can tag the object, you can use all_tags_list
.
Tags are stored in a Hash grouped by Tagger
. You can access this list
using tagger_<tag_type>_lists
. This allows you to directly access and
use the TagList
for any owner just like you would the <tag_type>_list
methods.
Examples:
tag_lists = @some_photo.tagger_location_lists
tag_lists.keys # => [@some_user]
# Add tags for @some_user
tag_lists[@some_user].add "berlin, london", parse: true
# Remove tags for @some_user
tag_lists[@some_user].remove "paris"
# Set the list for @some_user (replacing existing tags)
tag_lists[@some_user] = "new york, chicago, brussels, rome"
# Add tags for a new owner:
tag_lists[@other_user] = "moscow, bejing, tokyo"
Owned Tags can be difficult to work with having to manage owners manually. To ease that process, I have added the concept of defaults to use when working with Owned tags.
When a tag is defined, you can also define default behavior when creating the tag and accessing it, simplifying using the tag in everyday access.
Options:
- tagger: true - Simply defines that a Tag definition supports the concept of Taggers and allows setting the Tagger for a list.
- tagger: { default_tagger: :method_name} - This allows you to specify that when a Tagging is created on a Taggable object that the Taggable object will supply the default tagger using the indicated method.
- tagger: { tag_list_uses_default_tagger: true } - A
shortcut/syntax sugar to treat
<tag_type>_list
to return the tags for thedefault_tagger
instead of no taggers
class Car
acts_as_taggable_on tagger: { default_tagger: :owner, tag_list_uses_default_tagger: true }
belongs_to :owner, class_name: "User"
end
class User
acts_as_tagger
has_many :cars
end
car = @some_user.cars.first
car.tag_list = "antique, warrantied, registered"
car.tag_lists[car.owner] # => ["antique", "warrantied", "registerd"]
car.tag_lists[nil] # => []
car.tag_lists[@potential_buyer] = ["inspected", "test drove", "good value"]
car.tagger_tag_list(@potential_buyer) # => ["inspected", "test drove", "good value"]
all_list = car.all_tag_list # => ["antique", "warrantied", "registerd", "inspected", "test drove", "good value"]
all_list.tagger # => @some_user
# Changing `all_list` does NOT affect/change the tags on `car`
# Changing other lists will:
all_list.remove "warrantied", "test drove"
car.tag_lists[car.owner] # => ["antique", "warrantied", "registerd"]
car.tagger_tag_list(@potential_buyer) # => ["inspected", "test drove", "good value"]
car.all_tag_list # => ["antique", "warrantied", "registerd", "inspected", "test drove", "good value"]
car.tag_lists[car.owner].add "deposit"
car.tagger_tag_list(@potential_buyer).remove "good value"
car.tag_lists[car.owner] # => ["antique", "warrantied", "registerd", "deposit"]
car.tagger_tag_list(@potential_buyer) # => ["inspected", "test drove"]
car.all_tag_list # => ["antique", "warrantied", "registerd", "deposit", "inspected", "test drove"]
@bobby = User.find_by_name("Bobby")
@bobby.skill_list # => ["jogging", "diving"]
@bobby.skill_list_changed? #=> false
@bobby.changes #=> {}
@bobby.skill_list = "swimming"
@bobby.changes.should == {"skill_list"=>["jogging, diving", ["swimming"]]}
@bobby.skill_list_changed? #=> true
@bobby.skill_list_change.should == ["jogging, diving", ["swimming"]]
If the object allows for Taggers, there could be multiple Taggers each with their own changes. In this case, the changes (and corresponding was values) will be hashes of the Tagger and the tag lists.
@bobby = User.find_by_name("Bobby")
@bobby.skill_list_from(@teacher).add "attentive, assertive, gold star", parse: true
@bobby.skill_list_from(@parent).add "forger, standing in corner, truant", parse: true
@bobby.skill_list_change
# => [ {},
# => { @teacher => ["attentive", "assertive", "gold star"],
# => @parent => ["forger", "standing in corner", "truant"] } ]
To construct tag clouds, the frequency of each tag needs to be calculated.
Because we specified acts_as_taggable_on
on the User
class, we can
get a calculation of all the tag counts by using User.tag_counts_on(:customs)
. But what if we wanted a tag count for
a single user's posts? To achieve this we call tag_counts on the association:
User.find(:first).posts.tag_counts_on(:tags)
A helper is included to assist with generating tag clouds.
Here is an example that generates a tag cloud.
Helper:
module PostsHelper
include ActsAsTaggableOnMongoid::TagsHelper
end
Controller:
class PostController < ApplicationController
def tag_cloud
@tags = Post.tag_counts_on(:tags)
end
end
View:
<% tag_cloud(@tags, %w(css1 css2 css3 css4)) do |tag, css_class| %>
<%= link_to tag.name, { :action => :tag, :id => tag.name }, :class => css_class %>
<% end %>
CSS:
.css1 { font-size: 1.0em; }
.css2 { font-size: 1.2em; }
.css3 { font-size: 1.4em; }
.css4 { font-size: 1.6em; }
Each Tag that is defined allows you to specify a custom Tags
or Taggings
table for the data.
This allows you to add custom columns or features as needed.
To create a custom Tag
, you can either include ActsAsTaggableOnMongoid::Models::Concerns::TagModel
or
you can include one or more of the sub-concerns to add the features you want to inherit and define your own
version of those features yourself. If you do not include the TagModel
concern and you pick the modules
you want to add yourself, please note that the order of the included modules is important and that if you
do not include the modules in the order specified, some features may not perform as you expect in some
fringe cases.
Because the Tags tables and the Taggings tables refer to each other with the taggings
and tags
relationships
respectively, if you create a custom Tags table you should create a custom Taggings table as well. This means
that you will need to redefine the respective relationships in the two custom models.
class CustomTag
include ActsAsTaggableOnMongoid::Models::Concerns::TagModel
has_many :taggings, dependent: :destroy, class_name: "CustomTagging"
end
class CustomTag
include ActsAsTaggableOnMongoid::Models::Concerns::TagFields
include ActsAsTaggableOnMongoid::Models::Concerns::TagMethods
has_many :taggings, dependent: :destroy, class_name: "CustomTagging"
include ActsAsTaggableOnMongoid::Models::Concerns::TagValidations
include ActsAsTaggableOnMongoid::Models::Concerns::TagScopes
end
To create a custom Tagging
, you can either include ActsAsTaggableOnMongoid::Models::Concerns::TaggingModel
or
you can include one or more of the sub-concerns to add the features you want to inherit and define your own
version of those features yourself. If you do not include the TagModel
concern and you pick the modules
you want to add yourself, please note that the order of the included modules is important and that if you
do not include the modules in the order specified, some features may not perform as you expect in some
fringe cases.
Because the Tags tables and the Taggings tables refer to each other with the taggings
and tags
relationships
respectively, if you create a custom Taggings table you should create a custom Tags table as well. This means
that you will need to redefine the respective relationships in the two custom models.
NOTE: If you include the TaggingAssociations
module, do NOT include the counter_cache
option in the belongs_to
relationship for the tag
or the taggings_count
will be doubled. If you do not include that module, then you can
include it or not as you wish to get the count.
class CustomTagging
include ActsAsTaggableOnMongoid::Models::Concerns::TaggingModel
# Do NOT include `counter_cache` here
belongs_to :tag, inverse_of: :taggings
end
class CustomTagging
include ActsAsTaggableOnMongoid::Models::Concerns::TaggingFields
include ActsAsTaggableOnMongoid::Models::Concerns::TaggingMethods
# `counter_cache` is optional here.
# `remove_unused_tags` will not work properly if this is not included though.
belongs_to :tag, counter_cache: true, inverse_of: :taggings
belongs_to :taggable, polymorphic: true
include ActsAsTaggableOnMongoid::Models::Concerns::TaggingValidations
include ActsAsTaggableOnMongoid::Models::Concerns::TaggingScopes
end
Configurations set on the ActsAsTaggableOnMongoid
(also ActsAsTaggableOnMongoid.configuration
or
ActsAsTaggableOnMongoid.configure { |config| }
) set global defaults for these settings. Individual
tags can override the values to whatever they want. Custom contexts will be created using the configuration
defaults.
If you would like to remove unused tag objects after removing taggings, add:
ActsAsTaggableOnMongoid.remove_unused_tags = true
If you want force tags to be saved downcased:
ActsAsTaggableOnMongoid.force_lowercase = true
If you want tags to be saved parametrized (you can redefine to_param as well):
ActsAsTaggableOnMongoid.force_parameterize = true
If you would like tags to be case-sensitive and not use LIKE queries for creation:
ActsAsTaggableOnMongoid.tags_table = AatoTags
ActsAsTaggableOnMongoid.taggings_table = AatoTaggings
If you want to change the default delimiter (it defaults to ','). You can also pass in an array of delimiters such as ([',', '|']):
ActsAsTaggableOnMongoid::DefaultParser.delimiter = ','
After checking out the repo, run bin/setup
to install dependencies. Then, run rake spec
to run the tests. You can also run bin/console
for an interactive prompt that will allow you to experiment.
To install this gem onto your local machine, run bundle exec rake install
. To release a new version, update the version number in version.rb
, and then run bundle exec rake release
, which will create a git tag for the version, push git commits and tags, and push the .gem
file to rubygems.org.
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/acts-as-taggable-on-mongoid. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct.
The gem is available as open source under the terms of the MIT License.
Everyone interacting in the ActsAsTaggableOnMongoid project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.