Reputation: 729
I have implemented a tagging system for the models Unit, Group and Event, and currently, each one have their own instance of the methods add_tags and self.tagged_with.
def add_tags(options=[])
transaction do
options.each do |tag|
self.tags << Tag.find_by(name: tag)
end
end
end
and
def self.tagged_with(tags=[])
units = Unit.all
tags.each do |tag|
units = units & Tag.find_by_name(tag).units
end
units
end
end
I want to move these into a module and include them in the model, but as you can see, the tagged_with method is not polymorphic, as I don't know how I would refer the parenting class (Unit, Group etc.) and called methods like "all" on them. Any advice?
Tag model:
Class Tag < ActiveRecord::Base
has_and_belongs_to_many: units, :join_table => :unit_taggings
has_and_belongs_to_many: groups, :join_table => :group_taggings
has_and_belongs_to_many: events, :join_table => :event_taggings
end
Upvotes: 1
Views: 145
Reputation: 32933
I would do it like so:
#table: taggings, fields: tag_id, taggable type (string), taggable id
class Tagging
belongs_to :tag
belongs_to :taggable, :polymorphic => true
Now make a module in lib - let's call it "ActsAsTaggable"*
module ActsAsTaggable
def self.included(base)
base.extend(ClassMethods)
base.class_eval do
#common associations, callbacks, validations etc go here
has_many :taggings, :as => :taggable, :dependent => :destroy
has_many :tags, :through => :taggings
end
end
#instance methods can be defined in the normal way
#class methods go in here
module ClassMethods
end
end
Now you can do this in any class you want to make taggable
include ActsAsTaggable
EDIT: here's the code you need to set up the association at the Tag end: note the source option.
class Tag
has_many :taggings
has_many :taggables, :through => :taggings, :source => :taggable
Upvotes: 1
Reputation: 3773
You could call self.class
to get the current class, like this:
def self.tagged_with(tags=[])
klass = self.class
units = klass.all
tags.each do |tag|
units = units & Tag.find_by_name(tag).units
end
units
end
end
self.class
should return Unit
or any class, calling any method on a class object (self.class.tagged_with
) is the same as Unit.tagged_with
I would recommend that you use Concerns
, take a look here
EDIT Answer to your comment
Using concerns you could do something like this, each class have that methods you mentioned before, but you dont have to rewrite all that code on every class (or file):
# app/models/concerns/taggable.rb
module Taggable
extend ActiveSupport::Concern
module ClassMethods
def self.tagged_with(tags=[])
klass = self.class
units = klass.all
tags.each do |tag|
units = units & Tag.find_by_name(tag).units
end
units
end
end
end
end
# app/models/unit.rb
class Unit
include Taggable
...
end
# app/models/group.rb
class Group
include Taggable
...
end
# app/models/event.rb
class Event
include Taggable
...
end
Upvotes: 1