acts-as-taggable-on 2.0.6 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/.rspec +2 -0
- data/.travis.yml +18 -0
- data/Appraisals +11 -0
- data/CHANGELOG.md +47 -0
- data/Gemfile +10 -9
- data/Guardfile +5 -0
- data/{MIT-LICENSE → LICENSE.md} +1 -1
- data/README.md +313 -0
- data/Rakefile +21 -49
- data/UPGRADING +7 -0
- data/acts-as-taggable-on.gemspec +35 -0
- data/{lib/generators/acts_as_taggable_on/migration/templates/active_record/migration.rb → db/migrate/1_acts_as_taggable_on_migration.rb} +3 -1
- data/db/migrate/2_add_missing_unique_indices.rb +21 -0
- data/gemfiles/rails_3.2.gemfile +7 -0
- data/gemfiles/rails_4.0.gemfile +7 -0
- data/gemfiles/rails_4.1.gemfile +7 -0
- data/lib/acts-as-taggable-on.rb +44 -13
- data/lib/acts_as_taggable_on/acts_as_taggable_on/cache.rb +52 -23
- data/lib/acts_as_taggable_on/acts_as_taggable_on/collection.rb +111 -49
- data/lib/acts_as_taggable_on/acts_as_taggable_on/compatibility.rb +34 -0
- data/lib/acts_as_taggable_on/acts_as_taggable_on/core.rb +208 -55
- data/lib/acts_as_taggable_on/acts_as_taggable_on/dirty.rb +37 -0
- data/lib/acts_as_taggable_on/acts_as_taggable_on/ownership.rb +51 -17
- data/lib/acts_as_taggable_on/acts_as_taggable_on/related.rb +41 -22
- data/lib/acts_as_taggable_on/engine.rb +6 -0
- data/lib/acts_as_taggable_on/tag.rb +65 -20
- data/lib/acts_as_taggable_on/tag_list.rb +19 -14
- data/lib/acts_as_taggable_on/taggable.rb +105 -0
- data/lib/acts_as_taggable_on/{acts_as_tagger.rb → tagger.rb} +15 -6
- data/lib/acts_as_taggable_on/tagging.rb +14 -4
- data/lib/acts_as_taggable_on/tags_helper.rb +3 -5
- data/lib/acts_as_taggable_on/utils.rb +34 -0
- data/lib/acts_as_taggable_on/version.rb +4 -0
- data/lib/acts_as_taggable_on.rb +6 -0
- data/spec/acts_as_taggable_on/acts_as_taggable_on_spec.rb +137 -140
- data/spec/acts_as_taggable_on/acts_as_tagger_spec.rb +27 -27
- data/spec/acts_as_taggable_on/caching_spec.rb +77 -0
- data/spec/acts_as_taggable_on/related_spec.rb +143 -0
- data/spec/acts_as_taggable_on/single_table_inheritance_spec.rb +187 -0
- data/spec/acts_as_taggable_on/tag_list_spec.rb +118 -62
- data/spec/acts_as_taggable_on/tag_spec.rb +110 -14
- data/spec/acts_as_taggable_on/taggable_spec.rb +382 -75
- data/spec/acts_as_taggable_on/tagger_spec.rb +67 -21
- data/spec/acts_as_taggable_on/tagging_spec.rb +3 -6
- data/spec/acts_as_taggable_on/tags_helper_spec.rb +24 -8
- data/spec/acts_as_taggable_on/utils_spec.rb +21 -0
- data/spec/database.yml.sample +4 -2
- data/spec/models.rb +29 -2
- data/spec/schema.rb +40 -18
- data/spec/spec_helper.rb +40 -30
- metadata +177 -62
- data/CHANGELOG +0 -25
- data/README.rdoc +0 -221
- data/VERSION +0 -1
- data/generators/acts_as_taggable_on_migration/acts_as_taggable_on_migration_generator.rb +0 -7
- data/generators/acts_as_taggable_on_migration/templates/migration.rb +0 -29
- data/lib/acts_as_taggable_on/acts_as_taggable_on.rb +0 -53
- data/lib/acts_as_taggable_on/compatibility/Gemfile +0 -8
- data/lib/acts_as_taggable_on/compatibility/active_record_backports.rb +0 -17
- data/lib/acts_as_taggable_on/compatibility/postgresql.rb +0 -44
- data/lib/generators/acts_as_taggable_on/migration/migration_generator.rb +0 -32
- data/rails/init.rb +0 -1
- data/spec/database.yml +0 -17
data/lib/acts-as-taggable-on.rb
CHANGED
|
@@ -1,30 +1,61 @@
|
|
|
1
1
|
require "active_record"
|
|
2
|
+
require "active_record/version"
|
|
3
|
+
require "active_support/core_ext/module"
|
|
2
4
|
require "action_view"
|
|
5
|
+
require 'active_support/all'
|
|
3
6
|
|
|
4
|
-
|
|
7
|
+
require "digest/sha1"
|
|
5
8
|
|
|
6
|
-
|
|
9
|
+
module ActsAsTaggableOn
|
|
10
|
+
mattr_accessor :delimiter
|
|
11
|
+
@@delimiter = ','
|
|
7
12
|
|
|
8
|
-
|
|
13
|
+
mattr_accessor :force_lowercase
|
|
14
|
+
@@force_lowercase = false
|
|
15
|
+
|
|
16
|
+
mattr_accessor :force_parameterize
|
|
17
|
+
@@force_parameterize = false
|
|
18
|
+
|
|
19
|
+
mattr_accessor :strict_case_match
|
|
20
|
+
@@strict_case_match = false
|
|
21
|
+
|
|
22
|
+
mattr_accessor :remove_unused_tags
|
|
23
|
+
self.remove_unused_tags = false
|
|
24
|
+
|
|
25
|
+
def self.glue
|
|
26
|
+
delimiter = @@delimiter.kind_of?(Array) ? @@delimiter[0] : @@delimiter
|
|
27
|
+
delimiter.ends_with?(" ") ? delimiter : "#{delimiter} "
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.setup
|
|
31
|
+
yield self
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
require "acts_as_taggable_on/utils"
|
|
37
|
+
|
|
38
|
+
require "acts_as_taggable_on/taggable"
|
|
39
|
+
require "acts_as_taggable_on/acts_as_taggable_on/compatibility"
|
|
9
40
|
require "acts_as_taggable_on/acts_as_taggable_on/core"
|
|
10
41
|
require "acts_as_taggable_on/acts_as_taggable_on/collection"
|
|
11
42
|
require "acts_as_taggable_on/acts_as_taggable_on/cache"
|
|
12
43
|
require "acts_as_taggable_on/acts_as_taggable_on/ownership"
|
|
13
44
|
require "acts_as_taggable_on/acts_as_taggable_on/related"
|
|
45
|
+
require "acts_as_taggable_on/acts_as_taggable_on/dirty"
|
|
14
46
|
|
|
15
|
-
require "acts_as_taggable_on/
|
|
47
|
+
require "acts_as_taggable_on/tagger"
|
|
16
48
|
require "acts_as_taggable_on/tag"
|
|
17
49
|
require "acts_as_taggable_on/tag_list"
|
|
18
50
|
require "acts_as_taggable_on/tags_helper"
|
|
19
51
|
require "acts_as_taggable_on/tagging"
|
|
52
|
+
require 'acts_as_taggable_on/engine'
|
|
20
53
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
54
|
+
ActiveSupport.on_load(:active_record) do
|
|
55
|
+
extend ActsAsTaggableOn::Compatibility
|
|
56
|
+
extend ActsAsTaggableOn::Taggable
|
|
57
|
+
include ActsAsTaggableOn::Tagger
|
|
58
|
+
end
|
|
59
|
+
ActiveSupport.on_load(:action_view) do
|
|
60
|
+
include ActsAsTaggableOn::TagsHelper
|
|
26
61
|
end
|
|
27
|
-
|
|
28
|
-
if defined?(ActionView::Base)
|
|
29
|
-
ActionView::Base.send :include, ActsAsTaggableOn::TagsHelper
|
|
30
|
-
end
|
|
@@ -1,53 +1,82 @@
|
|
|
1
1
|
module ActsAsTaggableOn::Taggable
|
|
2
2
|
module Cache
|
|
3
3
|
def self.included(base)
|
|
4
|
-
#
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
4
|
+
# When included, conditionally adds tag caching methods when the model
|
|
5
|
+
# has any "cached_#{tag_type}_list" column
|
|
6
|
+
base.instance_eval do
|
|
7
|
+
# @private
|
|
8
|
+
def _has_tags_cache_columns?(db_columns)
|
|
9
|
+
db_column_names = db_columns.map(&:name)
|
|
10
|
+
tag_types.any? {|context|
|
|
11
|
+
db_column_names.include?("cached_#{context.to_s.singularize}_list")
|
|
12
|
+
}
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# @private
|
|
16
|
+
def _add_tags_caching_methods
|
|
17
|
+
send :include, ActsAsTaggableOn::Taggable::Cache::InstanceMethods
|
|
18
|
+
extend ActsAsTaggableOn::Taggable::Cache::ClassMethods
|
|
19
|
+
|
|
20
|
+
before_save :save_cached_tag_list
|
|
21
|
+
|
|
22
|
+
initialize_tags_cache
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# ActiveRecord::Base.columns makes a database connection and caches the calculated
|
|
26
|
+
# columns hash for the record as @columns. Since we don't want to add caching
|
|
27
|
+
# methods until we confirm the presence of a caching column, and we don't
|
|
28
|
+
# want to force opening a database connection when the class is loaded,
|
|
29
|
+
# here we intercept and cache the call to :columns as @acts_as_taggable_on_columns
|
|
30
|
+
# to mimic the underlying behavior. While processing this first call to columns,
|
|
31
|
+
# we do the caching column check and dynamically add the class and instance methods
|
|
32
|
+
def columns
|
|
33
|
+
@acts_as_taggable_on_columns ||= begin
|
|
34
|
+
db_columns = super
|
|
35
|
+
if _has_tags_cache_columns?(db_columns)
|
|
36
|
+
_add_tags_caching_methods
|
|
37
|
+
end
|
|
38
|
+
db_columns
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
12
42
|
end
|
|
13
|
-
|
|
14
|
-
base.intialize_acts_as_taggable_on_cache
|
|
15
43
|
end
|
|
16
|
-
|
|
44
|
+
|
|
17
45
|
module ClassMethods
|
|
18
|
-
def
|
|
46
|
+
def initialize_tags_cache
|
|
19
47
|
tag_types.map(&:to_s).each do |tag_type|
|
|
20
|
-
class_eval
|
|
48
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
21
49
|
def self.caching_#{tag_type.singularize}_list?
|
|
22
50
|
caching_tag_list_on?("#{tag_type}")
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
end
|
|
51
|
+
end
|
|
52
|
+
RUBY
|
|
53
|
+
end
|
|
26
54
|
end
|
|
27
|
-
|
|
55
|
+
|
|
28
56
|
def acts_as_taggable_on(*args)
|
|
29
57
|
super(*args)
|
|
30
|
-
|
|
58
|
+
initialize_tags_cache
|
|
31
59
|
end
|
|
32
|
-
|
|
60
|
+
|
|
33
61
|
def caching_tag_list_on?(context)
|
|
34
62
|
column_names.include?("cached_#{context.to_s.singularize}_list")
|
|
35
63
|
end
|
|
36
64
|
end
|
|
37
|
-
|
|
38
|
-
module InstanceMethods
|
|
65
|
+
|
|
66
|
+
module InstanceMethods
|
|
39
67
|
def save_cached_tag_list
|
|
40
68
|
tag_types.map(&:to_s).each do |tag_type|
|
|
41
69
|
if self.class.send("caching_#{tag_type.singularize}_list?")
|
|
42
70
|
if tag_list_cache_set_on(tag_type)
|
|
43
|
-
list = tag_list_cache_on(tag_type
|
|
71
|
+
list = tag_list_cache_on(tag_type).to_a.flatten.compact.join(', ')
|
|
44
72
|
self["cached_#{tag_type.singularize}_list"] = list
|
|
45
73
|
end
|
|
46
74
|
end
|
|
47
75
|
end
|
|
48
|
-
|
|
76
|
+
|
|
49
77
|
true
|
|
50
78
|
end
|
|
51
79
|
end
|
|
80
|
+
|
|
52
81
|
end
|
|
53
82
|
end
|
|
@@ -5,11 +5,11 @@ module ActsAsTaggableOn::Taggable
|
|
|
5
5
|
base.extend ActsAsTaggableOn::Taggable::Collection::ClassMethods
|
|
6
6
|
base.initialize_acts_as_taggable_on_collection
|
|
7
7
|
end
|
|
8
|
-
|
|
8
|
+
|
|
9
9
|
module ClassMethods
|
|
10
10
|
def initialize_acts_as_taggable_on_collection
|
|
11
11
|
tag_types.map(&:to_s).each do |tag_type|
|
|
12
|
-
class_eval
|
|
12
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
13
13
|
def self.#{tag_type.singularize}_counts(options={})
|
|
14
14
|
tag_counts_on('#{tag_type}', options)
|
|
15
15
|
end
|
|
@@ -24,20 +24,75 @@ module ActsAsTaggableOn::Taggable
|
|
|
24
24
|
|
|
25
25
|
def self.top_#{tag_type}(limit = 10)
|
|
26
26
|
tag_counts_on('#{tag_type}', :order => 'count desc', :limit => limit.to_i)
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
end
|
|
27
|
+
end
|
|
28
|
+
RUBY
|
|
29
|
+
end
|
|
30
30
|
end
|
|
31
|
-
|
|
31
|
+
|
|
32
32
|
def acts_as_taggable_on(*args)
|
|
33
33
|
super(*args)
|
|
34
34
|
initialize_acts_as_taggable_on_collection
|
|
35
35
|
end
|
|
36
|
-
|
|
36
|
+
|
|
37
37
|
def tag_counts_on(context, options = {})
|
|
38
38
|
all_tag_counts(options.merge({:on => context.to_s}))
|
|
39
39
|
end
|
|
40
|
-
|
|
40
|
+
|
|
41
|
+
def tags_on(context, options = {})
|
|
42
|
+
all_tags(options.merge({:on => context.to_s}))
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
##
|
|
46
|
+
# Calculate the tag names.
|
|
47
|
+
# To be used when you don't need tag counts and want to avoid the taggable joins.
|
|
48
|
+
#
|
|
49
|
+
# @param [Hash] options Options:
|
|
50
|
+
# * :start_at - Restrict the tags to those created after a certain time
|
|
51
|
+
# * :end_at - Restrict the tags to those created before a certain time
|
|
52
|
+
# * :conditions - A piece of SQL conditions to add to the query. Note we don't join the taggable objects for performance reasons.
|
|
53
|
+
# * :limit - The maximum number of tags to return
|
|
54
|
+
# * :order - A piece of SQL to order by. Eg 'tags.count desc' or 'taggings.created_at desc'
|
|
55
|
+
# * :on - Scope the find to only include a certain context
|
|
56
|
+
def all_tags(options = {})
|
|
57
|
+
options.assert_valid_keys :start_at, :end_at, :conditions, :order, :limit, :on
|
|
58
|
+
|
|
59
|
+
## Generate conditions:
|
|
60
|
+
options[:conditions] = sanitize_sql(options[:conditions]) if options[:conditions]
|
|
61
|
+
|
|
62
|
+
start_at_conditions = sanitize_sql(["#{ActsAsTaggableOn::Tagging.table_name}.created_at >= ?", options.delete(:start_at)]) if options[:start_at]
|
|
63
|
+
end_at_conditions = sanitize_sql(["#{ActsAsTaggableOn::Tagging.table_name}.created_at <= ?", options.delete(:end_at)]) if options[:end_at]
|
|
64
|
+
|
|
65
|
+
taggable_conditions = sanitize_sql(["#{ActsAsTaggableOn::Tagging.table_name}.taggable_type = ?", base_class.name])
|
|
66
|
+
taggable_conditions << sanitize_sql([" AND #{ActsAsTaggableOn::Tagging.table_name}.context = ?", options.delete(:on).to_s]) if options[:on]
|
|
67
|
+
|
|
68
|
+
tagging_conditions = [
|
|
69
|
+
taggable_conditions,
|
|
70
|
+
start_at_conditions,
|
|
71
|
+
end_at_conditions
|
|
72
|
+
].compact.reverse
|
|
73
|
+
|
|
74
|
+
tag_conditions = [
|
|
75
|
+
options[:conditions]
|
|
76
|
+
].compact.reverse
|
|
77
|
+
|
|
78
|
+
## Generate scope:
|
|
79
|
+
tagging_scope = ActsAsTaggableOn::Tagging.select("#{ActsAsTaggableOn::Tagging.table_name}.tag_id")
|
|
80
|
+
tag_scope = ActsAsTaggableOn::Tag.select("#{ActsAsTaggableOn::Tag.table_name}.*").order(options[:order]).limit(options[:limit])
|
|
81
|
+
|
|
82
|
+
# Joins and conditions
|
|
83
|
+
tagging_conditions.each { |condition| tagging_scope = tagging_scope.where(condition) }
|
|
84
|
+
tag_conditions.each { |condition| tag_scope = tag_scope.where(condition) }
|
|
85
|
+
|
|
86
|
+
group_columns = "#{ActsAsTaggableOn::Tagging.table_name}.tag_id"
|
|
87
|
+
|
|
88
|
+
# Append the current scope to the scope, because we can't use scope(:find) in RoR 3.0 anymore:
|
|
89
|
+
scoped_select = "#{table_name}.#{primary_key}"
|
|
90
|
+
tagging_scope = tagging_scope.where("#{ActsAsTaggableOn::Tagging.table_name}.taggable_id IN(#{safe_to_sql(select(scoped_select))})").group(group_columns)
|
|
91
|
+
|
|
92
|
+
tag_scope = tag_scope.joins("JOIN (#{safe_to_sql(tagging_scope)}) AS #{ActsAsTaggableOn::Tagging.table_name} ON #{ActsAsTaggableOn::Tagging.table_name}.tag_id = #{ActsAsTaggableOn::Tag.table_name}.id")
|
|
93
|
+
tag_scope.extending(CalculationMethods)
|
|
94
|
+
end
|
|
95
|
+
|
|
41
96
|
##
|
|
42
97
|
# Calculate the tag counts for all tags.
|
|
43
98
|
#
|
|
@@ -53,80 +108,87 @@ module ActsAsTaggableOn::Taggable
|
|
|
53
108
|
def all_tag_counts(options = {})
|
|
54
109
|
options.assert_valid_keys :start_at, :end_at, :conditions, :at_least, :at_most, :order, :limit, :on, :id
|
|
55
110
|
|
|
56
|
-
scope =
|
|
57
|
-
{}
|
|
58
|
-
else
|
|
59
|
-
scope(:find) || {}
|
|
60
|
-
end
|
|
111
|
+
scope = {}
|
|
61
112
|
|
|
62
113
|
## Generate conditions:
|
|
63
|
-
options[:conditions] = sanitize_sql(options[:conditions]) if options[:conditions]
|
|
64
|
-
|
|
114
|
+
options[:conditions] = sanitize_sql(options[:conditions]) if options[:conditions]
|
|
115
|
+
|
|
65
116
|
start_at_conditions = sanitize_sql(["#{ActsAsTaggableOn::Tagging.table_name}.created_at >= ?", options.delete(:start_at)]) if options[:start_at]
|
|
66
117
|
end_at_conditions = sanitize_sql(["#{ActsAsTaggableOn::Tagging.table_name}.created_at <= ?", options.delete(:end_at)]) if options[:end_at]
|
|
67
118
|
|
|
68
119
|
taggable_conditions = sanitize_sql(["#{ActsAsTaggableOn::Tagging.table_name}.taggable_type = ?", base_class.name])
|
|
69
|
-
taggable_conditions << sanitize_sql([" AND #{ActsAsTaggableOn::Tagging.table_name}.taggable_id = ?", options
|
|
120
|
+
taggable_conditions << sanitize_sql([" AND #{ActsAsTaggableOn::Tagging.table_name}.taggable_id = ?", options[:id]]) if options[:id]
|
|
121
|
+
taggable_conditions << sanitize_sql([" AND #{ActsAsTaggableOn::Tagging.table_name}.context = ?", options.delete(:on).to_s]) if options[:on]
|
|
70
122
|
|
|
71
|
-
|
|
123
|
+
tagging_conditions = [
|
|
72
124
|
taggable_conditions,
|
|
73
|
-
options[:conditions],
|
|
74
125
|
scope[:conditions],
|
|
75
126
|
start_at_conditions,
|
|
76
127
|
end_at_conditions
|
|
77
128
|
].compact.reverse
|
|
78
|
-
|
|
79
|
-
## Generate joins:
|
|
80
|
-
tagging_join = "LEFT OUTER JOIN #{ActsAsTaggableOn::Tagging.table_name} ON #{ActsAsTaggableOn::Tag.table_name}.id = #{ActsAsTaggableOn::Tagging.table_name}.tag_id"
|
|
81
|
-
tagging_join << sanitize_sql([" AND #{ActsAsTaggableOn::Tagging.table_name}.context = ?", options.delete(:on).to_s]) if options[:on]
|
|
82
129
|
|
|
130
|
+
tag_conditions = [
|
|
131
|
+
options[:conditions]
|
|
132
|
+
].compact.reverse
|
|
133
|
+
|
|
134
|
+
## Generate joins:
|
|
83
135
|
taggable_join = "INNER JOIN #{table_name} ON #{table_name}.#{primary_key} = #{ActsAsTaggableOn::Tagging.table_name}.taggable_id"
|
|
84
|
-
taggable_join << " AND #{table_name}.#{inheritance_column} = '#{name}'" unless descends_from_active_record? # Current model is STI descendant, so add type checking to the join condition
|
|
136
|
+
taggable_join << " AND #{table_name}.#{inheritance_column} = '#{name}'" unless descends_from_active_record? # Current model is STI descendant, so add type checking to the join condition
|
|
85
137
|
|
|
86
|
-
|
|
87
|
-
tagging_join,
|
|
138
|
+
tagging_joins = [
|
|
88
139
|
taggable_join,
|
|
89
140
|
scope[:joins]
|
|
90
141
|
].compact
|
|
91
142
|
|
|
92
|
-
|
|
93
|
-
|
|
143
|
+
tag_joins = [
|
|
144
|
+
].compact
|
|
94
145
|
|
|
95
146
|
## Generate scope:
|
|
96
|
-
|
|
97
|
-
|
|
147
|
+
tagging_scope = ActsAsTaggableOn::Tagging.select("#{ActsAsTaggableOn::Tagging.table_name}.tag_id, COUNT(#{ActsAsTaggableOn::Tagging.table_name}.tag_id) AS tags_count")
|
|
148
|
+
tag_scope = ActsAsTaggableOn::Tag.select("#{ActsAsTaggableOn::Tag.table_name}.*, #{ActsAsTaggableOn::Tagging.table_name}.tags_count AS count").order(options[:order]).limit(options[:limit])
|
|
149
|
+
|
|
98
150
|
# Joins and conditions
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
151
|
+
tagging_joins.each { |join| tagging_scope = tagging_scope.joins(join) }
|
|
152
|
+
tagging_conditions.each { |condition| tagging_scope = tagging_scope.where(condition) }
|
|
153
|
+
|
|
154
|
+
tag_joins.each { |join| tag_scope = tag_scope.joins(join) }
|
|
155
|
+
tag_conditions.each { |condition| tag_scope = tag_scope.where(condition) }
|
|
156
|
+
|
|
102
157
|
# GROUP BY and HAVING clauses:
|
|
103
|
-
at_least = sanitize_sql([
|
|
104
|
-
at_most = sanitize_sql([
|
|
105
|
-
having = [at_least, at_most].compact.join(' AND ')
|
|
158
|
+
at_least = sanitize_sql(["COUNT(#{ActsAsTaggableOn::Tagging.table_name}.tag_id) >= ?", options.delete(:at_least)]) if options[:at_least]
|
|
159
|
+
at_most = sanitize_sql(["COUNT(#{ActsAsTaggableOn::Tagging.table_name}.tag_id) <= ?", options.delete(:at_most)]) if options[:at_most]
|
|
160
|
+
having = ["COUNT(#{ActsAsTaggableOn::Tagging.table_name}.tag_id) > 0", at_least, at_most].compact.join(' AND ')
|
|
161
|
+
|
|
162
|
+
group_columns = "#{ActsAsTaggableOn::Tagging.table_name}.tag_id"
|
|
106
163
|
|
|
107
|
-
|
|
164
|
+
unless options[:id]
|
|
108
165
|
# Append the current scope to the scope, because we can't use scope(:find) in RoR 3.0 anymore:
|
|
109
166
|
scoped_select = "#{table_name}.#{primary_key}"
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
# We have having() in RoR 3.0 so use it:
|
|
113
|
-
having = having.blank? ? "COUNT(*) > 0" : "COUNT(*) > 0 AND #{having}"
|
|
114
|
-
scope = scope.group(grouped_column_names_for(ActsAsTaggableOn::Tag)).having(having)
|
|
115
|
-
else
|
|
116
|
-
# Having is not available in 2.3.x:
|
|
117
|
-
group_by = "#{grouped_column_names_for(ActsAsTaggableOn::Tag)} HAVING COUNT(*) > 0"
|
|
118
|
-
group_by << " AND #{having}" unless having.blank?
|
|
119
|
-
scope = scope.group(group_by)
|
|
167
|
+
tagging_scope = tagging_scope.where("#{ActsAsTaggableOn::Tagging.table_name}.taggable_id IN(#{safe_to_sql(select(scoped_select))})")
|
|
120
168
|
end
|
|
121
169
|
|
|
122
|
-
|
|
170
|
+
tagging_scope = tagging_scope.group(group_columns).having(having)
|
|
171
|
+
|
|
172
|
+
tag_scope = tag_scope.joins("JOIN (#{safe_to_sql(tagging_scope)}) AS #{ActsAsTaggableOn::Tagging.table_name} ON #{ActsAsTaggableOn::Tagging.table_name}.tag_id = #{ActsAsTaggableOn::Tag.table_name}.id")
|
|
173
|
+
tag_scope.extending(CalculationMethods)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def safe_to_sql(relation)
|
|
177
|
+
connection.respond_to?(:unprepared_statement) ? connection.unprepared_statement{relation.to_sql} : relation.to_sql
|
|
123
178
|
end
|
|
124
179
|
end
|
|
125
|
-
|
|
180
|
+
|
|
126
181
|
module InstanceMethods
|
|
127
182
|
def tag_counts_on(context, options={})
|
|
128
183
|
self.class.tag_counts_on(context, options.merge(:id => id))
|
|
129
184
|
end
|
|
130
185
|
end
|
|
186
|
+
|
|
187
|
+
module CalculationMethods
|
|
188
|
+
def count
|
|
189
|
+
# https://github.com/rails/rails/commit/da9b5d4a8435b744fcf278fffd6d7f1e36d4a4f2
|
|
190
|
+
super(:all)
|
|
191
|
+
end
|
|
192
|
+
end
|
|
131
193
|
end
|
|
132
|
-
end
|
|
194
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
module ActsAsTaggableOn::Compatibility
|
|
2
|
+
def has_many_with_compatibility(name, options = {}, &extention)
|
|
3
|
+
if ActiveRecord::VERSION::MAJOR >= 4
|
|
4
|
+
scope, opts = build_scope_and_options(options)
|
|
5
|
+
has_many(name, scope, opts, &extention)
|
|
6
|
+
else
|
|
7
|
+
has_many(name, options, &extention)
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def build_scope_and_options(opts)
|
|
12
|
+
scope_opts, opts = parse_options(opts)
|
|
13
|
+
|
|
14
|
+
unless scope_opts.empty?
|
|
15
|
+
scope = lambda do
|
|
16
|
+
scope_opts.inject(self) { |result, hash| result.send *hash }
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
[defined?(scope) ? scope : nil, opts]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def parse_options(opts)
|
|
24
|
+
scope_opts = {}
|
|
25
|
+
[:order, :having, :select, :group, :limit, :offset, :readonly].each do |o|
|
|
26
|
+
scope_opts[o] = opts.delete o if opts[o]
|
|
27
|
+
end
|
|
28
|
+
scope_opts[:where] = opts.delete :conditions if opts[:conditions]
|
|
29
|
+
scope_opts[:joins] = opts.delete :include if opts [:include]
|
|
30
|
+
scope_opts[:distinct] = opts.delete :uniq if opts[:uniq]
|
|
31
|
+
|
|
32
|
+
[scope_opts, opts]
|
|
33
|
+
end
|
|
34
|
+
end
|