acts-as-taggable-on 2.3.3 → 2.4.1

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 (44) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +3 -3
  3. data/Appraisals +7 -0
  4. data/Gemfile +3 -1
  5. data/{MIT-LICENSE → LICENSE.md} +1 -1
  6. data/README.md +303 -0
  7. data/Rakefile +2 -2
  8. data/acts-as-taggable-on.gemspec +25 -18
  9. data/gemfiles/rails_3.gemfile +8 -0
  10. data/gemfiles/rails_4.gemfile +8 -0
  11. data/lib/acts-as-taggable-on.rb +5 -0
  12. data/lib/acts_as_taggable_on/acts_as_taggable_on/cache.rb +2 -2
  13. data/lib/acts_as_taggable_on/acts_as_taggable_on/collection.rb +64 -7
  14. data/lib/acts_as_taggable_on/acts_as_taggable_on/compatibility.rb +34 -0
  15. data/lib/acts_as_taggable_on/acts_as_taggable_on/core.rb +86 -55
  16. data/lib/acts_as_taggable_on/acts_as_taggable_on/dirty.rb +3 -3
  17. data/lib/acts_as_taggable_on/acts_as_taggable_on/ownership.rb +24 -15
  18. data/lib/acts_as_taggable_on/acts_as_taggable_on/related.rb +32 -21
  19. data/lib/acts_as_taggable_on/tag.rb +37 -15
  20. data/lib/acts_as_taggable_on/tag_list.rb +2 -2
  21. data/lib/acts_as_taggable_on/taggable.rb +10 -7
  22. data/lib/acts_as_taggable_on/tagger.rb +12 -3
  23. data/lib/acts_as_taggable_on/tagging.rb +2 -2
  24. data/lib/acts_as_taggable_on/tags_helper.rb +0 -2
  25. data/lib/{acts-as-taggable-on → acts_as_taggable_on}/version.rb +1 -1
  26. data/spec/acts_as_taggable_on/acts_as_taggable_on_spec.rb +1 -183
  27. data/spec/acts_as_taggable_on/acts_as_tagger_spec.rb +8 -8
  28. data/spec/acts_as_taggable_on/related_spec.rb +143 -0
  29. data/spec/acts_as_taggable_on/single_table_inheritance_spec.rb +187 -0
  30. data/spec/acts_as_taggable_on/tag_list_spec.rb +4 -4
  31. data/spec/acts_as_taggable_on/tag_spec.rb +61 -3
  32. data/spec/acts_as_taggable_on/taggable_spec.rb +178 -98
  33. data/spec/acts_as_taggable_on/tagger_spec.rb +32 -33
  34. data/spec/acts_as_taggable_on/tagging_spec.rb +1 -1
  35. data/spec/acts_as_taggable_on/tags_helper_spec.rb +2 -2
  36. data/spec/acts_as_taggable_on/utils_spec.rb +2 -2
  37. data/spec/models.rb +8 -2
  38. data/spec/schema.rb +1 -1
  39. data/spec/spec_helper.rb +7 -4
  40. metadata +101 -56
  41. data/CHANGELOG +0 -35
  42. data/README.rdoc +0 -244
  43. data/rails/init.rb +0 -1
  44. data/uninstall.rb +0 -1
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d0b7cd2b4e279238a0fb8464721b40c3dbedaacd
4
+ data.tar.gz: edd07332dcbd884be1daab9e65d9be28b6f3f695
5
+ SHA512:
6
+ metadata.gz: 65f468a535ce620033df88d152bd5e1bff722c01a197d233794da097e682b29ad5f4ebf46b4eb46a18957d2ecda9f6699aad27b42789a2c8ee8d7c4f68e985ba
7
+ data.tar.gz: 2fd5249aa31f826fd56666dd3d07c4a63df1803722fc7e29c02b39fdbff6f0459316f30a1bb9345c53e885fa5a0edfd30495e211e8e6e90630600d9d60cd69a5
data/.gitignore CHANGED
@@ -2,10 +2,10 @@
2
2
  *.sqlite3
3
3
  /pkg/*
4
4
  .bundle
5
- .rvmrc
6
- Gemfile.lock
5
+ .ruby-version
7
6
  spec/database.yml
8
7
  tmp*.sw?
9
8
  *.sw?
10
9
  tmp
11
- *.gem
10
+ *.gem
11
+ *.lock
data/Appraisals ADDED
@@ -0,0 +1,7 @@
1
+ appraise "rails-3" do
2
+ gem "rails", "3.2.13"
3
+ end
4
+
5
+ appraise "rails-4" do
6
+ gem "rails", "4.0.0.beta1"
7
+ end
data/Gemfile CHANGED
@@ -1,3 +1,5 @@
1
- source 'http://rubygems.org'
1
+ source 'https://rubygems.org'
2
+
2
3
  gemspec
3
4
 
5
+ gem 'appraisal'
@@ -1,4 +1,4 @@
1
- Copyright (c) 2007 Michael Bleigh and Intridea Inc.
1
+ __Copyright (c) 2007 Michael Bleigh and Intridea Inc.__
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.md ADDED
@@ -0,0 +1,303 @@
1
+ # ActsAsTaggableOn
2
+ [![Build Status](https://secure.travis-ci.org/mbleigh/acts-as-taggable-on.png)](http://travis-ci.org/mbleigh/acts-as-taggable-on)
3
+
4
+ This plugin was originally based on Acts as Taggable on Steroids by Jonathan Viney.
5
+ It has evolved substantially since that point, but all credit goes to him for the
6
+ initial tagging functionality that so many people have used.
7
+
8
+ For instance, in a social network, a user might have tags that are called skills,
9
+ interests, sports, and more. There is no real way to differentiate between tags and
10
+ so an implementation of this type is not possible with acts as taggable on steroids.
11
+
12
+ Enter Acts as Taggable On. Rather than tying functionality to a specific keyword
13
+ (namely `tags`), acts as taggable on allows you to specify an arbitrary number of
14
+ tag "contexts" that can be used locally or in combination in the same way steroids
15
+ was used.
16
+
17
+ ## Compatibility
18
+
19
+ Versions 2.x are compatible with Ruby 1.8.7+ and Rails 3.
20
+
21
+ Versions 2.4.1 and up are compatible with Rails 4 too (thanks to arabonradar and cwoodcox).
22
+
23
+ Versions 3.x (currently unreleased) are compatible with Ruby 1.9.3+ and Rails 3 and 4.
24
+
25
+ For an up-to-date roadmap, see https://github.com/mbleigh/acts-as-taggable-on/issues/milestones
26
+
27
+ ## Installation
28
+
29
+ To use it, add it to your Gemfile:
30
+
31
+ ```ruby
32
+ gem 'acts-as-taggable-on'
33
+ ```
34
+
35
+ and bundle:
36
+
37
+ ```ruby
38
+ bundle
39
+ ```
40
+
41
+ #### Post Installation
42
+
43
+ ```shell
44
+ rails generate acts_as_taggable_on:migration
45
+ rake db:migrate
46
+ ```
47
+
48
+ ## Testing
49
+
50
+ Acts As Taggable On uses RSpec for its test coverage. Inside the gem
51
+ directory, you can run the specs with:
52
+
53
+ ```shell
54
+ bundle
55
+ rake spec
56
+ ```
57
+
58
+ If you want, add a `.ruby-version` file in the project root (and use rbenv or RVM) to work on a specific version of Ruby.
59
+
60
+ ## Usage
61
+
62
+ ```ruby
63
+ class User < ActiveRecord::Base
64
+ # Alias for acts_as_taggable_on :tags
65
+ acts_as_taggable
66
+ acts_as_taggable_on :skills, :interests
67
+ end
68
+
69
+ @user = User.new(:name => "Bobby")
70
+ @user.tag_list = "awesome, slick, hefty" # this should be familiar
71
+ @user.skill_list = "joking, clowning, boxing" # but you can do it for any context!
72
+
73
+ @user.tags # => [<Tag name:"awesome">,<Tag name:"slick">,<Tag name:"hefty">]
74
+ @user.skills # => [<Tag name:"joking">,<Tag name:"clowning">,<Tag name:"boxing">]
75
+ @user.skill_list # => ["joking","clowning","boxing"] as TagList
76
+
77
+ @user.tag_list.remove("awesome") # remove a single tag
78
+ @user.tag_list.remove("awesome, slick") # works with arrays too
79
+ @user.tag_list.add("awesomer") # add a single tag. alias for <<
80
+ @user.tag_list.add("awesomer, slicker") # also works with arrays
81
+
82
+ User.skill_counts # => [<Tag name="joking" count=2>,<Tag name="clowning" count=1>...]
83
+ ```
84
+
85
+ To preserve the order in which tags are created use `acts_as_ordered_taggable`:
86
+
87
+ ```ruby
88
+ class User < ActiveRecord::Base
89
+ # Alias for acts_as_ordered_taggable_on :tags
90
+ acts_as_ordered_taggable
91
+ acts_as_ordered_taggable_on :skills, :interests
92
+ end
93
+
94
+ @user = User.new(:name => "Bobby")
95
+ @user.tag_list = "east, south"
96
+ @user.save
97
+
98
+ @user.tag_list = "north, east, south, west"
99
+ @user.save
100
+
101
+ @user.reload
102
+ @user.tag_list # => ["north", "east", "south", "west"]
103
+ ```
104
+
105
+ ### Finding Tagged Objects
106
+
107
+ Acts As Taggable On uses scopes to create an association for tags.
108
+ This way you can mix and match to filter down your results.
109
+
110
+ ```ruby
111
+ class User < ActiveRecord::Base
112
+ acts_as_taggable_on :tags, :skills
113
+ scope :by_join_date, order("created_at DESC")
114
+ end
115
+
116
+ User.tagged_with("awesome").by_join_date
117
+ User.tagged_with("awesome").by_join_date.paginate(:page => params[:page], :per_page => 20)
118
+
119
+ # Find a user with matching all tags, not just one
120
+ User.tagged_with(["awesome", "cool"], :match_all => true)
121
+
122
+ # Find a user with any of the tags:
123
+ User.tagged_with(["awesome", "cool"], :any => true)
124
+
125
+ # Find a user that not tags with awesome or cool:
126
+ User.tagged_with(["awesome", "cool"], :exclude => true)
127
+
128
+ # Find a user with any of tags based on context:
129
+ User.tagged_with(['awesome, cool'], :on => :tags, :any => true).tagged_with(['smart', 'shy'], :on => :skills, :any => true)
130
+ ```
131
+
132
+ You can also use `:wild => true` option along with `:any` or `:exclude` option. It will looking for `%awesome%` and `%cool%` in sql.
133
+
134
+ __Tip:__ `User.tagged_with([])` or '' will return `[]`, but not all records.
135
+
136
+ ### Relationships
137
+
138
+ You can find objects of the same type based on similar tags on certain contexts.
139
+ Also, objects will be returned in descending order based on the total number of
140
+ matched tags.
141
+
142
+ ```ruby
143
+ @bobby = User.find_by_name("Bobby")
144
+ @bobby.skill_list # => ["jogging", "diving"]
145
+
146
+ @frankie = User.find_by_name("Frankie")
147
+ @frankie.skill_list # => ["hacking"]
148
+
149
+ @tom = User.find_by_name("Tom")
150
+ @tom.skill_list # => ["hacking", "jogging", "diving"]
151
+
152
+ @tom.find_related_skills # => [<User name="Bobby">,<User name="Frankie">]
153
+ @bobby.find_related_skills # => [<User name="Tom">]
154
+ @frankie.find_related_skills # => [<User name="Tom">]
155
+ ```
156
+
157
+ ### Dynamic Tag Contexts
158
+
159
+ In addition to the generated tag contexts in the definition, it is also possible
160
+ to allow for dynamic tag contexts (this could be user generated tag contexts!)
161
+
162
+ ```ruby
163
+ @user = User.new(:name => "Bobby")
164
+ @user.set_tag_list_on(:customs, "same, as, tag, list")
165
+ @user.tag_list_on(:customs) # => ["same","as","tag","list"]
166
+ @user.save
167
+ @user.tags_on(:customs) # => [<Tag name='same'>,...]
168
+ @user.tag_counts_on(:customs)
169
+ User.tagged_with("same", :on => :customs) # => [@user]
170
+ ```
171
+
172
+ ### Tag Ownership
173
+
174
+ Tags can have owners:
175
+
176
+ ```ruby
177
+ class User < ActiveRecord::Base
178
+ acts_as_tagger
179
+ end
180
+
181
+ class Photo < ActiveRecord::Base
182
+ acts_as_taggable_on :locations
183
+ end
184
+
185
+ @some_user.tag(@some_photo, :with => "paris, normandy", :on => :locations)
186
+ @some_user.owned_taggings
187
+ @some_user.owned_tags
188
+ Photo.tagged_with("paris", :on => :locations, :owned_by => @some_user)
189
+ @some_photo.locations_from(@some_user) # => ["paris", "normandy"]
190
+ @some_photo.owner_tags_on(@some_user, :locations) # => [#<ActsAsTaggableOn::Tag id: 1, name: "paris">...]
191
+ @some_photo.owner_tags_on(nil, :locations) # => Ownerships equivalent to saying @some_photo.locations
192
+ @some_user.tag(@some_photo, :with => "paris, normandy", :on => :locations, :skip_save => true) #won't save @some_photo object
193
+ ```
194
+
195
+ ### Dirty objects
196
+
197
+ ```ruby
198
+ @bobby = User.find_by_name("Bobby")
199
+ @bobby.skill_list # => ["jogging", "diving"]
200
+
201
+ @bobby.skill_list_changed? #=> false
202
+ @bobby.changes #=> {}
203
+
204
+ @bobby.skill_list = "swimming"
205
+ @bobby.changes.should == {"skill_list"=>["jogging, diving", ["swimming"]]}
206
+ @bobby.skill_list_changed? #=> true
207
+
208
+ @bobby.skill_list_change.should == ["jogging, diving", ["swimming"]]
209
+ ```
210
+
211
+ ### Tag cloud calculations
212
+
213
+ To construct tag clouds, the frequency of each tag needs to be calculated.
214
+ Because we specified `acts_as_taggable_on` on the `User` class, we can
215
+ get a calculation of all the tag counts by using `User.tag_counts_on(:customs)`. But what if we wanted a tag count for
216
+ an single user's posts? To achieve this we call tag_counts on the association:
217
+
218
+ ```ruby
219
+ User.find(:first).posts.tag_counts_on(:tags)
220
+ ```
221
+
222
+ A helper is included to assist with generating tag clouds.
223
+
224
+ Here is an example that generates a tag cloud.
225
+
226
+ Helper:
227
+
228
+ ```ruby
229
+ module PostsHelper
230
+ include ActsAsTaggableOn::TagsHelper
231
+ end
232
+ ```
233
+
234
+ Controller:
235
+
236
+ ```ruby
237
+ class PostController < ApplicationController
238
+ def tag_cloud
239
+ @tags = Post.tag_counts_on(:tags)
240
+ end
241
+ end
242
+ ```
243
+
244
+ View:
245
+
246
+ ```erb
247
+ <% tag_cloud(@tags, %w(css1 css2 css3 css4)) do |tag, css_class| %>
248
+ <%= link_to tag.name, { :action => :tag, :id => tag.name }, :class => css_class %>
249
+ <% end %>
250
+ ```
251
+
252
+ CSS:
253
+
254
+ ```css
255
+ .css1 { font-size: 1.0em; }
256
+ .css2 { font-size: 1.2em; }
257
+ .css3 { font-size: 1.4em; }
258
+ .css4 { font-size: 1.6em; }
259
+ ```
260
+
261
+ ## Configuration
262
+
263
+ If you would like to remove unused tag objects after removing taggings, add:
264
+
265
+ ```ruby
266
+ ActsAsTaggableOn.remove_unused_tags = true
267
+ ```
268
+
269
+ If you want force tags to be saved downcased:
270
+
271
+ ```ruby
272
+ ActsAsTaggableOn.force_lowercase = true
273
+ ```
274
+
275
+ If you want tags to be saved parametrized (you can redefine to_param as well):
276
+
277
+ ```ruby
278
+ ActsAsTaggableOn.force_parameterize = true
279
+ ```
280
+
281
+ If you would like tags to be case-sensitive and not use LIKE queries for creation:
282
+
283
+ ```ruby
284
+ ActsAsTaggableOn.strict_case_match = true
285
+ ```
286
+
287
+ If you want to change the default delimiter (it defaults to ','). You can also pass in an array of delimiters such as ([',', '|']):
288
+
289
+ ```ruby
290
+ ActsAsTaggableOn.delimiter = ','
291
+ ```
292
+
293
+ ## Contributors
294
+
295
+ We have a long list of valued contributors. [Check them all](https://github.com/mbleigh/acts-as-taggable-on/contributors)
296
+
297
+ ## Maintainer
298
+
299
+ * [Joost Baaij](https://github.com/tilsammans)
300
+
301
+ ## License
302
+
303
+ See [LICENSE](https://github.com/mbleigh/acts-as-taggable-on/blob/master/LICENSE.md)
data/Rakefile CHANGED
@@ -1,6 +1,6 @@
1
1
  require 'rubygems'
2
- require 'bundler'
3
- Bundler.setup :default, :development
2
+ require 'bundler/setup'
3
+ require 'appraisal'
4
4
 
5
5
  desc 'Default: run specs'
6
6
  task :default => :spec
@@ -1,28 +1,35 @@
1
- $:.push File.dirname(__FILE__) + '/lib'
2
- require 'acts-as-taggable-on/version'
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'acts_as_taggable_on/version'
3
5
 
4
6
  Gem::Specification.new do |gem|
5
- gem.name = %q{acts-as-taggable-on}
6
- gem.authors = ["Michael Bleigh"]
7
- gem.date = %q{2012-07-16}
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
- gem.summary = "Advanced tagging for Rails."
10
- gem.email = %q{michael@intridea.com}
11
- gem.homepage = ''
7
+ gem.name = "acts-as-taggable-on"
8
+ gem.version = ActsAsTaggableOn::VERSION
9
+ gem.authors = ["Michael Bleigh", "Joost Baaij"]
10
+ gem.email = ["michael@intridea.com", "joost@spacebabies.nl"]
11
+ 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.}
12
+ gem.summary = "Advanced tagging for Rails."
13
+ gem.homepage = 'https://github.com/mbleigh/acts-as-taggable-on'
14
+ gem.license = "MIT"
15
+
16
+ gem.files = `git ls-files`.split($/)
17
+ gem.executables = gem.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
19
+ gem.require_paths = ["lib"]
20
+
21
+ if File.exists?('UPGRADING')
22
+ gem.post_install_message = File.read('UPGRADING')
23
+ end
12
24
 
13
- gem.add_runtime_dependency 'rails', '~> 3.0'
25
+ gem.add_runtime_dependency 'rails', ['>= 3', '< 5']
26
+
27
+ gem.add_development_dependency 'rspec-rails', '2.13.0' # 2.13.1 is broken
14
28
  gem.add_development_dependency 'rspec', '~> 2.6'
15
- gem.add_development_dependency 'ammeter', '~> 0.1.3'
29
+ gem.add_development_dependency 'ammeter'
16
30
  gem.add_development_dependency 'sqlite3'
17
31
  gem.add_development_dependency 'mysql2', '~> 0.3.7'
18
32
  gem.add_development_dependency 'pg'
19
33
  gem.add_development_dependency 'guard'
20
34
  gem.add_development_dependency 'guard-rspec'
21
-
22
- gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
23
- gem.files = `git ls-files`.split("\n")
24
- gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
25
- gem.name = "acts-as-taggable-on"
26
- gem.require_paths = ['lib']
27
- gem.version = ActsAsTaggableOn::VERSION
28
35
  end
@@ -0,0 +1,8 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "appraisal"
6
+ gem "rails", "3.2.13"
7
+
8
+ gemspec :path=>"../"
@@ -0,0 +1,8 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "appraisal"
6
+ gem "rails", :github => 'rails/rails'
7
+
8
+ gemspec :path=>"../"
@@ -16,6 +16,9 @@ module ActsAsTaggableOn
16
16
  mattr_accessor :force_parameterize
17
17
  @@force_parameterize = false
18
18
 
19
+ mattr_accessor :strict_case_match
20
+ @@strict_case_match = false
21
+
19
22
  mattr_accessor :remove_unused_tags
20
23
  self.remove_unused_tags = false
21
24
 
@@ -33,6 +36,7 @@ end
33
36
  require "acts_as_taggable_on/utils"
34
37
 
35
38
  require "acts_as_taggable_on/taggable"
39
+ require "acts_as_taggable_on/acts_as_taggable_on/compatibility"
36
40
  require "acts_as_taggable_on/acts_as_taggable_on/core"
37
41
  require "acts_as_taggable_on/acts_as_taggable_on/collection"
38
42
  require "acts_as_taggable_on/acts_as_taggable_on/cache"
@@ -50,6 +54,7 @@ $LOAD_PATH.shift
50
54
 
51
55
 
52
56
  if defined?(ActiveRecord::Base)
57
+ ActiveRecord::Base.extend ActsAsTaggableOn::Compatibility
53
58
  ActiveRecord::Base.extend ActsAsTaggableOn::Taggable
54
59
  ActiveRecord::Base.send :include, ActsAsTaggableOn::Tagger
55
60
  end
@@ -17,11 +17,11 @@ module ActsAsTaggableOn::Taggable
17
17
  module ClassMethods
18
18
  def initialize_acts_as_taggable_on_cache
19
19
  tag_types.map(&:to_s).each do |tag_type|
20
- class_eval %(
20
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
21
21
  def self.caching_#{tag_type.singularize}_list?
22
22
  caching_tag_list_on?("#{tag_type}")
23
23
  end
24
- )
24
+ RUBY
25
25
  end
26
26
  end
27
27
 
@@ -9,7 +9,7 @@ module ActsAsTaggableOn::Taggable
9
9
  module ClassMethods
10
10
  def initialize_acts_as_taggable_on_collection
11
11
  tag_types.map(&:to_s).each do |tag_type|
12
- class_eval %(
12
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
13
13
  def self.#{tag_type.singularize}_counts(options={})
14
14
  tag_counts_on('#{tag_type}', options)
15
15
  end
@@ -25,7 +25,7 @@ module ActsAsTaggableOn::Taggable
25
25
  def self.top_#{tag_type}(limit = 10)
26
26
  tag_counts_on('#{tag_type}', :order => 'count desc', :limit => limit.to_i)
27
27
  end
28
- )
28
+ RUBY
29
29
  end
30
30
  end
31
31
 
@@ -37,6 +37,60 @@ module ActsAsTaggableOn::Taggable
37
37
  def tag_counts_on(context, options = {})
38
38
  all_tag_counts(options.merge({:on => context.to_s}))
39
39
  end
40
+
41
+ def tags_on(context, options = {})
42
+ all_tags(options.merge({:on => context.to_s}))
43
+ end
44
+
45
+ ##
46
+ # Calculate the tag names.
47
+ # To be used when you don't need tag counts and want to avoid the taggable joins.
48
+ #
49
+ # @param [Hash] options Options:
50
+ # * :start_at - Restrict the tags to those created after a certain time
51
+ # * :end_at - Restrict the tags to those created before a certain time
52
+ # * :conditions - A piece of SQL conditions to add to the query. Note we don't join the taggable objects for performance reasons.
53
+ # * :limit - The maximum number of tags to return
54
+ # * :order - A piece of SQL to order by. Eg 'tags.count desc' or 'taggings.created_at desc'
55
+ # * :on - Scope the find to only include a certain context
56
+ def all_tags(options = {})
57
+ options.assert_valid_keys :start_at, :end_at, :conditions, :order, :limit, :on
58
+
59
+ ## Generate conditions:
60
+ options[:conditions] = sanitize_sql(options[:conditions]) if options[:conditions]
61
+
62
+ start_at_conditions = sanitize_sql(["#{ActsAsTaggableOn::Tagging.table_name}.created_at >= ?", options.delete(:start_at)]) if options[:start_at]
63
+ end_at_conditions = sanitize_sql(["#{ActsAsTaggableOn::Tagging.table_name}.created_at <= ?", options.delete(:end_at)]) if options[:end_at]
64
+
65
+ taggable_conditions = sanitize_sql(["#{ActsAsTaggableOn::Tagging.table_name}.taggable_type = ?", base_class.name])
66
+ taggable_conditions << sanitize_sql([" AND #{ActsAsTaggableOn::Tagging.table_name}.context = ?", options.delete(:on).to_s]) if options[:on]
67
+
68
+ tagging_conditions = [
69
+ taggable_conditions,
70
+ start_at_conditions,
71
+ end_at_conditions
72
+ ].compact.reverse
73
+
74
+ tag_conditions = [
75
+ options[:conditions]
76
+ ].compact.reverse
77
+
78
+ ## Generate scope:
79
+ tagging_scope = ActsAsTaggableOn::Tagging.select("#{ActsAsTaggableOn::Tagging.table_name}.tag_id")
80
+ tag_scope = ActsAsTaggableOn::Tag.select("#{ActsAsTaggableOn::Tag.table_name}.*").order(options[:order]).limit(options[:limit])
81
+
82
+ # Joins and conditions
83
+ tagging_conditions.each { |condition| tagging_scope = tagging_scope.where(condition) }
84
+ tag_conditions.each { |condition| tag_scope = tag_scope.where(condition) }
85
+
86
+ group_columns = "#{ActsAsTaggableOn::Tagging.table_name}.tag_id"
87
+
88
+ # Append the current scope to the scope, because we can't use scope(:find) in RoR 3.0 anymore:
89
+ ids = select("#{table_name}.#{primary_key}").map(&:id)
90
+ tagging_scope = tagging_scope.where("#{ActsAsTaggableOn::Tagging.table_name}.taggable_id IN(?)", ids).group(group_columns)
91
+
92
+ 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")
93
+ end
40
94
 
41
95
  ##
42
96
  # Calculate the tag counts for all tags.
@@ -100,18 +154,21 @@ module ActsAsTaggableOn::Taggable
100
154
  tag_conditions.each { |condition| tag_scope = tag_scope.where(condition) }
101
155
 
102
156
  # GROUP BY and HAVING clauses:
103
- at_least = sanitize_sql(['tags_count >= ?', options.delete(:at_least)]) if options[:at_least]
104
- at_most = sanitize_sql(['tags_count <= ?', options.delete(:at_most)]) if options[:at_most]
157
+ at_least = sanitize_sql(["COUNT(#{ActsAsTaggableOn::Tagging.table_name}.tag_id) >= ?", options.delete(:at_least)]) if options[:at_least]
158
+ at_most = sanitize_sql(["COUNT(#{ActsAsTaggableOn::Tagging.table_name}.tag_id) <= ?", options.delete(:at_most)]) if options[:at_most]
105
159
  having = ["COUNT(#{ActsAsTaggableOn::Tagging.table_name}.tag_id) > 0", at_least, at_most].compact.join(' AND ')
106
160
 
107
161
  group_columns = "#{ActsAsTaggableOn::Tagging.table_name}.tag_id"
108
162
 
109
163
  # Append the current scope to the scope, because we can't use scope(:find) in RoR 3.0 anymore:
110
164
  scoped_select = "#{table_name}.#{primary_key}"
111
- tagging_scope = tagging_scope.where("#{ActsAsTaggableOn::Tagging.table_name}.taggable_id IN(#{select(scoped_select).to_sql})").
112
- group(group_columns).
113
- having(having)
165
+ select_query = "#{select(scoped_select).to_sql}"
166
+
167
+ res = ActiveRecord::Base.connection.select_all(select_query).map { |item| item.values }.flatten.compact.join(",")
168
+ res = "NULL" if res.blank?
114
169
 
170
+ tagging_scope = tagging_scope.where("#{ActsAsTaggableOn::Tagging.table_name}.taggable_id IN(#{res})")
171
+ tagging_scope = tagging_scope.group(group_columns).having(having)
115
172
 
116
173
  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")
117
174
  tag_scope
@@ -0,0 +1,34 @@
1
+ module ActsAsTaggableOn::Compatibility
2
+ def has_many_with_compatibility(name, options = {}, &extention)
3
+ if ActiveRecord::VERSION::MAJOR >= 4
4
+ scope, opts = build_scope_and_options(options)
5
+ has_many(name, scope, opts, &extention)
6
+ else
7
+ has_many(name, options, &extention)
8
+ end
9
+ end
10
+
11
+ def build_scope_and_options(opts)
12
+ scope_opts, opts = parse_options(opts)
13
+
14
+ unless scope_opts.empty?
15
+ scope = lambda do
16
+ scope_opts.inject(self) { |result, hash| result.send *hash }
17
+ end
18
+ end
19
+
20
+ [defined?(scope) ? scope : nil, opts]
21
+ end
22
+
23
+ def parse_options(opts)
24
+ scope_opts = {}
25
+ [:order, :having, :select, :group, :limit, :offset, :readonly].each do |o|
26
+ scope_opts[o] = opts.delete o if opts[o]
27
+ end
28
+ scope_opts[:where] = opts.delete :conditions if opts[:conditions]
29
+ scope_opts[:joins] = opts.delete :include if opts [:include]
30
+ scope_opts[:distinct] = opts.delete :uniq if opts[:uniq]
31
+
32
+ [scope_opts, opts]
33
+ end
34
+ end