acts-as-taggable-on 2.0.0.pre5 → 3.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (102) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +11 -0
  3. data/{spec/spec.opts → .rspec} +0 -0
  4. data/.travis.yml +40 -0
  5. data/Appraisals +16 -0
  6. data/CHANGELOG.md +208 -0
  7. data/CONTRIBUTING.md +44 -0
  8. data/Gemfile +10 -5
  9. data/Guardfile +5 -0
  10. data/{MIT-LICENSE → LICENSE.md} +1 -1
  11. data/README.md +477 -0
  12. data/Rakefile +14 -52
  13. data/UPGRADING.md +8 -0
  14. data/acts-as-taggable-on.gemspec +36 -0
  15. data/{lib/generators/acts_as_taggable_on/migration/templates/active_record/migration.rb → db/migrate/1_acts_as_taggable_on_migration.rb} +5 -3
  16. data/db/migrate/2_add_missing_unique_indices.rb +19 -0
  17. data/db/migrate/3_add_taggings_counter_cache_to_tags.rb +14 -0
  18. data/db/migrate/4_add_missing_taggable_index.rb +9 -0
  19. data/db/migrate/5_change_collation_for_tag_names.rb +9 -0
  20. data/gemfiles/activerecord_3.2.gemfile +15 -0
  21. data/gemfiles/activerecord_4.0.gemfile +15 -0
  22. data/gemfiles/activerecord_4.1.gemfile +15 -0
  23. data/gemfiles/activerecord_4.2.gemfile +16 -0
  24. data/lib/acts-as-taggable-on.rb +117 -22
  25. data/lib/acts_as_taggable_on/compatibility.rb +35 -0
  26. data/lib/acts_as_taggable_on/default_parser.rb +79 -0
  27. data/lib/acts_as_taggable_on/engine.rb +5 -0
  28. data/lib/acts_as_taggable_on/generic_parser.rb +19 -0
  29. data/lib/acts_as_taggable_on/tag.rb +137 -61
  30. data/lib/acts_as_taggable_on/tag_list.rb +96 -75
  31. data/lib/acts_as_taggable_on/tag_list_parser.rb +21 -0
  32. data/lib/acts_as_taggable_on/taggable/cache.rb +86 -0
  33. data/lib/acts_as_taggable_on/taggable/collection.rb +178 -0
  34. data/lib/acts_as_taggable_on/taggable/core.rb +459 -0
  35. data/lib/acts_as_taggable_on/taggable/dirty.rb +36 -0
  36. data/lib/acts_as_taggable_on/taggable/ownership.rb +125 -0
  37. data/lib/acts_as_taggable_on/taggable/related.rb +71 -0
  38. data/lib/acts_as_taggable_on/taggable.rb +102 -0
  39. data/lib/acts_as_taggable_on/tagger.rb +88 -0
  40. data/lib/acts_as_taggable_on/tagging.rb +38 -18
  41. data/lib/acts_as_taggable_on/tags_helper.rb +12 -14
  42. data/lib/acts_as_taggable_on/utils.rb +38 -0
  43. data/lib/acts_as_taggable_on/version.rb +4 -0
  44. data/lib/acts_as_taggable_on.rb +6 -0
  45. data/lib/tasks/tags_collate_utf8.rake +21 -0
  46. data/spec/acts_as_taggable_on/acts_as_taggable_on_spec.rb +205 -195
  47. data/spec/acts_as_taggable_on/acts_as_tagger_spec.rb +79 -81
  48. data/spec/acts_as_taggable_on/caching_spec.rb +83 -0
  49. data/spec/acts_as_taggable_on/default_parser_spec.rb +47 -0
  50. data/spec/acts_as_taggable_on/generic_parser_spec.rb +14 -0
  51. data/spec/acts_as_taggable_on/related_spec.rb +99 -0
  52. data/spec/acts_as_taggable_on/single_table_inheritance_spec.rb +211 -0
  53. data/spec/acts_as_taggable_on/tag_list_parser_spec.rb +46 -0
  54. data/spec/acts_as_taggable_on/tag_list_spec.rb +142 -62
  55. data/spec/acts_as_taggable_on/tag_spec.rb +274 -64
  56. data/spec/acts_as_taggable_on/taggable/dirty_spec.rb +127 -0
  57. data/spec/acts_as_taggable_on/taggable_spec.rb +704 -181
  58. data/spec/acts_as_taggable_on/tagger_spec.rb +134 -56
  59. data/spec/acts_as_taggable_on/tagging_spec.rb +54 -22
  60. data/spec/acts_as_taggable_on/tags_helper_spec.rb +39 -22
  61. data/spec/acts_as_taggable_on/utils_spec.rb +23 -0
  62. data/spec/internal/app/models/altered_inheriting_taggable_model.rb +3 -0
  63. data/spec/internal/app/models/cached_model.rb +3 -0
  64. data/spec/internal/app/models/cached_model_with_array.rb +5 -0
  65. data/spec/internal/app/models/company.rb +15 -0
  66. data/spec/internal/app/models/inheriting_taggable_model.rb +2 -0
  67. data/spec/internal/app/models/market.rb +2 -0
  68. data/spec/internal/app/models/models.rb +90 -0
  69. data/spec/internal/app/models/non_standard_id_taggable_model.rb +8 -0
  70. data/spec/internal/app/models/ordered_taggable_model.rb +4 -0
  71. data/spec/internal/app/models/other_cached_model.rb +3 -0
  72. data/spec/internal/app/models/other_taggable_model.rb +4 -0
  73. data/spec/internal/app/models/student.rb +2 -0
  74. data/spec/internal/app/models/taggable_model.rb +13 -0
  75. data/spec/internal/app/models/untaggable_model.rb +3 -0
  76. data/spec/internal/app/models/user.rb +3 -0
  77. data/spec/internal/config/database.yml.sample +19 -0
  78. data/spec/internal/db/schema.rb +97 -0
  79. data/spec/spec_helper.rb +12 -38
  80. data/spec/support/0-helpers.rb +32 -0
  81. data/spec/support/array.rb +9 -0
  82. data/spec/support/database.rb +42 -0
  83. data/spec/support/database_cleaner.rb +21 -0
  84. metadata +268 -73
  85. data/CHANGELOG +0 -25
  86. data/README.rdoc +0 -212
  87. data/VERSION +0 -1
  88. data/lib/acts_as_taggable_on/acts_as_taggable_on/cache.rb +0 -56
  89. data/lib/acts_as_taggable_on/acts_as_taggable_on/collection.rb +0 -97
  90. data/lib/acts_as_taggable_on/acts_as_taggable_on/core.rb +0 -220
  91. data/lib/acts_as_taggable_on/acts_as_taggable_on/dirty.rb +0 -29
  92. data/lib/acts_as_taggable_on/acts_as_taggable_on/ownership.rb +0 -101
  93. data/lib/acts_as_taggable_on/acts_as_taggable_on/related.rb +0 -64
  94. data/lib/acts_as_taggable_on/acts_as_taggable_on.rb +0 -41
  95. data/lib/acts_as_taggable_on/acts_as_tagger.rb +0 -47
  96. data/lib/acts_as_taggable_on/compatibility/Gemfile +0 -6
  97. data/lib/acts_as_taggable_on/compatibility/active_record_backports.rb +0 -17
  98. data/lib/generators/acts_as_taggable_on/migration/migration_generator.rb +0 -31
  99. data/rails/init.rb +0 -1
  100. data/spec/bm.rb +0 -52
  101. data/spec/models.rb +0 -36
  102. data/spec/schema.rb +0 -42
data/README.rdoc DELETED
@@ -1,212 +0,0 @@
1
- = ActsAsTaggableOn
2
-
3
- This plugin was originally based on Acts as Taggable on Steroids by Jonathan Viney.
4
- It has evolved substantially since that point, but all credit goes to him for the
5
- initial tagging functionality that so many people have used.
6
-
7
- For instance, in a social network, a user might have tags that are called skills,
8
- interests, sports, and more. There is no real way to differentiate between tags and
9
- so an implementation of this type is not possible with acts as taggable on steroids.
10
-
11
- Enter Acts as Taggable On. Rather than tying functionality to a specific keyword
12
- (namely "tags"), acts as taggable on allows you to specify an arbitrary number of
13
- tag "contexts" that can be used locally or in combination in the same way steroids
14
- was used.
15
-
16
- == Installation
17
-
18
- === Plugin
19
-
20
- Acts As Taggable On is available both as a gem and as a traditional plugin. For the
21
- traditional plugin you can install like so (Rails 2.1 or later):
22
-
23
- script/plugin install git://github.com/mbleigh/acts-as-taggable-on.git
24
-
25
- === GemPlugin
26
-
27
- Acts As Taggable On is also available as a gem plugin using Rails 2.1's gem dependencies.
28
- To install the gem, add this to your config/environment.rb:
29
-
30
- config.gem "acts-as-taggable-on", :source => "http://gemcutter.org"
31
-
32
- After that, you can run "rake gems:install" to install the gem if you don't already have it.
33
-
34
- == Rails 3.0
35
-
36
- Acts As Taggable On is now useable in Rails 3.0, thanks to the excellent work of Szymon Nowak
37
- and Jelle Vandebeeck. Because backwards compatibility is hard to maintain, their work is available
38
- in the feature/rails3_compatibility branch.
39
-
40
- A Rails 3.0 compatible version of the gem is also available:
41
-
42
- gem install acts-as-taggable-on -v=2.0.0.pre1
43
-
44
- === Post Installation (Rails)
45
-
46
- 1. script/generate acts_as_taggable_on_migration
47
- 2. rake db:migrate
48
-
49
- === Testing
50
-
51
- Acts As Taggable On uses RSpec for its test coverage. Inside the plugin
52
- directory, you can run the specs with:
53
-
54
- rake spec
55
-
56
- If you already have RSpec on your application, the specs will run while using:
57
-
58
- rake spec:plugins
59
-
60
-
61
- == Usage
62
-
63
- class User < ActiveRecord::Base
64
- acts_as_taggable_on :tags, :skills, :interests
65
- end
66
-
67
- @user = User.new(:name => "Bobby")
68
- @user.tag_list = "awesome, slick, hefty" # this should be familiar
69
- @user.skill_list = "joking, clowning, boxing" # but you can do it for any context!
70
- @user.skill_list # => ["joking","clowning","boxing"] as TagList
71
- @user.save
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
-
76
- # The old way
77
- User.find_tagged_with("awesome", :on => :tags) # => [@user]
78
- User.find_tagged_with("awesome", :on => :skills) # => []
79
-
80
- # The better way (utilizes named_scope)
81
- User.tagged_with("awesome", :on => :tags) # => [@user]
82
- User.tagged_with("awesome", :on => :skills) # => []
83
-
84
- @frankie = User.create(:name => "Frankie", :skill_list => "joking, flying, eating")
85
- User.skill_counts # => [<Tag name="joking" count=2>,<Tag name="clowning" count=1>...]
86
- @frankie.skill_counts
87
-
88
- === Finding Tagged Objects
89
-
90
- Acts As Taggable On utilizes Rails 2.1's named_scope to create an association
91
- for tags. This way you can mix and match to filter down your results, and it
92
- also improves compatibility with the will_paginate gem:
93
-
94
- class User < ActiveRecord::Base
95
- acts_as_taggable_on :tags
96
- named_scope :by_join_date, :order => "created_at DESC"
97
- end
98
-
99
- User.tagged_with("awesome").by_date
100
- User.tagged_with("awesome").by_date.paginate(:page => params[:page], :per_page => 20)
101
-
102
- #Find a user with matching all tags, not just one
103
- User.tagged_with(["awesome", "cool"], :match_all => :true)
104
-
105
- === Relationships
106
-
107
- You can find objects of the same type based on similar tags on certain contexts.
108
- Also, objects will be returned in descending order based on the total number of
109
- matched tags.
110
-
111
- @bobby = User.find_by_name("Bobby")
112
- @bobby.skill_list # => ["jogging", "diving"]
113
-
114
- @frankie = User.find_by_name("Frankie")
115
- @frankie.skill_list # => ["hacking"]
116
-
117
- @tom = User.find_by_name("Tom")
118
- @tom.skill_list # => ["hacking", "jogging", "diving"]
119
-
120
- @tom.find_related_skills # => [<User name="Bobby">,<User name="Frankie">]
121
- @bobby.find_related_skills # => [<User name="Tom">]
122
- @frankie.find_related_skills # => [<User name="Tom">]
123
-
124
- === Dynamic Tag Contexts
125
-
126
- In addition to the generated tag contexts in the definition, it is also possible
127
- to allow for dynamic tag contexts (this could be user generated tag contexts!)
128
-
129
- @user = User.new(:name => "Bobby")
130
- @user.set_tag_list_on(:customs, "same, as, tag, list")
131
- @user.tag_list_on(:customs) # => ["same","as","tag","list"]
132
- @user.save
133
- @user.tags_on(:customs) # => [<Tag name='same'>,...]
134
- @user.tag_counts_on(:customs)
135
- User.find_tagged_with("same", :on => :customs) # => [@user]
136
-
137
- === Tag Ownership
138
-
139
- Tags can have owners:
140
-
141
- class User < ActiveRecord::Base
142
- acts_as_tagger
143
- end
144
-
145
- class Photo < ActiveRecord::Base
146
- acts_as_taggable_on :locations
147
- end
148
-
149
- @some_user.tag(@some_photo, :with => "paris, normandy", :on => :locations)
150
- @some_user.owned_taggings
151
- @some_user.owned_tags
152
- @some_photo.locations_from(@some_user)
153
-
154
- === Tag cloud calculations
155
-
156
- To construct tag clouds, the frequency of each tag needs to be calculated.
157
- Because we specified +acts_as_taggable_on+ on the <tt>User</tt> class, we can
158
- get a calculation of all the tag counts by using <tt>User.tag_counts_on(:customs)</tt>. But what if we wanted a tag count for
159
- an single user's posts? To achieve this we call tag_counts on the association:
160
-
161
- User.find(:first).posts.tag_counts_on(:tags)
162
-
163
- A helper is included to assist with generating tag clouds.
164
-
165
- Here is an example that generates a tag cloud.
166
-
167
- Helper:
168
-
169
- module PostsHelper
170
- include TagsHelper
171
- end
172
-
173
- Controller:
174
-
175
- class PostController < ApplicationController
176
- def tag_cloud
177
- @tags = Post.tag_counts_on(:tags)
178
- end
179
- end
180
-
181
- View:
182
-
183
- <% tag_cloud(@tags, %w(css1 css2 css3 css4)) do |tag, css_class| %>
184
- <%= link_to tag.name, { :action => :tag, :id => tag.name }, :class => css_class %>
185
- <% end %>
186
-
187
- CSS:
188
-
189
- .css1 { font-size: 1.0em; }
190
- .css2 { font-size: 1.2em; }
191
- .css3 { font-size: 1.4em; }
192
- .css4 { font-size: 1.6em; }
193
-
194
- == Contributors
195
-
196
- * TomEric (i76) - Maintainer
197
- * Michael Bleigh - Original Author
198
- * Brendan Lim - Related Objects
199
- * Pradeep Elankumaran - Taggers
200
- * Sinclair Bain - Patch King
201
-
202
- == Patch Contributors
203
-
204
- * tristanzdunn - Related objects of other classes
205
- * azabaj - Fixed migrate down
206
- * Peter Cooper - named_scope fix
207
- * slainer68 - STI fix
208
- * harrylove - migration instructions and fix-ups
209
- * lawrencepit - cached tag work
210
- * sobrinho - fixed tag_cloud helper
211
-
212
- Copyright (c) 2007-2009 Michael Bleigh (http://mbleigh.com/) and Intridea Inc. (http://intridea.com/), released under the MIT license
data/VERSION DELETED
@@ -1 +0,0 @@
1
- 2.0.0.pre5
@@ -1,56 +0,0 @@
1
- module ActsAsTaggableOn::Taggable
2
- module Cache
3
- def self.included(base)
4
- # Skip adding caching capabilities if no cache columns exist
5
- return unless base.tag_types.any? { |context| base.column_names.include?("cached_#{context.to_s.singularize}_list") }
6
-
7
- base.send :include, ActsAsTaggableOn::Taggable::Cache::InstanceMethods
8
- base.extend ActsAsTaggableOn::Taggable::Cache::ClassMethods
9
-
10
- base.class_eval do
11
- before_save :save_cached_tag_list
12
- end
13
-
14
- base.intialize_acts_as_taggable_on_cache
15
- end
16
-
17
- module ClassMethods
18
- def intialize_acts_as_taggable_on_cache
19
- tag_types.map(&:to_s).each do |tag_type|
20
- class_eval %(
21
- def self.caching_#{tag_type.singularize}_list?
22
- caching_tag_list_on?("#{tag_type}")
23
- end
24
- )
25
- end
26
- end
27
-
28
- def acts_as_taggable_on(*args)
29
- super(*args)
30
- intialize_acts_as_taggable_on_cache
31
- end
32
-
33
- def caching_tag_list_on?(context)
34
- column_names.include?("cached_#{context.to_s.singularize}_list")
35
- end
36
- end
37
-
38
- module InstanceMethods
39
- def tag_list_cache_set_on(context)
40
- variable_name = "@#{context.to_s.singularize}_list"
41
- !instance_variable_get(variable_name).nil?
42
- end
43
-
44
- def save_cached_tag_list
45
- tag_types.map(&:to_s).each do |tag_type|
46
- if self.class.send("caching_#{tag_type.singularize}_list?")
47
- if tag_list_cache_set_on(tag_type)
48
- list = tag_list_cache_on(tag_type.singularize).to_a.flatten.compact.join(', ')
49
- self["cached_#{tag_type.singularize}_list"] = list
50
- end
51
- end
52
- end
53
- end
54
- end
55
- end
56
- end
@@ -1,97 +0,0 @@
1
- module ActsAsTaggableOn::Taggable
2
- module Collection
3
- def self.included(base)
4
- base.send :include, ActsAsTaggableOn::Taggable::Collection::InstanceMethods
5
- base.extend ActsAsTaggableOn::Taggable::Collection::ClassMethods
6
- base.initialize_acts_as_taggable_on_collection
7
- end
8
-
9
- module ClassMethods
10
- def initialize_acts_as_taggable_on_collection
11
- tag_types.map(&:to_s).each do |tag_type|
12
- class_eval %(
13
- def self.#{tag_type.singularize}_counts(options={})
14
- tag_counts_on('#{tag_type}', options)
15
- end
16
-
17
- def #{tag_type.singularize}_counts(options = {})
18
- tag_counts_on('#{tag_type}', options)
19
- end
20
-
21
- def top_#{tag_type}(limit = 10)
22
- tag_counts_on('#{tag_type}', :order => 'count desc', :limit => limit.to_i)
23
- end
24
-
25
- def self.top_#{tag_type}(limit = 10)
26
- tag_counts_on('#{tag_type}', :order => 'count desc', :limit => limit.to_i)
27
- end
28
- )
29
- end
30
- end
31
-
32
- def acts_as_taggable_on(*args)
33
- super(*args)
34
- initialize_acts_as_taggable_on_collection
35
- end
36
-
37
- def tag_counts_on(context, options = {})
38
- all_tag_counts(options.merge({:on => context.to_s}))
39
- end
40
-
41
- # Calculate the tag counts for all tags.
42
- #
43
- # Options:
44
- # :start_at - Restrict the tags to those created after a certain time
45
- # :end_at - Restrict the tags to those created before a certain time
46
- # :conditions - A piece of SQL conditions to add to the query
47
- # :limit - The maximum number of tags to return
48
- # :order - A piece of SQL to order by. Eg 'tags.count desc' or 'taggings.created_at desc'
49
- # :at_least - Exclude tags with a frequency less than the given value
50
- # :at_most - Exclude tags with a frequency greater than the given value
51
- # :on - Scope the find to only include a certain context
52
- def all_tag_counts(options = {})
53
- options.assert_valid_keys :start_at, :end_at, :conditions, :at_least, :at_most, :order, :limit, :on, :id
54
-
55
- start_at = sanitize_sql(["#{Tagging.table_name}.created_at >= ?", options.delete(:start_at)]) if options[:start_at]
56
- end_at = sanitize_sql(["#{Tagging.table_name}.created_at <= ?", options.delete(:end_at)]) if options[:end_at]
57
-
58
- taggable_type = sanitize_sql(["#{Tagging.table_name}.taggable_type = ?", base_class.name])
59
- taggable_id = sanitize_sql(["#{Tagging.table_name}.taggable_id = ?", options.delete(:id)]) if options[:id]
60
- options[:conditions] = sanitize_sql(options[:conditions]) if options[:conditions]
61
-
62
- conditions = [
63
- taggable_type,
64
- taggable_id,
65
- options[:conditions],
66
- start_at,
67
- end_at
68
- ]
69
-
70
- conditions = conditions.compact.join(' AND ')
71
-
72
- joins = ["LEFT OUTER JOIN #{Tagging.table_name} ON #{Tag.table_name}.id = #{Tagging.table_name}.tag_id"]
73
- joins << sanitize_sql(["AND #{Tagging.table_name}.context = ?",options.delete(:on).to_s]) unless options[:on].nil?
74
- joins << " INNER JOIN #{table_name} ON #{table_name}.#{primary_key} = #{Tagging.table_name}.taggable_id"
75
-
76
- unless descends_from_active_record?
77
- # Current model is STI descendant, so add type checking to the join condition
78
- joins << " AND #{table_name}.#{inheritance_column} = '#{name}'"
79
- end
80
-
81
- at_least = sanitize_sql(['COUNT(*) >= ?', options.delete(:at_least)]) if options[:at_least]
82
- at_most = sanitize_sql(['COUNT(*) <= ?', options.delete(:at_most)]) if options[:at_most]
83
- having = [at_least, at_most].compact.join(' AND ')
84
- group_by = "#{grouped_column_names_for(Tag)} HAVING COUNT(*) > 0"
85
- group_by << " AND #{having}" unless having.blank?
86
-
87
- Tag.select("#{Tag.table_name}.*, COUNT(*) AS count").joins(joins.join(" ")).where(conditions).group(group_by).limit(options[:limit]).order(options[:order])
88
- end
89
- end
90
-
91
- module InstanceMethods
92
- def tag_counts_on(context, options={})
93
- self.class.tag_counts_on(context, options.merge(:id => id))
94
- end
95
- end
96
- end
97
- end
@@ -1,220 +0,0 @@
1
- module ActsAsTaggableOn::Taggable
2
- module Core
3
- def self.included(base)
4
- base.send :include, ActsAsTaggableOn::Taggable::Core::InstanceMethods
5
- base.extend ActsAsTaggableOn::Taggable::Core::ClassMethods
6
-
7
- base.class_eval do
8
- attr_writer :custom_contexts
9
- after_save :save_tags
10
- end
11
-
12
- base.initialize_acts_as_taggable_on_core
13
- end
14
-
15
- module ClassMethods
16
- def initialize_acts_as_taggable_on_core
17
- tag_types.map(&:to_s).each do |tag_type|
18
- context_taggings = "#{tag_type.singularize}_taggings".to_sym
19
- context_tags = tag_type.to_sym
20
-
21
- class_eval do
22
- has_many context_taggings, :as => :taggable, :dependent => :destroy, :include => :tag, :class_name => "Tagging",
23
- :conditions => ['#{Tagging.table_name}.tagger_id IS NULL AND #{Tagging.table_name}.context = ?', tag_type]
24
- has_many context_tags, :through => context_taggings, :source => :tag
25
- end
26
-
27
- class_eval %(
28
- def #{tag_type.singularize}_list
29
- tag_list_on('#{tag_type}')
30
- end
31
-
32
- def #{tag_type.singularize}_list=(new_tags)
33
- set_tag_list_on('#{tag_type}', new_tags)
34
- end
35
-
36
- def all_#{tag_type}_list
37
- all_tags_list_on('#{tag_type}')
38
- end
39
- )
40
- end
41
- end
42
-
43
- def acts_as_taggable_on(*args)
44
- super(*args)
45
- initialize_acts_as_taggable_on_core
46
- end
47
-
48
- # all column names are necessary for PostgreSQL group clause
49
- def grouped_column_names_for(object)
50
- object.column_names.map { |column| "#{object.table_name}.#{column}" }.join(", ")
51
- end
52
-
53
- def tagged_with(tags, options = {})
54
- tag_list = TagList.from(tags)
55
-
56
- return {} if tag_list.empty?
57
-
58
- joins = []
59
- conditions = []
60
-
61
- context = options.delete(:on)
62
-
63
- if options.delete(:exclude)
64
- tags_conditions = tag_list.map { |t| sanitize_sql(["#{Tag.table_name}.name LIKE ?", t]) }.join(" OR ")
65
- conditions << "#{table_name}.#{primary_key} NOT IN (SELECT #{Tagging.table_name}.taggable_id FROM #{Tagging.table_name} JOIN #{Tag.table_name} ON #{Tagging.table_name}.tag_id = #{Tag.table_name}.id AND (#{tags_conditions}) WHERE #{Tagging.table_name}.taggable_type = #{quote_value(base_class.name)})"
66
-
67
- elsif options.delete(:any)
68
- tags_conditions = tag_list.map { |t| sanitize_sql(["#{Tag.table_name}.name LIKE ?", t]) }.join(" OR ")
69
- conditions << "#{table_name}.#{primary_key} IN (SELECT #{Tagging.table_name}.taggable_id FROM #{Tagging.table_name} JOIN #{Tag.table_name} ON #{Tagging.table_name}.tag_id = #{Tag.table_name}.id AND (#{tags_conditions}) WHERE #{Tagging.table_name}.taggable_type = #{quote_value(base_class.name)})"
70
-
71
- else
72
- tags = Tag.named_any(tag_list)
73
- return where("1 = 0") unless tags.length == tag_list.length
74
-
75
- tags.each do |tag|
76
- safe_tag = tag.name.gsub(/[^a-zA-Z0-9]/, '')
77
- prefix = "#{safe_tag}_#{rand(1024)}"
78
-
79
- taggings_alias = "#{table_name}_taggings_#{prefix}"
80
-
81
- tagging_join = "JOIN #{Tagging.table_name} #{taggings_alias}" +
82
- " ON #{taggings_alias}.taggable_id = #{table_name}.#{primary_key}" +
83
- " AND #{taggings_alias}.taggable_type = #{quote_value(base_class.name)}" +
84
- " AND #{taggings_alias}.tag_id = #{tag.id}"
85
- tagging_join << " AND " + sanitize_sql(["#{taggings_alias}.context = ?", context.to_s]) if context
86
-
87
- joins << tagging_join
88
- end
89
- end
90
-
91
- taggings_alias, tags_alias = "#{table_name}_taggings_group", "#{table_name}_tags_group"
92
-
93
- if options.delete(:match_all)
94
- joins << "LEFT OUTER JOIN #{Tagging.table_name} #{taggings_alias}" +
95
- " ON #{taggings_alias}.taggable_id = #{table_name}.#{primary_key}" +
96
- " AND #{taggings_alias}.taggable_type = #{quote_value(base_class.name)}"
97
-
98
- group = "#{grouped_column_names_for(self)} HAVING COUNT(#{taggings_alias}.taggable_id) = #{tags.size}"
99
- end
100
-
101
-
102
- joins(joins.join(" ")).group(group).where(conditions.join(" AND ")).readonly(false)
103
- end
104
-
105
- def is_taggable?
106
- true
107
- end
108
- end
109
-
110
- module InstanceMethods
111
- # all column names are necessary for PostgreSQL group clause
112
- def grouped_column_names_for(object)
113
- self.class.grouped_column_names_for(object)
114
- end
115
-
116
- def custom_contexts
117
- @custom_contexts ||= []
118
- end
119
-
120
- def is_taggable?
121
- self.class.is_taggable?
122
- end
123
-
124
- def add_custom_context(value)
125
- custom_contexts << value.to_s unless custom_contexts.include?(value.to_s) or self.class.tag_types.map(&:to_s).include?(value.to_s)
126
- end
127
-
128
- def cached_tag_list_on(context)
129
- self["cached_#{context.to_s.singularize}_list"]
130
- end
131
-
132
- def tag_list_cache_on(context)
133
- variable_name = "@#{context.to_s.singularize}_list"
134
- instance_variable_get(variable_name) || instance_variable_set(variable_name, TagList.new(tags_on(context).map(&:name)))
135
- end
136
-
137
- def tag_list_on(context)
138
- add_custom_context(context)
139
- tag_list_cache_on(context)
140
- end
141
-
142
- def all_tags_list_on(context)
143
- variable_name = "@all_#{context.to_s.singularize}_list"
144
- return instance_variable_get(variable_name) if instance_variable_get(variable_name)
145
-
146
- instance_variable_set(variable_name, TagList.new(all_tags_on(context).map(&:name)).freeze)
147
- end
148
-
149
- ##
150
- # Returns all tags of a given context
151
- def all_tags_on(context)
152
- opts = ["#{Tagging.table_name}.context = ?", context.to_s]
153
- base_tags.where(opts).order("#{Tagging.table_name}.created_at").group("#{Tagging.table_name}.tag_id").all
154
- end
155
-
156
- ##
157
- # Returns all tags that are not owned of a given context
158
- def tags_on(context)
159
- if respond_to?(context)
160
- # If the association is available, use it:
161
- send(context).all
162
- else
163
- # If the association is not available, query it the old fashioned way
164
- base_tags.where(["#{Tagging.table_name}.context = ? AND #{Tagging.table_name}.tagger_id IS NULL", context.to_s]).all
165
- end
166
- end
167
-
168
- def set_tag_list_on(context, new_list)
169
- add_custom_context(context)
170
-
171
- variable_name = "@#{context.to_s.singularize}_list"
172
- instance_variable_set(variable_name, TagList.from(new_list))
173
- end
174
-
175
- def tagging_contexts
176
- custom_contexts + self.class.tag_types.map(&:to_s)
177
- end
178
-
179
- def reload
180
- self.class.tag_types.each do |context|
181
- instance_variable_set("@#{context.to_s.singularize}_list", nil)
182
- instance_variable_set("@all_#{context.to_s.singularize}_list", nil)
183
- end
184
-
185
- super
186
- end
187
-
188
- def save_tags
189
- transaction do
190
- tagging_contexts.each do |context|
191
- tag_list = tag_list_cache_on(context).uniq
192
-
193
- # Find existing tags or create non-existing tags:
194
- tag_list = Tag.find_or_create_all_with_like_by_name(tag_list)
195
-
196
- current_tags = tags_on(context)
197
- old_tags = current_tags - tag_list
198
- new_tags = tag_list - current_tags
199
-
200
- # Find taggings to remove:
201
- old_taggings = taggings.where(:tagger_type => nil, :tagger_id => nil,
202
- :context => context.to_s, :tag_id => old_tags).all
203
-
204
- if old_taggings.present?
205
- # Destroy old taggings:
206
- Tagging.destroy_all :id => old_taggings.map(&:id)
207
- end
208
-
209
- # Create new taggings:
210
- new_tags.each do |tag|
211
- Tagging.create!(:tag_id => tag.id, :context => context.to_s, :taggable => self)
212
- end
213
- end
214
- end
215
-
216
- true
217
- end
218
- end
219
- end
220
- end
@@ -1,29 +0,0 @@
1
- module ActsAsTaggableOn::Taggable
2
- module Dirty
3
- def self.included(base)
4
- include ActsAsTaggableOn::Taggable::Dirty::InstanceMethods
5
-
6
- base.tag_types.map(&:to_s).each do |tag_type|
7
- base.class_eval %(
8
- def #{tag_type.singularize}_list_changed?
9
- tag_list_changed_on?('#{tag_type}')
10
- tag_list_on('#{tag_type}')
11
- end
12
-
13
- def #{tag_type.singularize}_list=(new_tags)
14
- change_tag_list_on('#{tag_type}', new_tags)
15
- super(new_tags)
16
- end
17
- )
18
- end
19
- end
20
-
21
- module InstanceMethods
22
- def tag_list_changed_on?(context)
23
- end
24
-
25
- def change_tag_list_on(context, new_tags)
26
- end
27
- end
28
- end
29
- end