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.
Files changed (65) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +11 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +18 -0
  5. data/Appraisals +11 -0
  6. data/CHANGELOG.md +47 -0
  7. data/Gemfile +10 -9
  8. data/Guardfile +5 -0
  9. data/{MIT-LICENSE → LICENSE.md} +1 -1
  10. data/README.md +313 -0
  11. data/Rakefile +21 -49
  12. data/UPGRADING +7 -0
  13. data/acts-as-taggable-on.gemspec +35 -0
  14. data/{lib/generators/acts_as_taggable_on/migration/templates/active_record/migration.rb → db/migrate/1_acts_as_taggable_on_migration.rb} +3 -1
  15. data/db/migrate/2_add_missing_unique_indices.rb +21 -0
  16. data/gemfiles/rails_3.2.gemfile +7 -0
  17. data/gemfiles/rails_4.0.gemfile +7 -0
  18. data/gemfiles/rails_4.1.gemfile +7 -0
  19. data/lib/acts-as-taggable-on.rb +44 -13
  20. data/lib/acts_as_taggable_on/acts_as_taggable_on/cache.rb +52 -23
  21. data/lib/acts_as_taggable_on/acts_as_taggable_on/collection.rb +111 -49
  22. data/lib/acts_as_taggable_on/acts_as_taggable_on/compatibility.rb +34 -0
  23. data/lib/acts_as_taggable_on/acts_as_taggable_on/core.rb +208 -55
  24. data/lib/acts_as_taggable_on/acts_as_taggable_on/dirty.rb +37 -0
  25. data/lib/acts_as_taggable_on/acts_as_taggable_on/ownership.rb +51 -17
  26. data/lib/acts_as_taggable_on/acts_as_taggable_on/related.rb +41 -22
  27. data/lib/acts_as_taggable_on/engine.rb +6 -0
  28. data/lib/acts_as_taggable_on/tag.rb +65 -20
  29. data/lib/acts_as_taggable_on/tag_list.rb +19 -14
  30. data/lib/acts_as_taggable_on/taggable.rb +105 -0
  31. data/lib/acts_as_taggable_on/{acts_as_tagger.rb → tagger.rb} +15 -6
  32. data/lib/acts_as_taggable_on/tagging.rb +14 -4
  33. data/lib/acts_as_taggable_on/tags_helper.rb +3 -5
  34. data/lib/acts_as_taggable_on/utils.rb +34 -0
  35. data/lib/acts_as_taggable_on/version.rb +4 -0
  36. data/lib/acts_as_taggable_on.rb +6 -0
  37. data/spec/acts_as_taggable_on/acts_as_taggable_on_spec.rb +137 -140
  38. data/spec/acts_as_taggable_on/acts_as_tagger_spec.rb +27 -27
  39. data/spec/acts_as_taggable_on/caching_spec.rb +77 -0
  40. data/spec/acts_as_taggable_on/related_spec.rb +143 -0
  41. data/spec/acts_as_taggable_on/single_table_inheritance_spec.rb +187 -0
  42. data/spec/acts_as_taggable_on/tag_list_spec.rb +118 -62
  43. data/spec/acts_as_taggable_on/tag_spec.rb +110 -14
  44. data/spec/acts_as_taggable_on/taggable_spec.rb +382 -75
  45. data/spec/acts_as_taggable_on/tagger_spec.rb +67 -21
  46. data/spec/acts_as_taggable_on/tagging_spec.rb +3 -6
  47. data/spec/acts_as_taggable_on/tags_helper_spec.rb +24 -8
  48. data/spec/acts_as_taggable_on/utils_spec.rb +21 -0
  49. data/spec/database.yml.sample +4 -2
  50. data/spec/models.rb +29 -2
  51. data/spec/schema.rb +40 -18
  52. data/spec/spec_helper.rb +40 -30
  53. metadata +177 -62
  54. data/CHANGELOG +0 -25
  55. data/README.rdoc +0 -221
  56. data/VERSION +0 -1
  57. data/generators/acts_as_taggable_on_migration/acts_as_taggable_on_migration_generator.rb +0 -7
  58. data/generators/acts_as_taggable_on_migration/templates/migration.rb +0 -29
  59. data/lib/acts_as_taggable_on/acts_as_taggable_on.rb +0 -53
  60. data/lib/acts_as_taggable_on/compatibility/Gemfile +0 -8
  61. data/lib/acts_as_taggable_on/compatibility/active_record_backports.rb +0 -17
  62. data/lib/acts_as_taggable_on/compatibility/postgresql.rb +0 -44
  63. data/lib/generators/acts_as_taggable_on/migration/migration_generator.rb +0 -32
  64. data/rails/init.rb +0 -1
  65. data/spec/database.yml +0 -17
@@ -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
- $LOAD_PATH.unshift(File.dirname(__FILE__))
7
+ require "digest/sha1"
5
8
 
6
- require "acts_as_taggable_on/compatibility/active_record_backports" if ActiveRecord::VERSION::MAJOR < 3
9
+ module ActsAsTaggableOn
10
+ mattr_accessor :delimiter
11
+ @@delimiter = ','
7
12
 
8
- require "acts_as_taggable_on/acts_as_taggable_on"
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/acts_as_tagger"
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
- $LOAD_PATH.shift
22
-
23
- if defined?(ActiveRecord::Base)
24
- ActiveRecord::Base.extend ActsAsTaggableOn::Taggable
25
- ActiveRecord::Base.send :include, ActsAsTaggableOn::Tagger
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
- # Skip adding caching capabilities if table not exists or no cache columns exist
5
- return unless base.table_exists? && base.tag_types.any? { |context| base.column_names.include?("cached_#{context.to_s.singularize}_list") }
6
-
7
- base.send :include, ActsAsTaggableOn::Taggable::Cache::InstanceMethods
8
- base.extend ActsAsTaggableOn::Taggable::Cache::ClassMethods
9
-
10
- base.class_eval do
11
- before_save :save_cached_tag_list
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 intialize_acts_as_taggable_on_cache
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
- intialize_acts_as_taggable_on_cache
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.singularize).to_a.flatten.compact.join(', ')
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 = if ActiveRecord::VERSION::MAJOR >= 3
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.delete(:id)]) if options[:id]
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
- conditions = [
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
- joins = [
87
- tagging_join,
138
+ tagging_joins = [
88
139
  taggable_join,
89
140
  scope[:joins]
90
141
  ].compact
91
142
 
92
- joins = joins.reverse if ActiveRecord::VERSION::MAJOR < 3
93
-
143
+ tag_joins = [
144
+ ].compact
94
145
 
95
146
  ## Generate scope:
96
- scope = ActsAsTaggableOn::Tag.scoped(:select => "#{ActsAsTaggableOn::Tag.table_name}.*, COUNT(*) AS count").order(options[:order]).limit(options[:limit])
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
- joins.each { |join| scope = scope.joins(join) }
100
- conditions.each { |condition| scope = scope.where(condition) }
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(['COUNT(*) >= ?', options.delete(:at_least)]) if options[:at_least]
104
- at_most = sanitize_sql(['COUNT(*) <= ?', options.delete(:at_most)]) if options[:at_most]
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
- if ActiveRecord::VERSION::MAJOR >= 3
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
- scope = scope.where("#{ActsAsTaggableOn::Tagging.table_name}.taggable_id IN(#{select(scoped_select).to_sql})")
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
- scope
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