1

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
manis
  • 731
  • 1
  • 13
  • 24

2 Answers2

1

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
Community
  • 1
  • 1
Nicos Karalis
  • 3,724
  • 4
  • 33
  • 62
1

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 
  • there is already a gem (or plugin perhaps) called ActsAsTaggable, which basically works in this way. But it's nicer to see the explanation rather than just get told to use the gem.

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
Max Williams
  • 32,435
  • 31
  • 130
  • 197
  • Excellent solution, but i would recommend `concerns` instead of modules, because they already to validation on class methods and includes, other than that great solution – Nicos Karalis Apr 07 '14 at 13:04
  • Yeah, i've not got into concerns yet, we're still kind of retro here :) – Max Williams Apr 07 '14 at 13:15
  • what does the :source do? – manis Apr 07 '14 at 13:20
  • :source is "the method to call in the join object to get the object at the far side of the join". Normally rails can guess what this is, but with a polymorphic association like this you need to tell it. NOTE: rails has made quite a few changes to do with polymorphic associations over the years, starting out with being dumb about them and growing cleverer and more convention-driven. So this may not work in your version. But you should be able to google it if not. – Max Williams Apr 07 '14 at 13:23
  • One thing I am missing with the polymorphic approach is that I am no longer able to call sometag.units, sometag.events to get all the records associated with that tag, is there a way around this? – manis Apr 08 '14 at 17:29