acts-as-taggable-on 3.0.2 → 3.1.0.rc1

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 CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- OGMwZTUwZGZkYzg3ZGRhM2E3ZWNhYjU1MjE4OGFhYjU5MTk2NTAwMg==
4
+ NWU0YmVhNGFlMjYzYTg1NDdiNTRkODQ5ODViYjRmNGI3ZjNiMjZiZA==
5
5
  data.tar.gz: !binary |-
6
- MmE0ZDlhNmE2ZjRjY2RiNGU3YjVlMzE1MGQwMjVhMzhhZDA5ZmYzMQ==
6
+ YTQ0MDhmYzEyMDA5Yzc5MTQ5MjgxODY1NWQ4YzQ1ZGI1YmYwYTg1Ng==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- YmI2YTcwNGFhMWFhN2U4YmQ1NjYwNzE0NjY0Nzc4MDZjN2E5NGY1ZjJlZDkz
10
- OWExZTIxNTkzYWM2ZTAwNjE2ZDRmMzk1NDc3NDU1ZTU2ZGIzZThkNWNhZGQy
11
- ODJlNWM0MThiNjFkYmY3NTRiNTYxYzU5ZmQxYmU3Mjc4MWYxNjM=
9
+ OGU1OTU3ZjFkMDc1ZDAzNGY1YzI4Yzk1ODFlYTE1YWNjZThmMDcyYTBhMDMz
10
+ MzdiYWUxYjgxNDhiYzQ4ZmI5ZTgyMjIwMWZkYmI3ZTc2ZWM4YmUyNzUxZDYx
11
+ ZWJhNmU1ZDRiOWU2ZGZiM2YyMTAwMTc1YmZmYWYzYzdkMTg4YzY=
12
12
  data.tar.gz: !binary |-
13
- ZTE4ZGVmYTZlMTlkMWE1OTY5OGQxYTE5ZmNmYWE3NDJhNTZmZjcyZTEyMzI1
14
- OGU1YTFiOTNhNTliZTliYjhmMzcwMDg4ZTBjY2M0MjcwZTYxMzg4NTY4OGQw
15
- MDU0ZmNlOTlhODkyOTk4NmYyNGJhZGM1NDIyZTQ1MzkxMmJiNWE=
13
+ YjU3MzdjMTIyNDM2ZDY2ZmNhZjUxZDhlMTgwYTM3NjFiY2E2Mzg5NDRmZDNi
14
+ ZTU1NTQ1MjAwMzIwNDVjNzEyYmNlZTU1MmNkYTk3OGY2ZjhkNTA2M2M2ZWVj
15
+ ZDQzODA0OTMxMTMxNDMzNTY5ZTk3NWE1MzgxZDM3ZjgwMDBjMTA=
@@ -4,25 +4,32 @@ Each change should fall into categories that would affect whether the release is
4
4
 
5
5
  As such, a _Feature_ would map to either major or minor. A _bug fix_ to a patch. And _misc_ is either minor or patch, the difference being kind of fuzzy for the purposes of history. Adding tests would be patch level.
6
6
 
7
- ### Master [changes](https://github.com/mbleigh/acts-as-taggable-on/compare/v3.0.2...master)
7
+ ### Master [changes](https://github.com/mbleigh/acts-as-taggable-on/compare/v3.0.1...master)
8
8
 
9
9
  * Breaking Changes
10
10
  * Features
11
+ * Fixes
12
+ * Misc
13
+
14
+ ### [3.1.0.rc1 / 2014-02-26](https://github.com/mbleigh/acts-as-taggable-on/compare/v3.0.1...v3.1.0.rc1)
15
+
16
+ * Features
17
+ * [@jamesburke-examtime #467 Add :order_by_matching_tag_count option](https://github.com/mbleigh/acts-as-taggable-on/pull/469)
11
18
  * Fixes
12
19
  * [@rafael #406 Dirty attributes not correctly derived](https://github.com/mbleigh/acts-as-taggable-on/pull/406)
13
20
  * [@bzbnhang #440 Did not respect strict_case_match](https://github.com/mbleigh/acts-as-taggable-on/pull/440)
14
21
  * [@znz #456 Fix breaking encoding of tag](https://github.com/mbleigh/acts-as-taggable-on/pull/456)
22
+ * [@rgould #417 Let '.count' work when tagged_with is accompanied by a group clause](https://github.com/mbleigh/acts-as-taggable-on/pull/417)
23
+ * [@developer88 #461 Move 'Distinct' out of select string and use .uniq instead](https://github.com/mbleigh/acts-as-taggable-on/pull/461)
24
+ * [@gerard-leijdekkers #473 Fixed down migration index name](https://github.com/mbleigh/acts-as-taggable-on/pull/473)
15
25
  * Misc
26
+ * [@billychan #463 Thread safe support](https://github.com/mbleigh/acts-as-taggable-on/pull/463)
16
27
  * [@billychan #386 Add parse:true instructions to README](https://github.com/mbleigh/acts-as-taggable-on/pull/386)
17
28
  * [@seuros #449 Improve README/UPGRADING/post install docs](https://github.com/mbleigh/acts-as-taggable-on/pull/449)
18
29
  * [@seuros #452 Remove I18n deprecation warning in specs](https://github.com/mbleigh/acts-as-taggable-on/pull/452)
19
30
  * [@seuros #453 Test against Ruby 2.1 on Travis CI](https://github.com/mbleigh/acts-as-taggable-on/pull/453)
20
31
  * [@takashi #454 Clarify example in docs](https://github.com/mbleigh/acts-as-taggable-on/pull/454)
21
32
 
22
- ### [3.0.2 / 2014-03-12](https://github.com/mbleigh/acts-as-taggable-on/compare/v3.0.1...v3.0.2)
23
-
24
- * [@mikehale #486 Match_all respects context. Backport of #487](https://github.com/mbleigh/acts-as-taggable-on/pull/486)
25
-
26
33
  ### [3.0.1 / 2014-01-08](https://github.com/mbleigh/acts-as-taggable-on/compare/v3.0.0...v3.0.1)
27
34
 
28
35
  * Fixes
@@ -22,7 +22,9 @@ Gem::Specification.new do |gem|
22
22
  gem.post_install_message = File.read('UPGRADING.md')
23
23
  end
24
24
 
25
- gem.add_runtime_dependency 'rails', ['>= 3', '< 5']
25
+ gem.add_runtime_dependency 'activerecord', ['>= 3', '< 5']
26
+ gem.add_runtime_dependency 'activesupport', ['>= 3', '< 5']
27
+ gem.add_runtime_dependency 'actionpack', ['>= 3', '< 5']
26
28
 
27
29
  gem.add_development_dependency 'sqlite3'
28
30
  gem.add_development_dependency 'mysql2', '~> 0.3.7'
@@ -13,7 +13,7 @@ class AddMissingUniqueIndices < ActiveRecord::Migration
13
13
  def self.down
14
14
  remove_index :tags, :name
15
15
 
16
- remove_index :taggings, name: 'tagging_idx'
16
+ remove_index :taggings, name: 'taggings_idx'
17
17
  add_index :taggings, :tag_id
18
18
  add_index :taggings, [:taggable_id, :taggable_type, :context]
19
19
  end
@@ -2,34 +2,44 @@ require "active_record"
2
2
  require "active_record/version"
3
3
  require "active_support/core_ext/module"
4
4
  require "action_view"
5
- require 'active_support/all'
6
5
 
7
6
  require "digest/sha1"
8
7
 
9
8
  module ActsAsTaggableOn
10
- mattr_accessor :delimiter
11
- @@delimiter = ','
12
-
13
- mattr_accessor :force_lowercase
14
- @@force_lowercase = false
15
-
16
- mattr_accessor :force_parameterize
17
- @@force_parameterize = false
9
+ def self.setup
10
+ @configuration ||= Configuration.new
11
+ yield @configuration if block_given?
12
+ end
18
13
 
19
- mattr_accessor :strict_case_match
20
- @@strict_case_match = false
14
+ def self.method_missing(method_name, *args, &block)
15
+ @configuration.respond_to?(method_name) ?
16
+ @configuration.send(method_name, *args, &block) : super
17
+ end
21
18
 
22
- mattr_accessor :remove_unused_tags
23
- self.remove_unused_tags = false
19
+ def self.respond_to?(method_name, include_private=false)
20
+ @configuration.respond_to? method_name
21
+ end
24
22
 
25
23
  def self.glue
26
- delimiter = @@delimiter.kind_of?(Array) ? @@delimiter[0] : @@delimiter
24
+ setting = @configuration.delimiter
25
+ delimiter = setting.kind_of?(Array) ? setting[0] : setting
27
26
  delimiter.ends_with?(" ") ? delimiter : "#{delimiter} "
28
27
  end
29
28
 
30
- def self.setup
31
- yield self
29
+ class Configuration
30
+ attr_accessor :delimiter, :force_lowercase, :force_parameterize,
31
+ :strict_case_match, :remove_unused_tags
32
+
33
+ def initialize
34
+ @delimiter = ','
35
+ @force_lowercase = false
36
+ @force_parameterize = false
37
+ @strict_case_match = false
38
+ @remove_unused_tags = false
39
+ end
32
40
  end
41
+
42
+ setup
33
43
  end
34
44
 
35
45
 
@@ -7,9 +7,9 @@ module ActsAsTaggableOn::Taggable
7
7
  # @private
8
8
  def _has_tags_cache_columns?(db_columns)
9
9
  db_column_names = db_columns.map(&:name)
10
- tag_types.any? {|context|
10
+ tag_types.any? do |context|
11
11
  db_column_names.include?("cached_#{context.to_s.singularize}_list")
12
- }
12
+ end
13
13
  end
14
14
 
15
15
  # @private
@@ -1,7 +1,6 @@
1
1
  module ActsAsTaggableOn::Taggable
2
2
  module Collection
3
3
  def self.included(base)
4
- base.send :include, ActsAsTaggableOn::Taggable::Collection::InstanceMethods
5
4
  base.extend ActsAsTaggableOn::Taggable::Collection::ClassMethods
6
5
  base.initialize_acts_as_taggable_on_collection
7
6
  end
@@ -178,10 +177,8 @@ module ActsAsTaggableOn::Taggable
178
177
  end
179
178
  end
180
179
 
181
- module InstanceMethods
182
- def tag_counts_on(context, options={})
183
- self.class.tag_counts_on(context, options.merge(:id => id))
184
- end
180
+ def tag_counts_on(context, options={})
181
+ self.class.tag_counts_on(context, options.merge(:id => id))
185
182
  end
186
183
 
187
184
  module CalculationMethods
@@ -12,7 +12,7 @@ module ActsAsTaggableOn::Compatibility
12
12
  scope_opts, opts = parse_options(opts)
13
13
 
14
14
  unless scope_opts.empty?
15
- scope = lambda do
15
+ scope = lambda do
16
16
  scope_opts.inject(self) { |result, hash| result.send *hash }
17
17
  end
18
18
  end
@@ -1,7 +1,6 @@
1
1
  module ActsAsTaggableOn::Taggable
2
2
  module Core
3
3
  def self.included(base)
4
- base.send :include, ActsAsTaggableOn::Taggable::Core::InstanceMethods
5
4
  base.extend ActsAsTaggableOn::Taggable::Core::ClassMethods
6
5
 
7
6
  base.class_eval do
@@ -72,6 +71,7 @@ module ActsAsTaggableOn::Taggable
72
71
  # @param [Hash] options A hash of options to alter you query:
73
72
  # * <tt>:exclude</tt> - if set to true, return objects that are *NOT* tagged with the specified tags
74
73
  # * <tt>:any</tt> - if set to true, return objects that are tagged with *ANY* of the specified tags
74
+ # * <tt>:order_by_matching_tag_count</tt> - if set to true and used with :any, sort by objects matching the most tags, descending
75
75
  # * <tt>:match_all</tt> - if set to true, return objects that are *ONLY* tagged with the specified tags
76
76
  # * <tt>:owned_by</tt> - return objects that are *ONLY* owned by the owner
77
77
  #
@@ -79,6 +79,7 @@ module ActsAsTaggableOn::Taggable
79
79
  # User.tagged_with("awesome", "cool") # Users that are tagged with awesome and cool
80
80
  # User.tagged_with("awesome", "cool", :exclude => true) # Users that are not tagged with awesome or cool
81
81
  # User.tagged_with("awesome", "cool", :any => true) # Users that are tagged with awesome or cool
82
+ # User.tagged_with("awesome", "cool", :any => true, :order_by_matching_tag_count => true) # Sort by users who match the most tags, descending
82
83
  # User.tagged_with("awesome", "cool", :match_all => true) # Users that are tagged with just awesome and cool
83
84
  # User.tagged_with("awesome", "cool", :owned_by => foo ) # Users that are tagged with just awesome and cool by 'foo'
84
85
  def tagged_with(tags, options = {})
@@ -91,6 +92,7 @@ module ActsAsTaggableOn::Taggable
91
92
  conditions = []
92
93
  having = []
93
94
  select_clause = []
95
+ order_by = []
94
96
 
95
97
  context = options.delete(:on)
96
98
  owned_by = options.delete(:owned_by)
@@ -139,7 +141,7 @@ module ActsAsTaggableOn::Taggable
139
141
 
140
142
  # don't need to sanitize sql, map all ids and join with OR logic
141
143
  conditions << tags.map { |t| "#{taggings_alias}.tag_id = #{quote_value(t.id, nil)}" }.join(" OR ")
142
- select_clause = "DISTINCT #{table_name}.*" unless context and tag_types.one?
144
+ select_clause = " #{table_name}.*" unless context and tag_types.one?
143
145
 
144
146
  if owned_by
145
147
  tagging_join << " AND " +
@@ -178,27 +180,35 @@ module ActsAsTaggableOn::Taggable
178
180
  end
179
181
  end
180
182
 
181
- taggings_alias, tags_alias = adjust_taggings_alias("#{alias_base_name}_taggings_group"), "#{alias_base_name}_tags_group"
183
+ group = [] # Rails interprets this as a no-op in the group() call below
184
+ if options.delete(:order_by_matching_tag_count)
185
+ select_clause = "#{table_name}.*, COUNT(#{taggings_alias}.tag_id) AS #{taggings_alias}_count"
186
+ group_columns = ActsAsTaggableOn::Tag.using_postgresql? ? grouped_column_names_for(self) : "#{table_name}.#{primary_key}"
187
+ group = group_columns
188
+ order_by << "#{taggings_alias}_count DESC"
182
189
 
183
- if options.delete(:match_all)
190
+ elsif options.delete(:match_all)
191
+ taggings_alias, _ = adjust_taggings_alias("#{alias_base_name}_taggings_group"), "#{alias_base_name}_tags_group"
184
192
  joins << "LEFT OUTER JOIN #{ActsAsTaggableOn::Tagging.table_name} #{taggings_alias}" +
185
193
  " ON #{taggings_alias}.taggable_id = #{quote}#{table_name}#{quote}.#{primary_key}" +
186
194
  " AND #{taggings_alias}.taggable_type = #{quote_value(base_class.name, nil)}"
187
195
 
188
- joins << " AND " + sanitize_sql(["#{taggings_alias}.context = ?", context.to_s]) if context
189
-
190
196
  group_columns = ActsAsTaggableOn::Tag.using_postgresql? ? grouped_column_names_for(self) : "#{table_name}.#{primary_key}"
191
197
  group = group_columns
192
198
  having = "COUNT(#{taggings_alias}.taggable_id) = #{tags.size}"
193
199
  end
194
200
 
195
- select(select_clause) \
196
- .joins(joins.join(" ")) \
197
- .where(conditions.join(" AND ")) \
198
- .group(group) \
199
- .having(having) \
200
- .order(options[:order]) \
201
- .readonly(false)
201
+ order_by << options[:order] if options[:order].present?
202
+
203
+ request = select(select_clause).
204
+ joins(joins.join(" ")).
205
+ where(conditions.join(" AND ")).
206
+ group(group).
207
+ having(having).
208
+ order(order_by.join(", ")).
209
+ readonly(false)
210
+
211
+ ((context and tag_types.one?) && options.delete(:any)) ? request : request.uniq
202
212
  end
203
213
 
204
214
  def is_taggable?
@@ -217,179 +227,176 @@ module ActsAsTaggableOn::Taggable
217
227
  end
218
228
  end
219
229
 
220
- module InstanceMethods
221
- # all column names are necessary for PostgreSQL group clause
222
- def grouped_column_names_for(object)
223
- self.class.grouped_column_names_for(object)
224
- end
230
+ # all column names are necessary for PostgreSQL group clause
231
+ def grouped_column_names_for(object)
232
+ self.class.grouped_column_names_for(object)
233
+ end
225
234
 
226
- def custom_contexts
227
- @custom_contexts ||= []
228
- end
235
+ def custom_contexts
236
+ @custom_contexts ||= []
237
+ end
229
238
 
230
- def is_taggable?
231
- self.class.is_taggable?
232
- end
239
+ def is_taggable?
240
+ self.class.is_taggable?
241
+ end
233
242
 
234
- def add_custom_context(value)
235
- custom_contexts << value.to_s unless custom_contexts.include?(value.to_s) or self.class.tag_types.map(&:to_s).include?(value.to_s)
236
- end
243
+ def add_custom_context(value)
244
+ custom_contexts << value.to_s unless custom_contexts.include?(value.to_s) or self.class.tag_types.map(&:to_s).include?(value.to_s)
245
+ end
237
246
 
238
- def cached_tag_list_on(context)
239
- self["cached_#{context.to_s.singularize}_list"]
240
- end
247
+ def cached_tag_list_on(context)
248
+ self["cached_#{context.to_s.singularize}_list"]
249
+ end
241
250
 
242
- def tag_list_cache_set_on(context)
243
- variable_name = "@#{context.to_s.singularize}_list"
244
- instance_variable_defined?(variable_name) && !instance_variable_get(variable_name).nil?
245
- end
251
+ def tag_list_cache_set_on(context)
252
+ variable_name = "@#{context.to_s.singularize}_list"
253
+ instance_variable_defined?(variable_name) && !instance_variable_get(variable_name).nil?
254
+ end
246
255
 
247
- def tag_list_cache_on(context)
248
- variable_name = "@#{context.to_s.singularize}_list"
249
- if instance_variable_get(variable_name)
250
- instance_variable_get(variable_name)
251
- elsif cached_tag_list_on(context) && self.class.caching_tag_list_on?(context)
252
- instance_variable_set(variable_name, ActsAsTaggableOn::TagList.from(cached_tag_list_on(context)))
253
- else
254
- instance_variable_set(variable_name, ActsAsTaggableOn::TagList.new(tags_on(context).map(&:name)))
255
- end
256
+ def tag_list_cache_on(context)
257
+ variable_name = "@#{context.to_s.singularize}_list"
258
+ if instance_variable_get(variable_name)
259
+ instance_variable_get(variable_name)
260
+ elsif cached_tag_list_on(context) && self.class.caching_tag_list_on?(context)
261
+ instance_variable_set(variable_name, ActsAsTaggableOn::TagList.from(cached_tag_list_on(context)))
262
+ else
263
+ instance_variable_set(variable_name, ActsAsTaggableOn::TagList.new(tags_on(context).map(&:name)))
256
264
  end
265
+ end
257
266
 
258
- def tag_list_on(context)
259
- add_custom_context(context)
260
- tag_list_cache_on(context)
261
- end
267
+ def tag_list_on(context)
268
+ add_custom_context(context)
269
+ tag_list_cache_on(context)
270
+ end
262
271
 
263
- def all_tags_list_on(context)
264
- variable_name = "@all_#{context.to_s.singularize}_list"
265
- return instance_variable_get(variable_name) if instance_variable_defined?(variable_name) && instance_variable_get(variable_name)
272
+ def all_tags_list_on(context)
273
+ variable_name = "@all_#{context.to_s.singularize}_list"
274
+ return instance_variable_get(variable_name) if instance_variable_defined?(variable_name) && instance_variable_get(variable_name)
266
275
 
267
- instance_variable_set(variable_name, ActsAsTaggableOn::TagList.new(all_tags_on(context).map(&:name)).freeze)
268
- end
276
+ instance_variable_set(variable_name, ActsAsTaggableOn::TagList.new(all_tags_on(context).map(&:name)).freeze)
277
+ end
269
278
 
270
- ##
271
- # Returns all tags of a given context
272
- def all_tags_on(context)
273
- tag_table_name = ActsAsTaggableOn::Tag.table_name
274
- tagging_table_name = ActsAsTaggableOn::Tagging.table_name
279
+ ##
280
+ # Returns all tags of a given context
281
+ def all_tags_on(context)
282
+ tagging_table_name = ActsAsTaggableOn::Tagging.table_name
275
283
 
276
- opts = ["#{tagging_table_name}.context = ?", context.to_s]
277
- scope = base_tags.where(opts)
284
+ opts = ["#{tagging_table_name}.context = ?", context.to_s]
285
+ scope = base_tags.where(opts)
278
286
 
279
- if ActsAsTaggableOn::Tag.using_postgresql?
280
- group_columns = grouped_column_names_for(ActsAsTaggableOn::Tag)
281
- scope.order("max(#{tagging_table_name}.created_at)").group(group_columns)
282
- else
283
- scope.group("#{ActsAsTaggableOn::Tag.table_name}.#{ActsAsTaggableOn::Tag.primary_key}")
284
- end.to_a
285
- end
286
-
287
- ##
288
- # Returns all tags that are not owned of a given context
289
- def tags_on(context)
290
- scope = base_tags.where(["#{ActsAsTaggableOn::Tagging.table_name}.context = ? AND #{ActsAsTaggableOn::Tagging.table_name}.tagger_id IS NULL", context.to_s])
291
- # when preserving tag order, return tags in created order
292
- # if we added the order to the association this would always apply
293
- scope = scope.order("#{ActsAsTaggableOn::Tagging.table_name}.id") if self.class.preserve_tag_order?
294
- scope
295
- end
287
+ if ActsAsTaggableOn::Tag.using_postgresql?
288
+ group_columns = grouped_column_names_for(ActsAsTaggableOn::Tag)
289
+ scope.order("max(#{tagging_table_name}.created_at)").group(group_columns)
290
+ else
291
+ scope.group("#{ActsAsTaggableOn::Tag.table_name}.#{ActsAsTaggableOn::Tag.primary_key}")
292
+ end.to_a
293
+ end
296
294
 
297
- def set_tag_list_on(context, new_list)
298
- add_custom_context(context)
295
+ ##
296
+ # Returns all tags that are not owned of a given context
297
+ def tags_on(context)
298
+ scope = base_tags.where(["#{ActsAsTaggableOn::Tagging.table_name}.context = ? AND #{ActsAsTaggableOn::Tagging.table_name}.tagger_id IS NULL", context.to_s])
299
+ # when preserving tag order, return tags in created order
300
+ # if we added the order to the association this would always apply
301
+ scope = scope.order("#{ActsAsTaggableOn::Tagging.table_name}.id") if self.class.preserve_tag_order?
302
+ scope
303
+ end
299
304
 
300
- variable_name = "@#{context.to_s.singularize}_list"
301
- process_dirty_object(context, new_list) unless custom_contexts.include?(context.to_s)
305
+ def set_tag_list_on(context, new_list)
306
+ add_custom_context(context)
302
307
 
303
- instance_variable_set(variable_name, ActsAsTaggableOn::TagList.from(new_list))
304
- end
308
+ variable_name = "@#{context.to_s.singularize}_list"
309
+ process_dirty_object(context, new_list) unless custom_contexts.include?(context.to_s)
305
310
 
306
- def tagging_contexts
307
- custom_contexts + self.class.tag_types.map(&:to_s)
308
- end
311
+ instance_variable_set(variable_name, ActsAsTaggableOn::TagList.from(new_list))
312
+ end
309
313
 
310
- def process_dirty_object(context,new_list)
311
- value = new_list.is_a?(Array) ? ActsAsTaggableOn::TagList.new(new_list) : new_list
312
- attrib = "#{context.to_s.singularize}_list"
314
+ def tagging_contexts
315
+ custom_contexts + self.class.tag_types.map(&:to_s)
316
+ end
313
317
 
314
- if changed_attributes.include?(attrib)
315
- # The attribute already has an unsaved change.
316
- old = changed_attributes[attrib]
317
- changed_attributes.delete(attrib) if (old.to_s == value.to_s)
318
- else
319
- old = tag_list_on(context).to_s
320
- changed_attributes[attrib] = old if (old.to_s != value.to_s)
321
- end
318
+ def process_dirty_object(context,new_list)
319
+ value = new_list.is_a?(Array) ? ActsAsTaggableOn::TagList.new(new_list) : new_list
320
+ attrib = "#{context.to_s.singularize}_list"
321
+
322
+ if changed_attributes.include?(attrib)
323
+ # The attribute already has an unsaved change.
324
+ old = changed_attributes[attrib]
325
+ changed_attributes.delete(attrib) if (old.to_s == value.to_s)
326
+ else
327
+ old = tag_list_on(context).to_s
328
+ changed_attributes[attrib] = old if (old.to_s != value.to_s)
322
329
  end
330
+ end
323
331
 
324
- def reload(*args)
325
- self.class.tag_types.each do |context|
326
- instance_variable_set("@#{context.to_s.singularize}_list", nil)
327
- instance_variable_set("@all_#{context.to_s.singularize}_list", nil)
328
- end
329
-
330
- super(*args)
332
+ def reload(*args)
333
+ self.class.tag_types.each do |context|
334
+ instance_variable_set("@#{context.to_s.singularize}_list", nil)
335
+ instance_variable_set("@all_#{context.to_s.singularize}_list", nil)
331
336
  end
332
337
 
333
- ##
334
- # Find existing tags or create non-existing tags
335
- def load_tags(tag_list)
336
- ActsAsTaggableOn::Tag.find_or_create_all_with_like_by_name(tag_list)
337
- end
338
+ super(*args)
339
+ end
338
340
 
339
- def save_tags
340
- tagging_contexts.each do |context|
341
- next unless tag_list_cache_set_on(context)
342
- # List of currently assigned tag names
343
- tag_list = tag_list_cache_on(context).uniq
341
+ ##
342
+ # Find existing tags or create non-existing tags
343
+ def load_tags(tag_list)
344
+ ActsAsTaggableOn::Tag.find_or_create_all_with_like_by_name(tag_list)
345
+ end
344
346
 
345
- # Find existing tags or create non-existing tags:
346
- tags = load_tags(tag_list)
347
+ def save_tags
348
+ tagging_contexts.each do |context|
349
+ next unless tag_list_cache_set_on(context)
350
+ # List of currently assigned tag names
351
+ tag_list = tag_list_cache_on(context).uniq
347
352
 
348
- # Tag objects for currently assigned tags
349
- current_tags = tags_on(context)
353
+ # Find existing tags or create non-existing tags:
354
+ tags = load_tags(tag_list)
350
355
 
351
- # Tag maintenance based on whether preserving the created order of tags
352
- if self.class.preserve_tag_order?
353
- old_tags, new_tags = current_tags - tags, tags - current_tags
356
+ # Tag objects for currently assigned tags
357
+ current_tags = tags_on(context)
354
358
 
355
- shared_tags = current_tags & tags
359
+ # Tag maintenance based on whether preserving the created order of tags
360
+ if self.class.preserve_tag_order?
361
+ old_tags, new_tags = current_tags - tags, tags - current_tags
356
362
 
357
- if shared_tags.any? && tags[0...shared_tags.size] != shared_tags
358
- index = shared_tags.each_with_index { |_, i| break i unless shared_tags[i] == tags[i] }
363
+ shared_tags = current_tags & tags
359
364
 
360
- # Update arrays of tag objects
361
- old_tags |= current_tags[index...current_tags.size]
362
- new_tags |= current_tags[index...current_tags.size] & shared_tags
365
+ if shared_tags.any? && tags[0...shared_tags.size] != shared_tags
366
+ index = shared_tags.each_with_index { |_, i| break i unless shared_tags[i] == tags[i] }
363
367
 
364
- # Order the array of tag objects to match the tag list
365
- new_tags = tags.map do |t|
366
- new_tags.find { |n| n.name.downcase == t.name.downcase }
367
- end.compact
368
- end
369
- else
370
- # Delete discarded tags and create new tags
371
- old_tags = current_tags - tags
372
- new_tags = tags - current_tags
373
- end
368
+ # Update arrays of tag objects
369
+ old_tags |= current_tags[index...current_tags.size]
370
+ new_tags |= current_tags[index...current_tags.size] & shared_tags
374
371
 
375
- # Find taggings to remove:
376
- if old_tags.present?
377
- old_taggings = taggings.where(:tagger_type => nil, :tagger_id => nil, :context => context.to_s, :tag_id => old_tags)
372
+ # Order the array of tag objects to match the tag list
373
+ new_tags = tags.map do |t|
374
+ new_tags.find { |n| n.name.downcase == t.name.downcase }
375
+ end.compact
378
376
  end
377
+ else
378
+ # Delete discarded tags and create new tags
379
+ old_tags = current_tags - tags
380
+ new_tags = tags - current_tags
381
+ end
379
382
 
380
- # Destroy old taggings:
381
- if old_taggings.present?
382
- ActsAsTaggableOn::Tagging.destroy_all "#{ActsAsTaggableOn::Tagging.primary_key}".to_sym => old_taggings.map(&:id)
383
- end
383
+ # Find taggings to remove:
384
+ if old_tags.present?
385
+ old_taggings = taggings.where(:tagger_type => nil, :tagger_id => nil, :context => context.to_s, :tag_id => old_tags)
386
+ end
384
387
 
385
- # Create new taggings:
386
- new_tags.each do |tag|
387
- taggings.create!(:tag_id => tag.id, :context => context.to_s, :taggable => self)
388
- end
388
+ # Destroy old taggings:
389
+ if old_taggings.present?
390
+ ActsAsTaggableOn::Tagging.destroy_all "#{ActsAsTaggableOn::Tagging.primary_key}".to_sym => old_taggings.map(&:id)
389
391
  end
390
392
 
391
- true
393
+ # Create new taggings:
394
+ new_tags.each do |tag|
395
+ taggings.create!(:tag_id => tag.id, :context => context.to_s, :taggable => self)
396
+ end
392
397
  end
398
+
399
+ true
393
400
  end
394
401
  end
395
402
  end
@@ -10,7 +10,6 @@ module ActsAsTaggableOn::Taggable
10
10
  def initialize_acts_as_taggable_on_dirty
11
11
  tag_types.map(&:to_s).each do |tags_type|
12
12
  tag_type = tags_type.to_s.singularize
13
- context_tags = tags_type.to_sym
14
13
 
15
14
  class_eval <<-RUBY, __FILE__, __LINE__ + 1
16
15
  def #{tag_type}_list_changed?
@@ -1,135 +1,132 @@
1
1
  module ActsAsTaggableOn::Taggable
2
2
  module Ownership
3
3
  def self.included(base)
4
- base.send :include, ActsAsTaggableOn::Taggable::Ownership::InstanceMethods
5
4
  base.extend ActsAsTaggableOn::Taggable::Ownership::ClassMethods
6
-
5
+
7
6
  base.class_eval do
8
- after_save :save_owned_tags
7
+ after_save :save_owned_tags
9
8
  end
10
-
9
+
11
10
  base.initialize_acts_as_taggable_on_ownership
12
11
  end
13
-
12
+
14
13
  module ClassMethods
15
14
  def acts_as_taggable_on(*args)
16
15
  initialize_acts_as_taggable_on_ownership
17
16
  super(*args)
18
17
  end
19
-
20
- def initialize_acts_as_taggable_on_ownership
18
+
19
+ def initialize_acts_as_taggable_on_ownership
21
20
  tag_types.map(&:to_s).each do |tag_type|
22
21
  class_eval <<-RUBY, __FILE__, __LINE__ + 1
23
22
  def #{tag_type}_from(owner)
24
23
  owner_tag_list_on(owner, '#{tag_type}')
25
- end
24
+ end
26
25
  RUBY
27
- end
26
+ end
28
27
  end
29
28
  end
30
-
31
- module InstanceMethods
32
- def owner_tags_on(owner, context)
33
- if owner.nil?
34
- scope = base_tags.where([%(#{ActsAsTaggableOn::Tagging.table_name}.context = ?), context.to_s])
35
- else
36
- scope = base_tags.where([%(#{ActsAsTaggableOn::Tagging.table_name}.context = ? AND
37
- #{ActsAsTaggableOn::Tagging.table_name}.tagger_id = ? AND
38
- #{ActsAsTaggableOn::Tagging.table_name}.tagger_type = ?), context.to_s, owner.id, owner.class.base_class.to_s])
39
- end
40
29
 
41
- # when preserving tag order, return tags in created order
42
- # if we added the order to the association this would always apply
43
- if self.class.preserve_tag_order?
44
- scope.order("#{ActsAsTaggableOn::Tagging.table_name}.id")
45
- else
46
- scope
47
- end
30
+ def owner_tags_on(owner, context)
31
+ if owner.nil?
32
+ scope = base_tags.where([%(#{ActsAsTaggableOn::Tagging.table_name}.context = ?), context.to_s])
33
+ else
34
+ scope = base_tags.where([%(#{ActsAsTaggableOn::Tagging.table_name}.context = ? AND
35
+ #{ActsAsTaggableOn::Tagging.table_name}.tagger_id = ? AND
36
+ #{ActsAsTaggableOn::Tagging.table_name}.tagger_type = ?), context.to_s, owner.id, owner.class.base_class.to_s])
48
37
  end
49
38
 
50
- def cached_owned_tag_list_on(context)
51
- variable_name = "@owned_#{context}_list"
52
- cache = (instance_variable_defined?(variable_name) && instance_variable_get(variable_name)) || instance_variable_set(variable_name, {})
39
+ # when preserving tag order, return tags in created order
40
+ # if we added the order to the association this would always apply
41
+ if self.class.preserve_tag_order?
42
+ scope.order("#{ActsAsTaggableOn::Tagging.table_name}.id")
43
+ else
44
+ scope
53
45
  end
54
-
55
- def owner_tag_list_on(owner, context)
56
- add_custom_context(context)
46
+ end
57
47
 
58
- cache = cached_owned_tag_list_on(context)
59
-
60
- cache[owner] ||= ActsAsTaggableOn::TagList.new(*owner_tags_on(owner, context).map(&:name))
61
- end
62
-
63
- def set_owner_tag_list_on(owner, context, new_list)
64
- add_custom_context(context)
65
-
66
- cache = cached_owned_tag_list_on(context)
48
+ def cached_owned_tag_list_on(context)
49
+ variable_name = "@owned_#{context}_list"
50
+ (instance_variable_defined?(variable_name) && instance_variable_get(variable_name)) || instance_variable_set(variable_name, {})
51
+ end
67
52
 
68
- cache[owner] = ActsAsTaggableOn::TagList.from(new_list)
69
- end
70
-
71
- def reload(*args)
72
- self.class.tag_types.each do |context|
73
- instance_variable_set("@owned_#{context}_list", nil)
74
- end
75
-
76
- super(*args)
53
+ def owner_tag_list_on(owner, context)
54
+ add_custom_context(context)
55
+
56
+ cache = cached_owned_tag_list_on(context)
57
+
58
+ cache[owner] ||= ActsAsTaggableOn::TagList.new(*owner_tags_on(owner, context).map(&:name))
59
+ end
60
+
61
+ def set_owner_tag_list_on(owner, context, new_list)
62
+ add_custom_context(context)
63
+
64
+ cache = cached_owned_tag_list_on(context)
65
+
66
+ cache[owner] = ActsAsTaggableOn::TagList.from(new_list)
67
+ end
68
+
69
+ def reload(*args)
70
+ self.class.tag_types.each do |context|
71
+ instance_variable_set("@owned_#{context}_list", nil)
77
72
  end
78
-
79
- def save_owned_tags
80
- tagging_contexts.each do |context|
81
- cached_owned_tag_list_on(context).each do |owner, tag_list|
82
-
83
- # Find existing tags or create non-existing tags:
84
- tags = ActsAsTaggableOn::Tag.find_or_create_all_with_like_by_name(tag_list.uniq)
85
-
86
- # Tag objects for owned tags
87
- owned_tags = owner_tags_on(owner, context)
88
-
89
- # Tag maintenance based on whether preserving the created order of tags
90
- if self.class.preserve_tag_order?
91
- old_tags, new_tags = owned_tags - tags, tags - owned_tags
92
-
93
- shared_tags = owned_tags & tags
94
-
95
- if shared_tags.any? && tags[0...shared_tags.size] != shared_tags
96
- index = shared_tags.each_with_index { |_, i| break i unless shared_tags[i] == tags[i] }
97
-
98
- # Update arrays of tag objects
99
- old_tags |= owned_tags.from(index)
100
- new_tags |= owned_tags.from(index) & shared_tags
101
-
102
- # Order the array of tag objects to match the tag list
103
- new_tags = tags.map { |t| new_tags.find { |n| n.name.downcase == t.name.downcase } }.compact
104
- end
105
- else
106
- # Delete discarded tags and create new tags
107
- old_tags = owned_tags - tags
108
- new_tags = tags - owned_tags
109
- end
110
-
111
- # Find all taggings that belong to the taggable (self), are owned by the owner,
112
- # have the correct context, and are removed from the list.
113
- if old_tags.present?
114
- old_taggings = ActsAsTaggableOn::Tagging.where(:taggable_id => id, :taggable_type => self.class.base_class.to_s,
115
- :tagger_type => owner.class.base_class.to_s, :tagger_id => owner.id,
116
- :tag_id => old_tags, :context => context)
117
- end
118
-
119
- # Destroy old taggings:
120
- if old_taggings.present?
121
- ActsAsTaggableOn::Tagging.destroy_all(:id => old_taggings.map(&:id))
122
- end
123
73
 
124
- # Create new taggings:
125
- new_tags.each do |tag|
126
- taggings.create!(:tag_id => tag.id, :context => context.to_s, :tagger => owner, :taggable => self)
74
+ super(*args)
75
+ end
76
+
77
+ def save_owned_tags
78
+ tagging_contexts.each do |context|
79
+ cached_owned_tag_list_on(context).each do |owner, tag_list|
80
+
81
+ # Find existing tags or create non-existing tags:
82
+ tags = ActsAsTaggableOn::Tag.find_or_create_all_with_like_by_name(tag_list.uniq)
83
+
84
+ # Tag objects for owned tags
85
+ owned_tags = owner_tags_on(owner, context)
86
+
87
+ # Tag maintenance based on whether preserving the created order of tags
88
+ if self.class.preserve_tag_order?
89
+ old_tags, new_tags = owned_tags - tags, tags - owned_tags
90
+
91
+ shared_tags = owned_tags & tags
92
+
93
+ if shared_tags.any? && tags[0...shared_tags.size] != shared_tags
94
+ index = shared_tags.each_with_index { |_, i| break i unless shared_tags[i] == tags[i] }
95
+
96
+ # Update arrays of tag objects
97
+ old_tags |= owned_tags.from(index)
98
+ new_tags |= owned_tags.from(index) & shared_tags
99
+
100
+ # Order the array of tag objects to match the tag list
101
+ new_tags = tags.map { |t| new_tags.find { |n| n.name.downcase == t.name.downcase } }.compact
127
102
  end
103
+ else
104
+ # Delete discarded tags and create new tags
105
+ old_tags = owned_tags - tags
106
+ new_tags = tags - owned_tags
107
+ end
108
+
109
+ # Find all taggings that belong to the taggable (self), are owned by the owner,
110
+ # have the correct context, and are removed from the list.
111
+ if old_tags.present?
112
+ old_taggings = ActsAsTaggableOn::Tagging.where(:taggable_id => id, :taggable_type => self.class.base_class.to_s,
113
+ :tagger_type => owner.class.base_class.to_s, :tagger_id => owner.id,
114
+ :tag_id => old_tags, :context => context)
115
+ end
116
+
117
+ # Destroy old taggings:
118
+ if old_taggings.present?
119
+ ActsAsTaggableOn::Tagging.destroy_all(:id => old_taggings.map(&:id))
120
+ end
121
+
122
+ # Create new taggings:
123
+ new_tags.each do |tag|
124
+ taggings.create!(:tag_id => tag.id, :context => context.to_s, :tagger => owner, :taggable => self)
128
125
  end
129
126
  end
130
-
131
- true
132
127
  end
128
+
129
+ true
133
130
  end
134
131
  end
135
132
  end
@@ -1,7 +1,6 @@
1
1
  module ActsAsTaggableOn::Taggable
2
2
  module Related
3
3
  def self.included(base)
4
- base.send :include, ActsAsTaggableOn::Taggable::Related::InstanceMethods
5
4
  base.extend ActsAsTaggableOn::Taggable::Related::ClassMethods
6
5
  base.initialize_acts_as_taggable_on_related
7
6
  end
@@ -20,18 +19,6 @@ module ActsAsTaggableOn::Taggable
20
19
  end
21
20
  RUBY
22
21
  end
23
-
24
- unless tag_types.empty?
25
- class_eval <<-RUBY, __FILE__, __LINE__ + 1
26
- def find_matching_contexts(search_context, result_context, options = {})
27
- matching_contexts_for(search_context.to_s, result_context.to_s, self.class, options)
28
- end
29
-
30
- def find_matching_contexts_for(klass, search_context, result_context, options = {})
31
- matching_contexts_for(search_context.to_s, result_context.to_s, klass, options)
32
- end
33
- RUBY
34
- end
35
22
  end
36
23
 
37
24
  def acts_as_taggable_on(*args)
@@ -40,44 +27,50 @@ module ActsAsTaggableOn::Taggable
40
27
  end
41
28
  end
42
29
 
43
- module InstanceMethods
44
- def matching_contexts_for(search_context, result_context, klass, options = {})
45
- tags_to_find = tags_on(search_context).collect { |t| t.name }
30
+ def find_matching_contexts(search_context, result_context, options = {})
31
+ matching_contexts_for(search_context.to_s, result_context.to_s, self.class, options)
32
+ end
46
33
 
47
- klass.select("#{klass.table_name}.*, COUNT(#{ActsAsTaggableOn::Tag.table_name}.#{ActsAsTaggableOn::Tag.primary_key}) AS count") \
48
- .from("#{klass.table_name}, #{ActsAsTaggableOn::Tag.table_name}, #{ActsAsTaggableOn::Tagging.table_name}") \
49
- .where(["#{exclude_self(klass, id)} #{klass.table_name}.#{klass.primary_key} = #{ActsAsTaggableOn::Tagging.table_name}.taggable_id AND #{ActsAsTaggableOn::Tagging.table_name}.taggable_type = '#{klass.base_class.to_s}' AND #{ActsAsTaggableOn::Tagging.table_name}.tag_id = #{ActsAsTaggableOn::Tag.table_name}.#{ActsAsTaggableOn::Tag.primary_key} AND #{ActsAsTaggableOn::Tag.table_name}.name IN (?) AND #{ActsAsTaggableOn::Tagging.table_name}.context = ?", tags_to_find, result_context]) \
50
- .group(group_columns(klass)) \
51
- .order("count DESC")
52
- end
34
+ def find_matching_contexts_for(klass, search_context, result_context, options = {})
35
+ matching_contexts_for(search_context.to_s, result_context.to_s, klass, options)
36
+ end
53
37
 
54
- def related_tags_for(context, klass, options = {})
55
- tags_to_ignore = Array.wrap(options.delete(:ignore)).map(&:to_s) || []
56
- tags_to_find = tags_on(context).collect { |t| t.name }.reject { |t| tags_to_ignore.include? t }
38
+ def matching_contexts_for(search_context, result_context, klass, options = {})
39
+ tags_to_find = tags_on(search_context).collect { |t| t.name }
57
40
 
58
- klass.select("#{klass.table_name}.*, COUNT(#{ActsAsTaggableOn::Tag.table_name}.#{ActsAsTaggableOn::Tag.primary_key}) AS count") \
59
- .from("#{klass.table_name}, #{ActsAsTaggableOn::Tag.table_name}, #{ActsAsTaggableOn::Tagging.table_name}") \
60
- .where(["#{exclude_self(klass, id)} #{klass.table_name}.#{klass.primary_key} = #{ActsAsTaggableOn::Tagging.table_name}.taggable_id AND #{ActsAsTaggableOn::Tagging.table_name}.taggable_type = '#{klass.base_class.to_s}' AND #{ActsAsTaggableOn::Tagging.table_name}.tag_id = #{ActsAsTaggableOn::Tag.table_name}.#{ActsAsTaggableOn::Tag.primary_key} AND #{ActsAsTaggableOn::Tag.table_name}.name IN (?)", tags_to_find]) \
61
- .group(group_columns(klass)) \
62
- .order("count DESC")
63
- end
41
+ klass.select("#{klass.table_name}.*, COUNT(#{ActsAsTaggableOn::Tag.table_name}.#{ActsAsTaggableOn::Tag.primary_key}) AS count").
42
+ from("#{klass.table_name}, #{ActsAsTaggableOn::Tag.table_name}, #{ActsAsTaggableOn::Tagging.table_name}").
43
+ where(["#{exclude_self(klass, id)} #{klass.table_name}.#{klass.primary_key} = #{ActsAsTaggableOn::Tagging.table_name}.taggable_id AND #{ActsAsTaggableOn::Tagging.table_name}.taggable_type = '#{klass.base_class.to_s}' AND #{ActsAsTaggableOn::Tagging.table_name}.tag_id = #{ActsAsTaggableOn::Tag.table_name}.#{ActsAsTaggableOn::Tag.primary_key} AND #{ActsAsTaggableOn::Tag.table_name}.name IN (?) AND #{ActsAsTaggableOn::Tagging.table_name}.context = ?", tags_to_find, result_context]).
44
+ group(group_columns(klass)).
45
+ order("count DESC")
46
+ end
64
47
 
65
- private
66
-
67
- def exclude_self(klass, id)
68
- if [self.class.base_class, self.class].include? klass
69
- "#{klass.table_name}.#{klass.primary_key} != #{id} AND"
70
- else
71
- nil
72
- end
48
+ def related_tags_for(context, klass, options = {})
49
+ tags_to_ignore = Array.wrap(options.delete(:ignore)).map(&:to_s) || []
50
+ tags_to_find = tags_on(context).collect { |t| t.name }.reject { |t| tags_to_ignore.include? t }
51
+
52
+ klass.select("#{klass.table_name}.*, COUNT(#{ActsAsTaggableOn::Tag.table_name}.#{ActsAsTaggableOn::Tag.primary_key}) AS count").
53
+ from("#{klass.table_name}, #{ActsAsTaggableOn::Tag.table_name}, #{ActsAsTaggableOn::Tagging.table_name}").
54
+ where(["#{exclude_self(klass, id)} #{klass.table_name}.#{klass.primary_key} = #{ActsAsTaggableOn::Tagging.table_name}.taggable_id AND #{ActsAsTaggableOn::Tagging.table_name}.taggable_type = '#{klass.base_class.to_s}' AND #{ActsAsTaggableOn::Tagging.table_name}.tag_id = #{ActsAsTaggableOn::Tag.table_name}.#{ActsAsTaggableOn::Tag.primary_key} AND #{ActsAsTaggableOn::Tag.table_name}.name IN (?)", tags_to_find]).
55
+ group(group_columns(klass)).
56
+ order("count DESC")
57
+ end
58
+
59
+ private
60
+
61
+ def exclude_self(klass, id)
62
+ if [self.class.base_class, self.class].include? klass
63
+ "#{klass.table_name}.#{klass.primary_key} != #{id} AND"
64
+ else
65
+ nil
73
66
  end
67
+ end
74
68
 
75
- def group_columns(klass)
76
- if ActsAsTaggableOn::Tag.using_postgresql?
77
- grouped_column_names_for(klass)
78
- else
79
- "#{klass.table_name}.#{klass.primary_key}"
80
- end
69
+ def group_columns(klass)
70
+ if ActsAsTaggableOn::Tag.using_postgresql?
71
+ grouped_column_names_for(klass)
72
+ else
73
+ "#{klass.table_name}.#{klass.primary_key}"
81
74
  end
82
75
  end
83
76
  end
@@ -1,7 +1,7 @@
1
1
  # coding: utf-8
2
2
  module ActsAsTaggableOn
3
3
  class Tag < ::ActiveRecord::Base
4
- include ActsAsTaggableOn::Utils
4
+ extend ActsAsTaggableOn::Utils
5
5
 
6
6
  attr_accessible :name if defined?(ActiveModel::MassAssignmentSecurity)
7
7
 
@@ -87,7 +87,7 @@ module ActsAsTaggableOn
87
87
  true
88
88
  end
89
89
 
90
- include ActsAsTaggableOn::Utils
90
+ extend ActsAsTaggableOn::Utils
91
91
  end
92
92
  end
93
93
 
@@ -1,34 +1,26 @@
1
1
  module ActsAsTaggableOn
2
2
  module Utils
3
- def self.included(base)
4
-
5
- base.send :include, ActsAsTaggableOn::Utils::OverallMethods
6
- base.extend ActsAsTaggableOn::Utils::OverallMethods
3
+ def using_postgresql?
4
+ ::ActiveRecord::Base.connection && ::ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
7
5
  end
8
6
 
9
- module OverallMethods
10
- def using_postgresql?
11
- ::ActiveRecord::Base.connection && ::ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
12
- end
7
+ def using_sqlite?
8
+ ::ActiveRecord::Base.connection && ::ActiveRecord::Base.connection.adapter_name == 'SQLite'
9
+ end
13
10
 
14
- def using_sqlite?
15
- ::ActiveRecord::Base.connection && ::ActiveRecord::Base.connection.adapter_name == 'SQLite'
16
- end
11
+ def sha_prefix(string)
12
+ Digest::SHA1.hexdigest("#{string}#{rand}")[0..6]
13
+ end
17
14
 
18
- def sha_prefix(string)
19
- Digest::SHA1.hexdigest("#{string}#{rand}")[0..6]
20
- end
21
-
22
- private
23
- def like_operator
24
- using_postgresql? ? 'ILIKE' : 'LIKE'
25
- end
15
+ private
26
16
 
27
- # escape _ and % characters in strings, since these are wildcards in SQL.
28
- def escape_like(str)
29
- str.gsub(/[!%_]/){ |x| '!' + x }
30
- end
17
+ def like_operator
18
+ using_postgresql? ? 'ILIKE' : 'LIKE'
31
19
  end
32
20
 
21
+ # escape _ and % characters in strings, since these are wildcards in SQL.
22
+ def escape_like(str)
23
+ str.gsub(/[!%_]/){ |x| '!' + x }
24
+ end
33
25
  end
34
26
  end
@@ -1,4 +1,4 @@
1
1
  module ActsAsTaggableOn
2
- VERSION = '3.0.2'
2
+ VERSION = '3.1.0.rc1'
3
3
  end
4
4
 
@@ -216,6 +216,13 @@ describe "Taggable" do
216
216
  TaggableModel.tagged_with("ruby").first.should == @taggable
217
217
  end
218
218
 
219
+ it "should be able to get a count with find by tag when using a group by" do
220
+ @taggable.skill_list = "ruby"
221
+ @taggable.save
222
+
223
+ expect(TaggableModel.tagged_with("ruby").group(:created_at).count.count).to eq(1)
224
+ end
225
+
219
226
  it "should be able to find by tag with context" do
220
227
  @taggable.skill_list = "ruby, rails, css"
221
228
  @taggable.tag_list = "bob, charlie"
@@ -369,6 +376,18 @@ describe "Taggable" do
369
376
  TaggableModel.tagged_with(["depressed", "css"], :order => 'taggable_models.name', :any => true).to_a.should == [bob, frank]
370
377
  end
371
378
 
379
+ it "should be able to order by number of matching tags when matching any" do
380
+ bob = TaggableModel.create(:name => "Bob", :tag_list => "fitter, happier, more productive", :skill_list => "ruby, rails, css")
381
+ frank = TaggableModel.create(:name => "Frank", :tag_list => "weaker, depressed, inefficient", :skill_list => "ruby, rails, css")
382
+ steve = TaggableModel.create(:name => 'Steve', :tag_list => 'fitter, happier, more productive', :skill_list => 'c++, java, ruby')
383
+
384
+ TaggableModel.tagged_with(["ruby", "java"], :any => true, :order_by_matching_tag_count => true, :order => 'taggable_models.name').to_a.should == [steve, bob, frank]
385
+ TaggableModel.tagged_with(["c++", "fitter"], :any => true, :order_by_matching_tag_count => true, :order => 'taggable_models.name').to_a.should == [steve, bob]
386
+ TaggableModel.tagged_with(["depressed", "css"], :any => true, :order_by_matching_tag_count => true, :order => 'taggable_models.name').to_a.should == [frank, bob]
387
+ TaggableModel.tagged_with(["fitter", "happier", "more productive", "c++", "java", "ruby"], :any => true, :order_by_matching_tag_count => true, :order => 'taggable_models.name').to_a.should == [steve, bob, frank]
388
+ TaggableModel.tagged_with(["c++", "java", "ruby", "fitter"], :any => true, :order_by_matching_tag_count => true, :order => 'taggable_models.name').to_a.should == [steve, bob, frank]
389
+ end
390
+
372
391
  context "wild: true" do
373
392
  it "should use params as wildcards" do
374
393
  bob = TaggableModel.create(:name => "Bob", :tag_list => "bob, tricia")
@@ -411,14 +430,6 @@ describe "Taggable" do
411
430
  TaggableModel.tagged_with("fitter, happier", :match_all => true).to_a.should == [steve]
412
431
  end
413
432
 
414
- it "should be able to find tagged with only the matching tags for a context" do
415
- bob = TaggableModel.create(:name => "Bob", :tag_list => "lazy, happier", :skill_list => "ruby, rails, css")
416
- frank = TaggableModel.create(:name => "Frank", :tag_list => "fitter, happier, inefficient", :skill_list => "css")
417
- steve = TaggableModel.create(:name => 'Steve', :tag_list => "fitter, happier", :skill_list => "ruby, rails, css")
418
-
419
- TaggableModel.tagged_with("css", :on => :skills, :match_all => true).to_a.should == [frank]
420
- end
421
-
422
433
  it "should be able to find tagged with some excluded tags" do
423
434
  bob = TaggableModel.create(:name => "Bob", :tag_list => "happier, lazy")
424
435
  frank = TaggableModel.create(:name => "Frank", :tag_list => "happier")
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: acts-as-taggable-on
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.2
4
+ version: 3.1.0.rc1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Bleigh
@@ -9,10 +9,50 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-03-12 00:00:00.000000000 Z
12
+ date: 2014-02-27 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
- name: rails
15
+ name: activerecord
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ! '>='
19
+ - !ruby/object:Gem::Version
20
+ version: '3'
21
+ - - <
22
+ - !ruby/object:Gem::Version
23
+ version: '5'
24
+ type: :runtime
25
+ prerelease: false
26
+ version_requirements: !ruby/object:Gem::Requirement
27
+ requirements:
28
+ - - ! '>='
29
+ - !ruby/object:Gem::Version
30
+ version: '3'
31
+ - - <
32
+ - !ruby/object:Gem::Version
33
+ version: '5'
34
+ - !ruby/object:Gem::Dependency
35
+ name: activesupport
36
+ requirement: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ! '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '3'
41
+ - - <
42
+ - !ruby/object:Gem::Version
43
+ version: '5'
44
+ type: :runtime
45
+ prerelease: false
46
+ version_requirements: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - ! '>='
49
+ - !ruby/object:Gem::Version
50
+ version: '3'
51
+ - - <
52
+ - !ruby/object:Gem::Version
53
+ version: '5'
54
+ - !ruby/object:Gem::Dependency
55
+ name: actionpack
16
56
  requirement: !ruby/object:Gem::Requirement
17
57
  requirements:
18
58
  - - ! '>='
@@ -194,12 +234,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
194
234
  version: '0'
195
235
  required_rubygems_version: !ruby/object:Gem::Requirement
196
236
  requirements:
197
- - - ! '>='
237
+ - - ! '>'
198
238
  - !ruby/object:Gem::Version
199
- version: '0'
239
+ version: 1.3.1
200
240
  requirements: []
201
241
  rubyforge_project:
202
- rubygems_version: 2.2.2
242
+ rubygems_version: 2.2.1
203
243
  signing_key:
204
244
  specification_version: 4
205
245
  summary: Advanced tagging for Rails.