acts-as-taggable-on 2.1.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. data/.gitignore +1 -0
  2. data/.travis.yml +1 -2
  3. data/CHANGELOG +10 -0
  4. data/README.rdoc +31 -63
  5. data/acts-as-taggable-on.gemspec +4 -3
  6. data/lib/acts-as-taggable-on/version.rb +1 -1
  7. data/lib/acts-as-taggable-on.rb +0 -4
  8. data/lib/acts_as_taggable_on/acts_as_taggable_on/cache.rb +1 -1
  9. data/lib/acts_as_taggable_on/acts_as_taggable_on/collection.rb +1 -1
  10. data/lib/acts_as_taggable_on/acts_as_taggable_on/core.rb +29 -17
  11. data/lib/acts_as_taggable_on/acts_as_taggable_on/related.rb +15 -15
  12. data/lib/acts_as_taggable_on/acts_as_taggable_on.rb +4 -13
  13. data/lib/acts_as_taggable_on/tag.rb +12 -13
  14. data/lib/acts_as_taggable_on/tagging.rb +0 -2
  15. data/lib/acts_as_taggable_on/utils.rb +7 -8
  16. data/lib/generators/acts_as_taggable_on/migration/migration_generator.rb +9 -2
  17. data/spec/acts_as_taggable_on/acts_as_taggable_on_spec.rb +228 -61
  18. data/spec/acts_as_taggable_on/tag_list_spec.rb +4 -0
  19. data/spec/acts_as_taggable_on/tag_spec.rb +11 -15
  20. data/spec/acts_as_taggable_on/taggable_spec.rb +89 -27
  21. data/spec/acts_as_taggable_on/tagger_spec.rb +14 -0
  22. data/spec/acts_as_taggable_on/utils_spec.rb +2 -3
  23. data/spec/database.yml.sample +1 -1
  24. data/spec/generators/acts_as_taggable_on/migration/migration_generator_spec.rb +22 -0
  25. data/spec/models.rb +11 -2
  26. data/spec/schema.rb +7 -0
  27. data/spec/spec_helper.rb +1 -0
  28. metadata +95 -123
  29. data/VERSION +0 -1
  30. data/generators/acts_as_taggable_on_migration/acts_as_taggable_on_migration_generator.rb +0 -7
  31. data/generators/acts_as_taggable_on_migration/templates/migration.rb +0 -29
  32. data/lib/acts_as_taggable_on/compatibility/Gemfile +0 -8
  33. data/lib/acts_as_taggable_on/compatibility/active_record_backports.rb +0 -21
data/.gitignore CHANGED
@@ -5,3 +5,4 @@
5
5
  .rvmrc
6
6
  Gemfile.lock
7
7
  spec/database.yml
8
+ tmp
data/.travis.yml CHANGED
@@ -1,9 +1,8 @@
1
1
  script: "cp spec/database.yml.sample spec/database.yml && bundle install && bundle exec rake"
2
2
  rvm:
3
3
  - 1.8.7
4
- - ree
5
4
  - 1.9.2
6
- - rbx
5
+ - 1.9.3
7
6
  env:
8
7
  - DB=sqlite3
9
8
  - DB=mysql
data/CHANGELOG CHANGED
@@ -1,3 +1,13 @@
1
+ == 2011-08-21
2
+ * escape _ and % for mysql and postgres (@tilsammans)
3
+ * Now depends on mysql2 gem
4
+ * tagged_with :any is chainable now (@jeffreyiacono)
5
+ * tagged_with(nil) returns scoped object
6
+ * Case-insensitivity for TaggedModel.tagged_with for PostgreSQL database
7
+ * tagged_with(' ') returns scoped object
8
+ * remove warning for rails 3.1 about class_inheritable_attribute
9
+ * use ActiveRecord migration_number to avoid clashs (@atd)
10
+
1
11
  == 2010-02-17
2
12
  * Converted the plugin to be compatible with Rails3
3
13
 
data/README.rdoc CHANGED
@@ -1,4 +1,5 @@
1
1
  = ActsAsTaggableOn
2
+ {<img src="https://secure.travis-ci.org/mbleigh/acts-as-taggable-on.png" />}[http://travis-ci.org/mbleigh/acts-as-taggable-on]
2
3
 
3
4
  This plugin was originally based on Acts as Taggable on Steroids by Jonathan Viney.
4
5
  It has evolved substantially since that point, but all credit goes to him for the
@@ -17,34 +18,14 @@ was used.
17
18
 
18
19
  === Rails 2.3.x
19
20
 
20
- Acts As Taggable On is tested to work in Rails 2.3.5.
21
-
22
- ==== Plugin
23
-
24
- Acts As Taggable On is available both as a gem and as a traditional plugin. For the
25
- traditional plugin you can install like so:
26
-
27
- script/plugin install git://github.com/mbleigh/acts-as-taggable-on.git
28
-
29
- Acts As Taggable On is also available as a gem plugin using Rails 2.1's gem dependencies.
30
- To install the gem, add this to your config/environment.rb:
31
-
32
- config.gem "acts-as-taggable-on", :source => "http://gemcutter.org", :version => '2.0.0.rc1'
33
-
34
- After that, you can run "rake gems:install" to install the gem if you don't already have it.
35
-
36
- ==== Post Installation
37
-
38
- 1. script/generate acts_as_taggable_on_migration
39
- 2. rake db:migrate
21
+ To use it, add it to your Gemfile:
40
22
 
41
- === Rails 3.0
23
+ gem 'acts-as-taggable-on', '2.1.0'
42
24
 
43
- Acts As Taggable On is now useable in Rails 3.0, thanks to the excellent work of Szymon Nowak
44
- and Jelle Vandebeeck.
25
+ === Rails 3.x
45
26
 
46
27
  To use it, add it to your Gemfile:
47
-
28
+
48
29
  gem 'acts-as-taggable-on'
49
30
 
50
31
  ==== Post Installation
@@ -54,20 +35,11 @@ To use it, add it to your Gemfile:
54
35
 
55
36
  == Testing
56
37
 
57
- Acts As Taggable On uses RSpec for its test coverage. Inside the plugin
58
- directory, you can run the specs for RoR 3.0.0 with:
38
+ Acts As Taggable On uses RSpec for its test coverage. Inside the gem
39
+ directory, you can run the specs for RoR 3.x with:
59
40
 
60
41
  rake spec
61
42
 
62
- If you want to test the plugin for Rails 2.3.x, use:
63
-
64
- rake rails2.3:spec
65
-
66
- If you already have RSpec on your application, the specs will run while using:
67
-
68
- rake spec:plugins
69
-
70
-
71
43
  == Usage
72
44
 
73
45
  class User < ActiveRecord::Base
@@ -97,7 +69,7 @@ compatibility with the will_paginate gem:
97
69
 
98
70
  class User < ActiveRecord::Base
99
71
  acts_as_taggable_on :tags, :skills
100
- named_scope :by_join_date, :order => "created_at DESC"
72
+ scope :by_join_date, order("created_at DESC")
101
73
  end
102
74
 
103
75
  User.tagged_with("awesome").by_date
@@ -105,17 +77,19 @@ compatibility with the will_paginate gem:
105
77
 
106
78
  # Find a user with matching all tags, not just one
107
79
  User.tagged_with(["awesome", "cool"], :match_all => :true)
108
-
80
+
109
81
  # Find a user with any of the tags:
110
82
  User.tagged_with(["awesome", "cool"], :any => true)
111
-
83
+
112
84
  # Find a user with any of tags based on context:
113
85
  User.tagged_with(['awesome, cool'], :on => :tags, :any => true).tagged_with(['smart', 'shy'], :on => :skills, :any => true)
114
-
86
+
87
+ Note: User.tagged_with([]) or '' will return [], but not all records.
88
+
115
89
  === Relationships
116
90
 
117
91
  You can find objects of the same type based on similar tags on certain contexts.
118
- Also, objects will be returned in descending order based on the total number of
92
+ Also, objects will be returned in descending order based on the total number of
119
93
  matched tags.
120
94
 
121
95
  @bobby = User.find_by_name("Bobby")
@@ -128,8 +102,8 @@ matched tags.
128
102
  @tom.skill_list # => ["hacking", "jogging", "diving"]
129
103
 
130
104
  @tom.find_related_skills # => [<User name="Bobby">,<User name="Frankie">]
131
- @bobby.find_related_skills # => [<User name="Tom">]
132
- @frankie.find_related_skills # => [<User name="Tom">]
105
+ @bobby.find_related_skills # => [<User name="Tom">]
106
+ @frankie.find_related_skills # => [<User name="Tom">]
133
107
 
134
108
  === Dynamic Tag Contexts
135
109
 
@@ -159,8 +133,10 @@ Tags can have owners:
159
133
  @some_user.tag(@some_photo, :with => "paris, normandy", :on => :locations)
160
134
  @some_user.owned_taggings
161
135
  @some_user.owned_tags
162
- @some_photo.locations_from(@some_user)
163
-
136
+ @some_photo.locations_from(@some_user) # => ["paris", "normandy"]
137
+ @some_photo.owner_tags_on(@some_user, :locations) # => [#<ActsAsTaggableOn::Tag id: 1, name: "paris">...]
138
+ @some_photo.owner_tags_on(nil, :locations) # => Ownerships equivalent to saying @some_photo.locations
139
+
164
140
  === Tag cloud calculations
165
141
 
166
142
  To construct tag clouds, the frequency of each tag needs to be calculated.
@@ -203,22 +179,14 @@ CSS:
203
179
 
204
180
  == Contributors
205
181
 
206
- * TomEric (i76) - Maintainer
207
- * Michael Bleigh - Original Author
208
- * Szymon Nowak - Rails 3.0 compatibility
209
- * Jelle Vandebeeck - Rails 3.0 compatibility
210
- * Brendan Lim - Related Objects
211
- * Pradeep Elankumaran - Taggers
212
- * Sinclair Bain - Patch King
213
-
214
- === Patch Contributors
215
-
216
- * tristanzdunn - Related objects of other classes
217
- * azabaj - Fixed migrate down
218
- * Peter Cooper - named_scope fix
219
- * slainer68 - STI fix
220
- * harrylove - migration instructions and fix-ups
221
- * lawrencepit - cached tag work
222
- * sobrinho - fixed tag_cloud helper
223
-
224
- Copyright (c) 2007-2010 Michael Bleigh (http://mbleigh.com/) and Intridea Inc. (http://intridea.com/), released under the MIT license
182
+ We have a long list of valued contributors. {Check them all}[https://github.com/mbleigh/acts-as-taggable-on/contributors]
183
+
184
+ == Maintainers
185
+
186
+ * Artem Kramarenko (artemk)
187
+
188
+ == Author
189
+
190
+ * Michael Bleigh
191
+
192
+ Copyright (c) 2007-2011 Michael Bleigh (http://mbleigh.com/) and Intridea Inc. (http://intridea.com/), released under the MIT license
@@ -4,16 +4,17 @@ require 'acts-as-taggable-on/version'
4
4
  Gem::Specification.new do |gem|
5
5
  gem.name = %q{acts-as-taggable-on}
6
6
  gem.authors = ["Michael Bleigh"]
7
- gem.date = %q{2010-05-19}
7
+ gem.date = %q{2011-12-09}
8
8
  gem.description = %q{With ActsAsTaggableOn, you can tag a single model on several contexts, such as skills, interests, and awards. It also provides other advanced functionality.}
9
9
  gem.summary = "Advanced tagging for Rails."
10
10
  gem.email = %q{michael@intridea.com}
11
11
  gem.homepage = ''
12
12
 
13
- gem.add_runtime_dependency 'rails'
13
+ gem.add_runtime_dependency 'rails', '~> 3.1'
14
14
  gem.add_development_dependency 'rspec', '~> 2.5'
15
+ gem.add_development_dependency 'ammeter', '~> 0.1.3'
15
16
  gem.add_development_dependency 'sqlite3'
16
- gem.add_development_dependency 'mysql2', '< 0.3'
17
+ gem.add_development_dependency 'mysql2', '~> 0.3.7'
17
18
  gem.add_development_dependency 'pg'
18
19
  gem.add_development_dependency 'guard'
19
20
  gem.add_development_dependency 'guard-rspec'
@@ -1,4 +1,4 @@
1
1
  module ActsAsTaggableOn
2
- VERSION = '2.1.0'
2
+ VERSION = '2.2.0'
3
3
  end
4
4
 
@@ -1,12 +1,9 @@
1
1
  require "active_record"
2
2
  require "active_record/version"
3
3
  require "action_view"
4
- RAILS_3 = ::ActiveRecord::VERSION::MAJOR >= 3
5
4
 
6
5
  $LOAD_PATH.unshift(File.dirname(__FILE__))
7
6
 
8
- require "acts_as_taggable_on/compatibility/active_record_backports" unless RAILS_3
9
-
10
7
  require "acts_as_taggable_on/utils"
11
8
 
12
9
  require "acts_as_taggable_on/acts_as_taggable_on"
@@ -16,7 +13,6 @@ require "acts_as_taggable_on/acts_as_taggable_on/cache"
16
13
  require "acts_as_taggable_on/acts_as_taggable_on/ownership"
17
14
  require "acts_as_taggable_on/acts_as_taggable_on/related"
18
15
 
19
- #require "acts_as_taggable_on/utils"
20
16
  require "acts_as_taggable_on/acts_as_tagger"
21
17
  require "acts_as_taggable_on/tag"
22
18
  require "acts_as_taggable_on/tag_list"
@@ -40,7 +40,7 @@ module ActsAsTaggableOn::Taggable
40
40
  tag_types.map(&:to_s).each do |tag_type|
41
41
  if self.class.send("caching_#{tag_type.singularize}_list?")
42
42
  if tag_list_cache_set_on(tag_type)
43
- list = tag_list_cache_on(tag_type.singularize).to_a.flatten.compact.join(', ')
43
+ list = tag_list_cache_on(tag_type).to_a.flatten.compact.join(', ')
44
44
  self["cached_#{tag_type.singularize}_list"] = list
45
45
  end
46
46
  end
@@ -125,7 +125,7 @@ module ActsAsTaggableOn::Taggable
125
125
  tagging_scope = tagging_scope.group(group_by)
126
126
  end
127
127
 
128
- tag_scope = tag_scope.joins("JOIN (#{tagging_scope.to_sql}) AS taggings ON taggings.tag_id = tags.id")
128
+ tag_scope = tag_scope.joins("JOIN (#{tagging_scope.to_sql}) AS #{ActsAsTaggableOn::Tagging.table_name} ON #{ActsAsTaggableOn::Tagging.table_name}.tag_id = #{ActsAsTaggableOn::Tag.table_name}.id")
129
129
  tag_scope
130
130
  end
131
131
  end
@@ -1,5 +1,5 @@
1
1
  module ActsAsTaggableOn::Taggable
2
- module Core
2
+ module Core
3
3
  def self.included(base)
4
4
  base.send :include, ActsAsTaggableOn::Taggable::Core::InstanceMethods
5
5
  base.extend ActsAsTaggableOn::Taggable::Core::ClassMethods
@@ -8,10 +8,10 @@ module ActsAsTaggableOn::Taggable
8
8
  attr_writer :custom_contexts
9
9
  after_save :save_tags
10
10
  end
11
-
11
+
12
12
  base.initialize_acts_as_taggable_on_core
13
13
  end
14
-
14
+
15
15
  module ClassMethods
16
16
  def initialize_acts_as_taggable_on_core
17
17
  tag_types.map(&:to_s).each do |tags_type|
@@ -20,9 +20,9 @@ module ActsAsTaggableOn::Taggable
20
20
  context_tags = tags_type.to_sym
21
21
 
22
22
  class_eval do
23
- has_many context_taggings, :as => :taggable, :dependent => :destroy, :include => :tag, :class_name => "ActsAsTaggableOn::Tagging",
24
- :conditions => ["#{ActsAsTaggableOn::Tagging.table_name}.tag_id = #{ActsAsTaggableOn::Tag.table_name}.id AND #{ActsAsTaggableOn::Tagging.table_name}.context = ?", tags_type]
25
- has_many context_tags, :through => context_taggings, :source => :tag, :class_name => "ActsAsTaggableOn::Tag"
23
+ has_many context_taggings, :as => :taggable, :dependent => :destroy, :include => :tag, :class_name => "ActsAsTaggableOn::Tagging",
24
+ :conditions => ["#{ActsAsTaggableOn::Tagging.table_name}.context = ?", tags_type]
25
+ has_many context_tags, :through => context_taggings, :source => :tag, :class_name => "ActsAsTaggableOn::Tag"
26
26
  end
27
27
 
28
28
  class_eval %(
@@ -38,14 +38,14 @@ module ActsAsTaggableOn::Taggable
38
38
  all_tags_list_on('#{tags_type}')
39
39
  end
40
40
  )
41
- end
41
+ end
42
42
  end
43
-
43
+
44
44
  def acts_as_taggable_on(*args)
45
45
  super(*args)
46
46
  initialize_acts_as_taggable_on_core
47
47
  end
48
-
48
+
49
49
  # all column names are necessary for PostgreSQL group clause
50
50
  def grouped_column_names_for(object)
51
51
  object.column_names.map { |column| "#{object.table_name}.#{column}" }.join(", ")
@@ -59,12 +59,14 @@ module ActsAsTaggableOn::Taggable
59
59
  # * <tt>:exclude</tt> - if set to true, return objects that are *NOT* tagged with the specified tags
60
60
  # * <tt>:any</tt> - if set to true, return objects that are tagged with *ANY* of the specified tags
61
61
  # * <tt>:match_all</tt> - if set to true, return objects that are *ONLY* tagged with the specified tags
62
+ # * <tt>:owned_by</tt> - return objects that are *ONLY* owned by the owner
62
63
  #
63
64
  # Example:
64
65
  # User.tagged_with("awesome", "cool") # Users that are tagged with awesome and cool
65
66
  # User.tagged_with("awesome", "cool", :exclude => true) # Users that are not tagged with awesome or cool
66
67
  # User.tagged_with("awesome", "cool", :any => true) # Users that are tagged with awesome or cool
67
68
  # User.tagged_with("awesome", "cool", :match_all => true) # Users that are tagged with just awesome and cool
69
+ # User.tagged_with("awesome", "cool", :owned_by => foo ) # Users that are tagged with just awesome and cool by 'foo'
68
70
  def tagged_with(tags, options = {})
69
71
  tag_list = ActsAsTaggableOn::TagList.from(tags)
70
72
  empty_result = scoped(:conditions => "1 = 0")
@@ -75,11 +77,12 @@ module ActsAsTaggableOn::Taggable
75
77
  conditions = []
76
78
 
77
79
  context = options.delete(:on)
80
+ owned_by = options.delete(:owned_by)
78
81
  alias_base_name = undecorated_table_name.gsub('.','_')
79
82
 
80
83
  if options.delete(:exclude)
81
84
  tags_conditions = tag_list.map { |t| sanitize_sql(["#{ActsAsTaggableOn::Tag.table_name}.name #{like_operator} ?", t]) }.join(" OR ")
82
- conditions << "#{table_name}.#{primary_key} NOT IN (SELECT #{ActsAsTaggableOn::Tagging.table_name}.taggable_id FROM #{ActsAsTaggableOn::Tagging.table_name} JOIN #{ActsAsTaggableOn::Tag.table_name} ON #{ActsAsTaggableOn::Tagging.table_name}.tag_id = #{ActsAsTaggableOn::Tag.table_name}.id AND (#{tags_conditions}) WHERE #{ActsAsTaggableOn::Tagging.table_name}.taggable_type = #{quote_value(base_class.name)})"
85
+ conditions << "#{table_name}.#{primary_key} NOT IN (SELECT #{ActsAsTaggableOn::Tagging.table_name}.taggable_id FROM #{ActsAsTaggableOn::Tagging.table_name} JOIN #{ActsAsTaggableOn::Tag.table_name} ON #{ActsAsTaggableOn::Tagging.table_name}.tag_id = #{ActsAsTaggableOn::Tag.table_name}.#{ActsAsTaggableOn::Tag.primary_key} AND (#{tags_conditions}) WHERE #{ActsAsTaggableOn::Tagging.table_name}.taggable_type = #{quote_value(base_class.name)})"
83
86
 
84
87
  elsif options.delete(:any)
85
88
  # get tags, drop out if nothing returned (we need at least one)
@@ -89,7 +92,7 @@ module ActsAsTaggableOn::Taggable
89
92
  # setup taggings alias so we can chain, ex: items_locations_taggings_awesome_cool_123
90
93
  # avoid ambiguous column name
91
94
  taggings_context = context ? "_#{context}" : ''
92
-
95
+
93
96
  #TODO: fix alias to be smaller
94
97
  taggings_alias = "#{alias_base_name}#{taggings_context}_taggings_#{tags.map(&:safe_name).join('_')}_#{rand(1024)}"
95
98
 
@@ -119,6 +122,15 @@ module ActsAsTaggableOn::Taggable
119
122
  " AND #{taggings_alias}.tag_id = #{tag.id}"
120
123
  tagging_join << " AND " + sanitize_sql(["#{taggings_alias}.context = ?", context.to_s]) if context
121
124
 
125
+ if owned_by
126
+ tagging_join << " AND " +
127
+ sanitize_sql([
128
+ "#{taggings_alias}.tagger_id = ? AND #{taggings_alias}.tagger_type = ?",
129
+ owned_by.id,
130
+ owned_by.class.to_s
131
+ ])
132
+ end
133
+
122
134
  joins << tagging_join
123
135
  end
124
136
  end
@@ -146,8 +158,8 @@ module ActsAsTaggableOn::Taggable
146
158
  def is_taggable?
147
159
  true
148
160
  end
149
- end
150
-
161
+ end
162
+
151
163
  module InstanceMethods
152
164
  # all column names are necessary for PostgreSQL group clause
153
165
  def grouped_column_names_for(object)
@@ -200,7 +212,7 @@ module ActsAsTaggableOn::Taggable
200
212
 
201
213
  opts = ["#{tagging_table_name}.context = ?", context.to_s]
202
214
  scope = base_tags.where(opts)
203
-
215
+
204
216
  if ActsAsTaggableOn::Tag.using_postgresql?
205
217
  group_columns = grouped_column_names_for(ActsAsTaggableOn::Tag)
206
218
  scope = scope.order("max(#{tagging_table_name}.created_at)").group(group_columns)
@@ -233,7 +245,7 @@ module ActsAsTaggableOn::Taggable
233
245
  instance_variable_set("@#{context.to_s.singularize}_list", nil)
234
246
  instance_variable_set("@all_#{context.to_s.singularize}_list", nil)
235
247
  end
236
-
248
+
237
249
  super(*args)
238
250
  end
239
251
 
@@ -249,14 +261,14 @@ module ActsAsTaggableOn::Taggable
249
261
  current_tags = tags_on(context)
250
262
  old_tags = current_tags - tag_list
251
263
  new_tags = tag_list - current_tags
252
-
264
+
253
265
  # Find taggings to remove:
254
266
  old_taggings = taggings.where(:tagger_type => nil, :tagger_id => nil,
255
267
  :context => context.to_s, :tag_id => old_tags).all
256
268
 
257
269
  if old_taggings.present?
258
270
  # Destroy old taggings:
259
- ActsAsTaggableOn::Tagging.destroy_all :id => old_taggings.map(&:id)
271
+ ActsAsTaggableOn::Tagging.destroy_all "#{ActsAsTaggableOn::Tagging.primary_key}".to_sym => old_taggings.map(&:id)
260
272
  end
261
273
 
262
274
  # Create new taggings:
@@ -5,7 +5,7 @@ module ActsAsTaggableOn::Taggable
5
5
  base.extend ActsAsTaggableOn::Taggable::Related::ClassMethods
6
6
  base.initialize_acts_as_taggable_on_related
7
7
  end
8
-
8
+
9
9
  module ClassMethods
10
10
  def initialize_acts_as_taggable_on_related
11
11
  tag_types.map(&:to_s).each do |tag_type|
@@ -20,7 +20,7 @@ module ActsAsTaggableOn::Taggable
20
20
  end
21
21
  )
22
22
  end
23
-
23
+
24
24
  unless tag_types.empty?
25
25
  class_eval %(
26
26
  def find_matching_contexts(search_context, result_context, options = {})
@@ -31,43 +31,43 @@ module ActsAsTaggableOn::Taggable
31
31
  matching_contexts_for(search_context.to_s, result_context.to_s, klass, options)
32
32
  end
33
33
  )
34
- end
34
+ end
35
35
  end
36
-
36
+
37
37
  def acts_as_taggable_on(*args)
38
38
  super(*args)
39
39
  initialize_acts_as_taggable_on_related
40
40
  end
41
41
  end
42
-
42
+
43
43
  module InstanceMethods
44
44
  def matching_contexts_for(search_context, result_context, klass, options = {})
45
45
  tags_to_find = tags_on(search_context).collect { |t| t.name }
46
46
 
47
- exclude_self = "#{klass.table_name}.id != #{id} AND" if self.class == klass
48
-
47
+ exclude_self = "#{klass.table_name}.#{klass.primary_key} != #{id} AND" if [self.class.base_class, self.class].include? klass
48
+
49
49
  group_columns = ActsAsTaggableOn::Tag.using_postgresql? ? grouped_column_names_for(klass) : "#{klass.table_name}.#{klass.primary_key}"
50
-
51
- klass.scoped({ :select => "#{klass.table_name}.*, COUNT(#{ActsAsTaggableOn::Tag.table_name}.id) AS count",
50
+
51
+ klass.scoped({ :select => "#{klass.table_name}.*, COUNT(#{ActsAsTaggableOn::Tag.table_name}.#{ActsAsTaggableOn::Tag.primary_key}) AS count",
52
52
  :from => "#{klass.table_name}, #{ActsAsTaggableOn::Tag.table_name}, #{ActsAsTaggableOn::Tagging.table_name}",
53
- :conditions => ["#{exclude_self} #{klass.table_name}.id = #{ActsAsTaggableOn::Tagging.table_name}.taggable_id AND #{ActsAsTaggableOn::Tagging.table_name}.taggable_type = '#{klass.to_s}' AND #{ActsAsTaggableOn::Tagging.table_name}.tag_id = #{ActsAsTaggableOn::Tag.table_name}.id AND #{ActsAsTaggableOn::Tag.table_name}.name IN (?) AND #{ActsAsTaggableOn::Tagging.table_name}.context = ?", tags_to_find, result_context],
53
+ :conditions => ["#{exclude_self} #{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],
54
54
  :group => group_columns,
55
55
  :order => "count DESC" }.update(options))
56
56
  end
57
-
57
+
58
58
  def related_tags_for(context, klass, options = {})
59
59
  tags_to_find = tags_on(context).collect { |t| t.name }
60
60
 
61
- exclude_self = "#{klass.table_name}.id != #{id} AND" if self.class == klass
61
+ exclude_self = "#{klass.table_name}.#{klass.primary_key} != #{id} AND" if [self.class.base_class, self.class].include? klass
62
62
 
63
63
  group_columns = ActsAsTaggableOn::Tag.using_postgresql? ? grouped_column_names_for(klass) : "#{klass.table_name}.#{klass.primary_key}"
64
64
 
65
- klass.scoped({ :select => "#{klass.table_name}.*, COUNT(#{ActsAsTaggableOn::Tag.table_name}.id) AS count",
65
+ klass.scoped({ :select => "#{klass.table_name}.*, COUNT(#{ActsAsTaggableOn::Tag.table_name}.#{ActsAsTaggableOn::Tag.primary_key}) AS count",
66
66
  :from => "#{klass.table_name}, #{ActsAsTaggableOn::Tag.table_name}, #{ActsAsTaggableOn::Tagging.table_name}",
67
- :conditions => ["#{exclude_self} #{klass.table_name}.id = #{ActsAsTaggableOn::Tagging.table_name}.taggable_id AND #{ActsAsTaggableOn::Tagging.table_name}.taggable_type = '#{klass.to_s}' AND #{ActsAsTaggableOn::Tagging.table_name}.tag_id = #{ActsAsTaggableOn::Tag.table_name}.id AND #{ActsAsTaggableOn::Tag.table_name}.name IN (?)", tags_to_find],
67
+ :conditions => ["#{exclude_self} #{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],
68
68
  :group => group_columns,
69
69
  :order => "count DESC" }.update(options))
70
70
  end
71
71
  end
72
72
  end
73
- end
73
+ end
@@ -28,20 +28,11 @@ module ActsAsTaggableOn
28
28
  tag_types = tag_types.to_a.flatten.compact.map(&:to_sym)
29
29
 
30
30
  if taggable?
31
- if RAILS_3
32
- self.tag_types = (self.tag_types + tag_types).uniq
33
- else
34
- write_inheritable_attribute(:tag_types, (self.tag_types + tag_types).uniq)
35
- end
31
+ self.tag_types = (self.tag_types + tag_types).uniq
36
32
  else
37
- if RAILS_3
38
33
  class_attribute :tag_types
39
34
  self.tag_types = tag_types
40
- else
41
- write_inheritable_attribute(:tag_types, tag_types)
42
- class_inheritable_reader(:tag_types)
43
- end
44
-
35
+
45
36
  class_eval do
46
37
  has_many :taggings, :as => :taggable, :dependent => :destroy, :include => :tag, :class_name => "ActsAsTaggableOn::Tagging"
47
38
  has_many :base_tags, :through => :taggings, :source => :tag, :class_name => "ActsAsTaggableOn::Tag"
@@ -49,8 +40,8 @@ module ActsAsTaggableOn
49
40
  def self.taggable?
50
41
  true
51
42
  end
52
-
53
- include ActsAsTaggableOn::Utils
43
+
44
+ include ActsAsTaggableOn::Utils
54
45
  include ActsAsTaggableOn::Taggable::Core
55
46
  include ActsAsTaggableOn::Taggable::Collection
56
47
  include ActsAsTaggableOn::Taggable::Cache
@@ -1,8 +1,7 @@
1
1
  module ActsAsTaggableOn
2
2
  class Tag < ::ActiveRecord::Base
3
- include ActsAsTaggableOn::ActiveRecord::Backports if ::ActiveRecord::VERSION::MAJOR < 3
4
3
  include ActsAsTaggableOn::Utils
5
-
4
+
6
5
  attr_accessible :name
7
6
 
8
7
  ### ASSOCIATIONS:
@@ -15,21 +14,21 @@ module ActsAsTaggableOn
15
14
  validates_uniqueness_of :name
16
15
 
17
16
  ### SCOPES:
18
-
17
+
19
18
  def self.named(name)
20
- where(["name #{like_operator} ?", escape_like(name)])
19
+ where(["name #{like_operator} ? ESCAPE '!'", escape_like(name)])
21
20
  end
22
-
21
+
23
22
  def self.named_any(list)
24
- where(list.map { |tag| sanitize_sql(["name #{like_operator} ?", escape_like(tag.to_s)]) }.join(" OR "))
23
+ where(list.map { |tag| sanitize_sql(["name #{like_operator} ? ESCAPE '!'", escape_like(tag.to_s)]) }.join(" OR "))
25
24
  end
26
-
25
+
27
26
  def self.named_like(name)
28
- where(["name #{like_operator} ?", "%#{escape_like(name)}%"])
27
+ where(["name #{like_operator} ? ESCAPE '!'", "%#{escape_like(name)}%"])
29
28
  end
30
29
 
31
30
  def self.named_like_any(list)
32
- where(list.map { |tag| sanitize_sql(["name #{like_operator} ?", "%#{escape_like(tag.to_s)}%"]) }.join(" OR "))
31
+ where(list.map { |tag| sanitize_sql(["name #{like_operator} ? ESCAPE '!'", "%#{escape_like(tag.to_s)}%"]) }.join(" OR "))
33
32
  end
34
33
 
35
34
  ### CLASS METHODS:
@@ -44,7 +43,7 @@ module ActsAsTaggableOn
44
43
  return [] if list.empty?
45
44
 
46
45
  existing_tags = Tag.named_any(list).all
47
- new_tag_names = list.reject do |name|
46
+ new_tag_names = list.reject do |name|
48
47
  name = comparable_name(name)
49
48
  existing_tags.any? { |tag| comparable_name(tag.name) == name }
50
49
  end
@@ -66,13 +65,13 @@ module ActsAsTaggableOn
66
65
  def count
67
66
  read_attribute(:count).to_i
68
67
  end
69
-
68
+
70
69
  def safe_name
71
70
  name.gsub(/[^a-zA-Z0-9]/, '')
72
71
  end
73
-
72
+
74
73
  class << self
75
- private
74
+ private
76
75
  def comparable_name(str)
77
76
  RUBY_VERSION >= "1.9" ? str.downcase : str.mb_chars.downcase
78
77
  end
@@ -1,7 +1,5 @@
1
1
  module ActsAsTaggableOn
2
2
  class Tagging < ::ActiveRecord::Base #:nodoc:
3
- include ActsAsTaggableOn::ActiveRecord::Backports if ::ActiveRecord::VERSION::MAJOR < 3
4
-
5
3
  attr_accessible :tag,
6
4
  :tag_id,
7
5
  :context,
@@ -1,29 +1,28 @@
1
1
  module ActsAsTaggableOn
2
2
  module Utils
3
3
  def self.included(base)
4
-
4
+
5
5
  base.send :include, ActsAsTaggableOn::Utils::OverallMethods
6
- base.extend ActsAsTaggableOn::Utils::OverallMethods
6
+ base.extend ActsAsTaggableOn::Utils::OverallMethods
7
7
  end
8
8
 
9
9
  module OverallMethods
10
10
  def using_postgresql?
11
11
  ::ActiveRecord::Base.connection && ::ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
12
12
  end
13
-
13
+
14
14
  def using_sqlite?
15
15
  ::ActiveRecord::Base.connection && ::ActiveRecord::Base.connection.adapter_name == 'SQLite'
16
- end
17
-
16
+ end
17
+
18
18
  private
19
19
  def like_operator
20
20
  using_postgresql? ? 'ILIKE' : 'LIKE'
21
21
  end
22
-
22
+
23
23
  # escape _ and % characters in strings, since these are wildcards in SQL.
24
24
  def escape_like(str)
25
- return str if using_sqlite? # skip escaping for SQLite
26
- str.to_s.gsub("_", "\\\_").gsub("%", "\\\%")
25
+ str.gsub(/[!%_]/){ |x| '!' + x }
27
26
  end
28
27
  end
29
28
 
@@ -1,3 +1,4 @@
1
+ require 'rails/generators'
1
2
  require 'rails/generators/migration'
2
3
 
3
4
  module ActsAsTaggableOn
@@ -18,8 +19,14 @@ module ActsAsTaggableOn
18
19
  [:active_record].include? orm
19
20
  end
20
21
 
21
- def self.next_migration_number(path)
22
- ActiveRecord::Generators::Base.next_migration_number(path)
22
+ def self.next_migration_number(dirname)
23
+ if ActiveRecord::Base.timestamped_migrations
24
+ migration_number = Time.now.utc.strftime("%Y%m%d%H%M%S").to_i
25
+ migration_number += 1
26
+ migration_number.to_s
27
+ else
28
+ "%.3d" % (current_migration_number(dirname) + 1)
29
+ end
23
30
  end
24
31
 
25
32
  def create_migration_file