module SemanticallyTaggable::Taggable::Core::ClassMethods

Public Instance Methods

grouped_column_names_for(object) click to toggle source

all column names are necessary for PostgreSQL group clause

# File lib/semantically_taggable/semantically_taggable/core.rb, line 56
def grouped_column_names_for(object)
  object.column_names.map { |column| "#{object.table_name}.#{column}" }.join(", ")
end
initialize_semantically_taggable_core() click to toggle source
# File lib/semantically_taggable/semantically_taggable/core.rb, line 16
def initialize_semantically_taggable_core
  scheme_names.map(&:to_s).each do |scheme_name|
    scheme = SemanticallyTaggable::Scheme.by_name(scheme_name) rescue SemanticallyTaggable::Scheme.new(
        :name => 'tmp', :id => 'initialization_only')

    singular_scheme_name  = scheme_name.to_s.singularize
    scheme_taggings      = "#{singular_scheme_name}_taggings".to_sym
    scheme_tags          = scheme_name.to_sym

    class_eval do
      has_many scheme_taggings, :as => :taggable, :dependent => :destroy, :include => :tag, :class_name => "SemanticallyTaggable::Tagging"

      # TODO: revisit with dynamic join? Something not right about remembering scheme IDs at startup...
      has_many scheme_tags,
               :through => scheme_taggings, :source => :tag, :class_name => "SemanticallyTaggable::Tag",
               :conditions => "tags.scheme_id = #{scheme.id}"
    end

    class_eval %(
      def #{singular_scheme_name}_list
        tag_list_on('#{scheme_name}')
      end

      def #{singular_scheme_name}_list=(new_tags)
        set_tag_list_on('#{scheme_name}', new_tags)
      end

      def all_#{scheme_name}_list
        all_tags_list_on('#{scheme_name}')
      end
    )
  end
end
is_taggable?() click to toggle source
# File lib/semantically_taggable/semantically_taggable/core.rb, line 157
def is_taggable?
  true
end
scheme_taggings_for_these_tags(scheme, tag_list) click to toggle source
# File lib/semantically_taggable/semantically_taggable/core.rb, line 74
def scheme_taggings_for_these_tags(scheme, tag_list)
  "SELECT taggings.taggable_id FROM taggings
   JOIN tags ON
      taggings.tag_id = tags.id AND
      taggings.taggable_type = #{quote_value(base_class.name)} AND
      #{scheme_tags_like_any_of_these(scheme, tag_list)}"
end
scheme_tags_like_any_of_these(scheme, tag_list) click to toggle source
# File lib/semantically_taggable/semantically_taggable/core.rb, line 60
def scheme_tags_like_any_of_these(scheme, tag_list)
  if scheme.polyhierarchical
    tags = scheme.tags.named_any(tag_list)
    return "1 = 0" if tags.empty?
    "tags.id IN (#{tags.map {
        |t| "SELECT #{t.id} UNION SELECT child_tag_id FROM tag_parentages WHERE parent_tag_id = #{t.id}"
      }.join(" UNION ")
    })"
  else
    "(#{tag_list.map { |t| sanitize_sql(["tags.name LIKE ?", t]) }.join(" OR ")})"\
        + " AND tags.scheme_id = #{scheme.id}"
  end
end
semantically_taggable(*args) click to toggle source
Calls superclass method
# File lib/semantically_taggable/semantically_taggable/core.rb, line 50
def semantically_taggable(*args)
  super(*args)
  initialize_semantically_taggable_core
end
tagged_with(tags, options = {}) click to toggle source

Return a scope of objects that are tagged with the specified tags.

@param tags The tags that we want to query for @param [Hash] options A hash of options to alter you query:

* <tt>:exclude</tt> - if set to true, return objects that are *NOT* tagged with the specified tags
* <tt>:any</tt> - if set to true, return objects that are tagged with *ANY* of the specified tags
* <tt>:match_all</tt> - if set to true, return objects that are *ONLY* tagged with the specified tags

Example:

User.tagged_with("awesome", "cool")                     # Users that are tagged with awesome and cool
User.tagged_with("awesome", "cool", :exclude => true)   # Users that are not tagged with awesome or cool
User.tagged_with("awesome", "cool", :any => true)       # Users that are tagged with awesome or cool
User.tagged_with("awesome", "cool", :match_all => true) # Users that are tagged with just awesome and cool
# File lib/semantically_taggable/semantically_taggable/core.rb, line 96
def tagged_with(tags, options = {})
  scheme_name = options.delete(:on)
  raise ArgumentError, 'tagged_with requires :on' unless scheme_name

  scheme = SemanticallyTaggable::Scheme.by_name(scheme_name)
  tag_list = SemanticallyTaggable::TagList.from(tags)

  return {} if tag_list.empty?

  joins = []
  conditions = []

  if options.delete(:exclude)
    conditions << %{
      #{table_name}.#{primary_key} NOT IN (#{scheme_taggings_for_these_tags(scheme, tag_list)})
    }
  elsif options.delete(:any)
    conditions << %{
      #{table_name}.#{primary_key} IN (#{scheme_taggings_for_these_tags(scheme, tag_list)})
    }
  else
    tags = scheme.tags.named_any(tag_list)
    return scoped(:conditions => "1 = 0") unless tags.length == tag_list.length

    tags.each do |tag|
      safe_tag = tag.name.gsub(/[^a-zA-Z0-9]/, '')
      prefix   = "#{safe_tag}_#{rand(1024)}"

      taggings_alias = "#{undecorated_table_name}_taggings_#{prefix}"

      tagging_join  = "JOIN #{SemanticallyTaggable::Tagging.table_name} #{taggings_alias}" +
                      "  ON #{taggings_alias}.taggable_id = #{table_name}.#{primary_key}" +
                      " AND #{taggings_alias}.taggable_type = #{quote_value(base_class.name)}" +
                      (scheme.polyhierarchical ?
                        " AND #{taggings_alias}.tag_id IN (SELECT #{tag.id} UNION SELECT child_tag_id FROM tag_parentages WHERE parent_tag_id = #{tag.id})" :
                        " AND #{taggings_alias}.tag_id = #{tag.id}")

      joins << tagging_join
    end
  end

  taggings_alias = "#{undecorated_table_name}_taggings_group"

  if options.delete(:match_all)
    joins << "LEFT OUTER JOIN #{SemanticallyTaggable::Tagging.table_name} #{taggings_alias}" +
             "  ON #{taggings_alias}.taggable_id = #{table_name}.#{primary_key}" +
             " AND #{taggings_alias}.taggable_type = #{quote_value(base_class.name)}"


    group_columns = SemanticallyTaggable::Tag.using_postgresql? ? grouped_column_names_for(self) : "#{table_name}.#{primary_key}"
    group = "#{group_columns} HAVING COUNT(#{taggings_alias}.taggable_id) = #{tags.size}"
  end


  scoped(:joins      => joins.join(" "),
         :group      => group,
         :conditions => conditions.join(" AND "),
         :order      => options[:order],
         :readonly   => false)
end