module ActsAsTaggableOn::Taggable::Core

Public Class Methods

included(base) click to toggle source
# File lib/acts_as_taggable_on/taggable/core.rb, line 3
def self.included(base)
  base.extend ActsAsTaggableOn::Taggable::Core::ClassMethods

  base.class_eval do
    attr_writer :custom_contexts
    after_save :save_tags
  end

  base.initialize_acts_as_taggable_on_core
end

Public Instance Methods

add_custom_context(value) click to toggle source
# File lib/acts_as_taggable_on/taggable/core.rb, line 269
def add_custom_context(value)
  custom_contexts << value.to_s unless custom_contexts.include?(value.to_s) or self.class.tag_types.map(&:to_s).include?(value.to_s)
end
all_tags_list_on(context) click to toggle source
# File lib/acts_as_taggable_on/taggable/core.rb, line 298
def all_tags_list_on(context)
  variable_name = "@all_#{context.to_s.singularize}_list"
  return instance_variable_get(variable_name) if instance_variable_defined?(variable_name) && instance_variable_get(variable_name)

  instance_variable_set(variable_name, ActsAsTaggableOn::TagList.new(all_tags_on(context).map(&:name)).freeze)
end
all_tags_on(context) click to toggle source

Returns all tags of a given context

# File lib/acts_as_taggable_on/taggable/core.rb, line 307
def all_tags_on(context)
  tagging_table_name = ActsAsTaggableOn::Tagging.table_name

  opts = ["#{tagging_table_name}.context = ?", context.to_s]
  scope = base_tags.where(opts)

  if ActsAsTaggableOn::Utils.using_postgresql?
    group_columns = grouped_column_names_for(ActsAsTaggableOn::Tag)
    scope.order("max(#{tagging_table_name}.created_at)").group(group_columns)
  else
    scope.group("#{ActsAsTaggableOn::Tag.table_name}.#{ActsAsTaggableOn::Tag.primary_key}")
  end.to_a
end
cached_tag_list_on(context) click to toggle source
# File lib/acts_as_taggable_on/taggable/core.rb, line 273
def cached_tag_list_on(context)
  self["cached_#{context.to_s.singularize}_list"]
end
custom_contexts() click to toggle source
# File lib/acts_as_taggable_on/taggable/core.rb, line 261
def custom_contexts
  @custom_contexts ||= taggings.map(&:context).uniq
end
grouped_column_names_for(object) click to toggle source

all column names are necessary for PostgreSQL group clause

# File lib/acts_as_taggable_on/taggable/core.rb, line 257
def grouped_column_names_for(object)
  self.class.grouped_column_names_for(object)
end
is_taggable?() click to toggle source
# File lib/acts_as_taggable_on/taggable/core.rb, line 265
def is_taggable?
  self.class.is_taggable?
end
load_tags(tag_list) click to toggle source

Find existing tags or create non-existing tags

# File lib/acts_as_taggable_on/taggable/core.rb, line 373
def load_tags(tag_list)
  ActsAsTaggableOn::Tag.find_or_create_all_with_like_by_name(tag_list)
end
process_dirty_object(context, new_list) click to toggle source
# File lib/acts_as_taggable_on/taggable/core.rb, line 344
def process_dirty_object(context, new_list)
  value = new_list.is_a?(Array) ? ActsAsTaggableOn::TagList.new(new_list) : new_list
  attrib = "#{context.to_s.singularize}_list"

  if changed_attributes.include?(attrib)
    # The attribute already has an unsaved change.
    old = changed_attributes[attrib]
    @changed_attributes.delete(attrib) if old.to_s == value.to_s
  else
    old = tag_list_on(context)
    if self.class.preserve_tag_order
      @changed_attributes[attrib] = old if old.to_s != value.to_s
    else
      @changed_attributes[attrib] = old.to_s if old.sort != ActsAsTaggableOn.default_parser.new(value).parse.sort
    end
  end
end
reload(*args) click to toggle source
Calls superclass method
# File lib/acts_as_taggable_on/taggable/core.rb, line 362
def reload(*args)
  self.class.tag_types.each do |context|
    instance_variable_set("@#{context.to_s.singularize}_list", nil)
    instance_variable_set("@all_#{context.to_s.singularize}_list", nil)
  end

  super(*args)
end
save_tags() click to toggle source
# File lib/acts_as_taggable_on/taggable/core.rb, line 377
def save_tags
  tagging_contexts.each do |context|
    next unless tag_list_cache_set_on(context)
    # List of currently assigned tag names
    tag_list = tag_list_cache_on(context).uniq

    # Find existing tags or create non-existing tags:
    tags = find_or_create_tags_from_list_with_context(tag_list, context)

    # Tag objects for currently assigned tags
    current_tags = tags_on(context)

    # Tag maintenance based on whether preserving the created order of tags
    if self.class.preserve_tag_order?
      old_tags, new_tags = current_tags - tags, tags - current_tags

      shared_tags = current_tags & tags

      if shared_tags.any? && tags[0...shared_tags.size] != shared_tags
        index = shared_tags.each_with_index { |_, i| break i unless shared_tags[i] == tags[i] }

        # Update arrays of tag objects
        old_tags |= current_tags[index...current_tags.size]
        new_tags |= current_tags[index...current_tags.size] & shared_tags

        # Order the array of tag objects to match the tag list
        new_tags = tags.map do |t|
          new_tags.find { |n| n.name.downcase == t.name.downcase }
        end.compact
      end
    else
      # Delete discarded tags and create new tags
      old_tags = current_tags - tags
      new_tags = tags - current_tags
    end

    # Destroy old taggings:
    if old_tags.present?
      taggings.not_owned.by_context(context).where(tag_id: old_tags).destroy_all
    end

    # Create new taggings:
    new_tags.each do |tag|
      taggings.create!(tag_id: tag.id, context: context.to_s, taggable: self)
    end
  end

  true
end
set_tag_list_on(context, new_list) click to toggle source
# File lib/acts_as_taggable_on/taggable/core.rb, line 331
def set_tag_list_on(context, new_list)
  add_custom_context(context)

  variable_name = "@#{context.to_s.singularize}_list"
  process_dirty_object(context, new_list) unless custom_contexts.include?(context.to_s)

  instance_variable_set(variable_name, ActsAsTaggableOn.default_parser.new(new_list).parse)
end
tag_list_cache_on(context) click to toggle source
# File lib/acts_as_taggable_on/taggable/core.rb, line 282
def tag_list_cache_on(context)
  variable_name = "@#{context.to_s.singularize}_list"
  if instance_variable_get(variable_name)
    instance_variable_get(variable_name)
  elsif cached_tag_list_on(context) && self.class.caching_tag_list_on?(context)
    instance_variable_set(variable_name, ActsAsTaggableOn.default_parser.new(cached_tag_list_on(context)).parse)
  else
    instance_variable_set(variable_name, ActsAsTaggableOn::TagList.new(tags_on(context).map(&:name)))
  end
end
tag_list_cache_set_on(context) click to toggle source
# File lib/acts_as_taggable_on/taggable/core.rb, line 277
def tag_list_cache_set_on(context)
  variable_name = "@#{context.to_s.singularize}_list"
  instance_variable_defined?(variable_name) && instance_variable_get(variable_name)
end
tag_list_on(context) click to toggle source
# File lib/acts_as_taggable_on/taggable/core.rb, line 293
def tag_list_on(context)
  add_custom_context(context)
  tag_list_cache_on(context)
end
tagging_contexts() click to toggle source
# File lib/acts_as_taggable_on/taggable/core.rb, line 340
def tagging_contexts
  self.class.tag_types.map(&:to_s) + custom_contexts
end
tags_on(context) click to toggle source

Returns all tags that are not owned of a given context

# File lib/acts_as_taggable_on/taggable/core.rb, line 323
def tags_on(context)
  scope = base_tags.where(["#{ActsAsTaggableOn::Tagging.table_name}.context = ? AND #{ActsAsTaggableOn::Tagging.table_name}.tagger_id IS NULL", context.to_s])
  # when preserving tag order, return tags in created order
  # if we added the order to the association this would always apply
  scope = scope.order("#{ActsAsTaggableOn::Tagging.table_name}.id") if self.class.preserve_tag_order?
  scope
end

Private Instance Methods

attributes_for_create(attribute_names) click to toggle source

Filters the tag lists from the attribute names.

Calls superclass method
# File lib/acts_as_taggable_on/taggable/core.rb, line 436
def attributes_for_create(attribute_names)
  tag_lists = tag_types.map {|tags_type| "#{tags_type.to_s.singularize}_list"}
  super.delete_if {|attr| tag_lists.include? attr }
end
attributes_for_update(attribute_names) click to toggle source

Filters the tag lists from the attribute names.

Calls superclass method
# File lib/acts_as_taggable_on/taggable/core.rb, line 430
def attributes_for_update(attribute_names)
  tag_lists = tag_types.map {|tags_type| "#{tags_type.to_s.singularize}_list"}
  super.delete_if {|attr| tag_lists.include? attr }
end
find_or_create_tags_from_list_with_context(tag_list, _context) click to toggle source

Override this hook if you wish to subclass {ActsAsTaggableOn::Tag} – context is provided so that you may conditionally use a Tag subclass only for some contexts.

@example Custom Tag class for one context

class Company < ActiveRecord::Base
  acts_as_taggable_on :markets, :locations

  def find_or_create_tags_from_list_with_context(tag_list, context)
    if context.to_sym == :markets
      MarketTag.find_or_create_all_with_like_by_name(tag_list)
    else
      super
    end
  end

@param [Array<String>] tag_list Tags to find or create @param [Symbol] context The tag context for the tag_list

# File lib/acts_as_taggable_on/taggable/core.rb, line 460
def find_or_create_tags_from_list_with_context(tag_list, _context)
  load_tags(tag_list)
end