Module: MakeTaggable::Taggable::Collection::ClassMethods

Defined in:
lib/make_taggable/taggable/collection.rb

Overview

Added to every taggable model.

Instance Method Summary collapse

Instance Method Details

#all_tag_counts(options = {}) ⇒ ActiveRecord::Relation

Every tag applied to this model, each carrying how often it was used as a count attribute.

Examples:

The ten most used genres

Book.all_tag_counts(on: :genres, order: "count desc", limit: 10)

Parameters:

  • options (Hash) (defaults to: {})

    the query options

Options Hash (options):

  • :start_at (Time, Date)

    only tags applied after this time

  • :end_at (Time, Date)

    only tags applied before this time

  • :conditions (String, Array)

    SQL conditions added to the tag query

  • :limit (Integer)

    the most tags to return

  • :order (String)

    SQL to order by, such as "count desc"

  • :at_least (Integer)

    skip tags used fewer times than this

  • :at_most (Integer)

    skip tags used more times than this

  • :on (Symbol, String)

    only tags applied in this context

Returns:

  • (ActiveRecord::Relation)


144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
# File 'lib/make_taggable/taggable/collection.rb', line 144

def all_tag_counts(options = {})
  options = options.dup
  options.assert_valid_keys :start_at, :end_at, :conditions, :at_least, :at_most, :order, :limit, :on, :id

  ## Generate conditions:
  options[:conditions] = sanitize_sql(options[:conditions]) if options[:conditions]

  ## Generate scope:
  tagging_scope = MakeTaggable::Tagging.select(
    "#{MakeTaggable::Tagging.table_name}.tag_id, COUNT(#{MakeTaggable::Tagging.table_name}.tag_id) AS tags_count, #{last_applied_at_projection}"
  )
  tag_scope = MakeTaggable.tag_model.select("#{MakeTaggable::Tag.table_name}.*, #{MakeTaggable::Tagging.table_name}.tags_count AS count").order(options[:order]).limit(options[:limit])

  # Current model is STI descendant, so add type checking to the join condition
  unless descends_from_active_record?
    taggable_join = "INNER JOIN #{table_name} ON #{table_name}.#{primary_key} = #{MakeTaggable::Tagging.table_name}.taggable_id"
    taggable_join += sanitize_sql([" AND #{table_name}.#{inheritance_column} = ?", name])
    tagging_scope = tagging_scope.joins(taggable_join)
  end

  # Conditions
  tagging_conditions(options).each { |condition| tagging_scope = tagging_scope.where(condition) }
  tag_scope = tag_scope.where(options[:conditions])

  # GROUP BY and HAVING clauses:
  having = ["COUNT(#{MakeTaggable::Tagging.table_name}.tag_id) > 0"]
  having.push sanitize_sql(["COUNT(#{MakeTaggable::Tagging.table_name}.tag_id) >= ?", options.delete(:at_least)]) if options[:at_least]
  having.push sanitize_sql(["COUNT(#{MakeTaggable::Tagging.table_name}.tag_id) <= ?", options.delete(:at_most)]) if options[:at_most]
  having = having.compact.join(" AND ")

  group_columns = "#{MakeTaggable::Tagging.table_name}.tag_id"

  unless options[:id]
    # Append the current scope to the scope, because we can't use scope(:find) in RoR 3.0 anymore:
    tagging_scope = generate_tagging_scope_in_clause(tagging_scope, table_name, primary_key)
  end

  tagging_scope = tagging_scope.group(group_columns).having(having)

  tag_scope_joins(tag_scope, tagging_scope)
end

#all_tags(options = {}) ⇒ ActiveRecord::Relation

Every tag applied to this model, without counting them.

Cheaper than #all_tag_counts, which has to join the taggables.

Parameters:

  • options (Hash) (defaults to: {})

    the query options

Options Hash (options):

  • :start_at (Time, Date)

    only tags applied after this time

  • :end_at (Time, Date)

    only tags applied before this time

  • :conditions (String, Array)

    SQL conditions added to the tag query

  • :limit (Integer)

    the most tags to return

  • :order (String)

    SQL to order by, such as "tags.name asc"

  • :on (Symbol, String)

    only tags applied in this context

Returns:

  • (ActiveRecord::Relation)


102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/make_taggable/taggable/collection.rb', line 102

def all_tags(options = {})
  options = options.dup
  options.assert_valid_keys :start_at, :end_at, :conditions, :order, :limit, :on

  ## Generate conditions:
  options[:conditions] = sanitize_sql(options[:conditions]) if options[:conditions]

  ## Generate scope:
  tagging_scope = MakeTaggable::Tagging.select(
    "#{MakeTaggable::Tagging.table_name}.tag_id, #{last_applied_at_projection}"
  )
  tag_scope = MakeTaggable.tag_model.select("#{MakeTaggable::Tag.table_name}.*").order(options[:order]).limit(options[:limit])

  # Joins and conditions
  tagging_conditions(options).each { |condition| tagging_scope = tagging_scope.where(condition) }
  tag_scope = tag_scope.where(options[:conditions])

  group_columns = "#{MakeTaggable::Tagging.table_name}.tag_id"

  # Append the current scope to the scope, because we can't use scope(:find) in RoR 3.0 anymore:
  tagging_scope = generate_tagging_scope_in_clause(tagging_scope, table_name, primary_key).group(group_columns)

  tag_scope_joins(tag_scope, tagging_scope)
end

#initialize_make_taggable_collectionvoid

This method returns an undefined value.

Defines the counting methods for each context.



30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/make_taggable/taggable/collection.rb', line 30

def initialize_make_taggable_collection
  tag_types.map(&:to_s).each do |tag_type|
    class_eval <<-RUBY, __FILE__, __LINE__ + 1
      def self.#{tag_type.singularize}_counts(options={})
        tag_counts_on('#{tag_type}', options)
      end

      def #{tag_type.singularize}_counts(options = {})
        tag_counts_on('#{tag_type}', options)
      end

      def top_#{tag_type}(limit = 10)
        tag_counts_on('#{tag_type}', order: 'count desc', limit: limit.to_i)
      end

      def self.top_#{tag_type}(limit = 10)
        tag_counts_on('#{tag_type}', order: 'count desc', limit: limit.to_i)
      end
    RUBY
  end
end

#make_taggable(*args) ⇒ void

This method returns an undefined value.

Adds contexts and refreshes the counting methods.

Parameters:

  • args (Array<Symbol, String>)

    the contexts to add



58
59
60
61
# File 'lib/make_taggable/taggable/collection.rb', line 58

def make_taggable(*args)
  super
  initialize_make_taggable_collection
end

#safe_to_sql(relation) ⇒ String

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

A relation's SQL with its bind parameters inlined, so it can be embedded in another query.

Parameters:

  • relation (ActiveRecord::Relation)

    the relation to render

Returns:

  • (String)


194
195
196
# File 'lib/make_taggable/taggable/collection.rb', line 194

def safe_to_sql(relation)
  connection.unprepared_statement { relation.to_sql }
end

#tag_counts_on(context, options = {}) ⇒ ActiveRecord::Relation

Tags used in one context, each carrying how often it was used.

Examples:

Book.tag_counts_on(:genres)

Parameters:

  • context (Symbol, String)

    the tagging context

  • options (Hash) (defaults to: {})

    options accepted by #all_tag_counts

Returns:

  • (ActiveRecord::Relation)


73
74
75
# File 'lib/make_taggable/taggable/collection.rb', line 73

def tag_counts_on(context, options = {})
  all_tag_counts(options.merge({on: context.to_s}))
end

#tags_on(context, options = {}) ⇒ ActiveRecord::Relation

Tags used in one context, without counting them.

Parameters:

  • context (Symbol, String)

    the tagging context

  • options (Hash) (defaults to: {})

    options accepted by #all_tags

Returns:

  • (ActiveRecord::Relation)


84
85
86
# File 'lib/make_taggable/taggable/collection.rb', line 84

def tags_on(context, options = {})
  all_tags(options.merge({on: context.to_s}))
end