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

Sign up to get free protection for your applications and to get access to all the features.
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.