Module: ActiveRecord::Acts::Taxonomy::SingletonMethods

Includes:
GroupHelper
Defined in:
lib/taxonomy/has_taxonomy.rb

Instance Method Summary collapse

Methods included from GroupHelper

#grouped_column_names_for

Instance Method Details

#all_tag_counts(options = {}) ⇒ Object



178
179
180
# File 'lib/taxonomy/has_taxonomy.rb', line 178

def all_tag_counts(options = {})
  Tag.all(find_options_for_tag_counts(options))
end

#caching_tag_list_on?(context) ⇒ Boolean

Returns:

  • (Boolean)


166
167
168
# File 'lib/taxonomy/has_taxonomy.rb', line 166

def caching_tag_list_on?(context)
  column_names.include?("cached_#{context.to_s.singularize}_list")
end

#find_options_for_find_tagged_with(tags, options = {}) ⇒ Object



182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
# File 'lib/taxonomy/has_taxonomy.rb', line 182

def find_options_for_find_tagged_with(tags, options = {})
  tag_list = TagList.from(tags)

  return {} if tag_list.empty?

  joins = []
  conditions = []

  context = options.delete(:on)
  context = context.to_s.singularize

  if options.delete(:exclude)
    tags_conditions = tag_list.map { |t| sanitize_sql(["#{Tag.table_name}.name LIKE ?", t]) }.join(" OR ")
    conditions << "#{table_name}.#{primary_key} NOT IN (SELECT #{Tagging.table_name}.taggable_id FROM #{Tagging.table_name} JOIN #{Tag.table_name} ON #{Tagging.table_name}.tag_id = #{Tag.table_name}.id AND (#{tags_conditions}) WHERE #{Tagging.table_name}.taggable_type = #{quote_value(base_class.name)})"

  elsif options.delete(:any)
    tags_conditions = tag_list.map { |t| sanitize_sql(["#{Tag.table_name}.name LIKE ?", t]) }.join(" OR ")
    conditions << "#{table_name}.#{primary_key} IN (SELECT #{Tagging.table_name}.taggable_id FROM #{Tagging.table_name} JOIN #{Tag.table_name} ON #{Tagging.table_name}.tag_id = #{Tag.table_name}.id AND (#{tags_conditions}) WHERE #{Tagging.table_name}.taggable_type = #{quote_value(base_class.name)})"

  else
    tags = Tag.named_any(context, tag_list)
    return { :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 = "#{table_name}_taggings_#{prefix}"

      tagging_join  = "JOIN #{Tagging.table_name} #{taggings_alias}" +
                      "  ON #{taggings_alias}.taggable_id = #{table_name}.#{primary_key}" +
                      " AND #{taggings_alias}.taggable_type = #{quote_value(base_class.name)}" +
                      " AND #{taggings_alias}.tag_id = #{tag.id}"
      # tagging_join << " AND " + sanitize_sql(["tags.context = ?", context.to_s]) if context

      joins << tagging_join
    end
  end

  taggings_alias, tags_alias = "#{table_name}_taggings_group", "#{table_name}_tags_group"

  if options.delete(:match_all)
    joins << "LEFT OUTER JOIN #{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 = "#{grouped_column_names_for(self)} HAVING COUNT(#{taggings_alias}.taggable_id) = #{tags.size}"
  end

  { :joins      => joins.join(" "),
    :group      => group,
    :conditions => conditions.join(" AND "),
    :readonly   => false }.update(options)
end

#find_options_for_tag_counts(options = {}) ⇒ Object

Calculate the tag counts for all tags.

Options:

:start_at - Restrict the tags to those created after a certain time
:end_at - Restrict the tags to those created before a certain time
:conditions - A piece of SQL conditions to add to the query
:limit - The maximum number of tags to return
:order - A piece of SQL to order by. Eg 'tags.count desc' or 'taggings.created_at desc'
:at_least - Exclude tags with a frequency less than the given value
:at_most - Exclude tags with a frequency greater than the given value
:on - Scope the find to only include a certain context


248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
# File 'lib/taxonomy/has_taxonomy.rb', line 248

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

  # scope = scope(:find)

  start_at = sanitize_sql(["#{Tagging.table_name}.created_at >= ?", options.delete(:start_at)]) if options[:start_at]
  end_at = sanitize_sql(["#{Tagging.table_name}.created_at <= ?", options.delete(:end_at)]) if options[:end_at]

  taggable_type = sanitize_sql(["#{Tagging.table_name}.taggable_type = ?", base_class.name])
  taggable_id = sanitize_sql(["#{Tagging.table_name}.taggable_id = ?", options.delete(:id)]) if options[:id]
  options[:conditions] = sanitize_sql(options[:conditions]) if options[:conditions]

  conditions = [
    taggable_type,
    taggable_id,
    options[:conditions],
    start_at,
    end_at
  ]

  conditions = conditions.compact.join(' AND ')
  # conditions = merge_conditions(conditions, scope[:conditions]) if scope

  joins = ["LEFT OUTER JOIN #{Tagging.table_name} ON #{Tag.table_name}.id = #{Tagging.table_name}.tag_id"]
  joins << sanitize_sql(["AND #{Tag.table_name}.context = ?",options.delete(:on).to_s]) unless options[:on].nil?
  joins << " INNER JOIN #{table_name} ON #{table_name}.#{primary_key} = #{Tagging.table_name}.taggable_id"
  
  unless descends_from_active_record?
    # Current model is STI descendant, so add type checking to the join condition
    joins << " AND #{table_name}.#{inheritance_column} = '#{name}'"
  end

  # Based on a proposed patch by donV to ActiveRecord Base
  # This is needed because merge_joins and construct_join are private in ActiveRecord Base
  # if scope && scope[:joins]
  #   case scope[:joins]
  #   when Array
  #     scope_joins = scope[:joins].flatten
  #     strings = scope_joins.select{|j| j.is_a? String}
  #     joins << strings.join(' ') + " "
  #     symbols = scope_joins - strings
  #     join_dependency = ActiveRecord::Associations::ClassMethods::InnerJoinDependency.new(self, symbols, nil)
  #     joins << " #{join_dependency.join_associations.collect { |assoc| assoc.association_join }.join} "
  #     joins.flatten!
  #   when Symbol, Hash
  #     join_dependency = ActiveRecord::Associations::ClassMethods::InnerJoinDependency.new(self, scope[:joins], nil)
  #     joins << " #{join_dependency.join_associations.collect { |assoc| assoc.association_join }.join} "
  #   when String
  #     joins << scope[:joins]
  #   end
  # end

  at_least  = sanitize_sql(['COUNT(*) >= ?', options.delete(:at_least)]) if options[:at_least]
  at_most   = sanitize_sql(['COUNT(*) <= ?', options.delete(:at_most)]) if options[:at_most]
  having    = [at_least, at_most].compact.join(' AND ')
  if joins.include?(".context") # if :on is passed
    group_by  = "#{grouped_column_names_for(Tag)} HAVING COUNT(*) > 0"
  else
    group_by  = "tags.name HAVING COUNT(*) > 0"
  end
  group_by << " AND #{having}" unless having.blank?

  { :select     => "#{Tag.table_name}.*, COUNT(*) AS count",
    :joins      => joins.join(" "),
    :conditions => conditions,
    :group      => group_by,
    :limit      => options[:limit],
    :order      => options[:order]
  }
end

#find_tagged_with(context, name, *args) ⇒ Object

Pass either a tag string, or an array of strings or tags

Options:

:any - find models that match any of the given tags
:exclude - Find models that are not tagged with the given tags
:match_all - Find models that match all of the given tags, not just one
:conditions - A piece of SQL conditions to add to the query
:on - scopes the find to a context


154
155
156
157
158
159
160
161
162
163
164
# File 'lib/taxonomy/has_taxonomy.rb', line 154

def find_tagged_with(context, name, *args)
  find_opts = {:on => context}
  case args #fixme: args will probably always be an array
  when Array
    if args.first.is_a?(Hash)
      find_opts.merge!(args.first)
    end
  end
  options = find_options_for_find_tagged_with(name, find_opts)
  options.blank? ? [] : find(:all,options)
end

#is_taggable?Boolean

Returns:

  • (Boolean)


319
320
321
# File 'lib/taxonomy/has_taxonomy.rb', line 319

def is_taggable?
  true
end

#tag_counts_on(context, options = {}) ⇒ Object



174
175
176
# File 'lib/taxonomy/has_taxonomy.rb', line 174

def tag_counts_on(context, options = {})
  Tag.all(find_options_for_tag_counts(options.merge({:on => context.to_s.singularize})))
end

#treed_list_on?(context) ⇒ Boolean

Returns:

  • (Boolean)


170
171
172
# File 'lib/taxonomy/has_taxonomy.rb', line 170

def treed_list_on?(context)
  treed_tag_types.include?(context)
end