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.
- data/.gitignore +1 -0
- data/.travis.yml +1 -2
- data/CHANGELOG +10 -0
- data/README.rdoc +31 -63
- data/acts-as-taggable-on.gemspec +4 -3
- data/lib/acts-as-taggable-on/version.rb +1 -1
- data/lib/acts-as-taggable-on.rb +0 -4
- data/lib/acts_as_taggable_on/acts_as_taggable_on/cache.rb +1 -1
- data/lib/acts_as_taggable_on/acts_as_taggable_on/collection.rb +1 -1
- data/lib/acts_as_taggable_on/acts_as_taggable_on/core.rb +29 -17
- data/lib/acts_as_taggable_on/acts_as_taggable_on/related.rb +15 -15
- data/lib/acts_as_taggable_on/acts_as_taggable_on.rb +4 -13
- data/lib/acts_as_taggable_on/tag.rb +12 -13
- data/lib/acts_as_taggable_on/tagging.rb +0 -2
- data/lib/acts_as_taggable_on/utils.rb +7 -8
- data/lib/generators/acts_as_taggable_on/migration/migration_generator.rb +9 -2
- data/spec/acts_as_taggable_on/acts_as_taggable_on_spec.rb +228 -61
- data/spec/acts_as_taggable_on/tag_list_spec.rb +4 -0
- data/spec/acts_as_taggable_on/tag_spec.rb +11 -15
- data/spec/acts_as_taggable_on/taggable_spec.rb +89 -27
- data/spec/acts_as_taggable_on/tagger_spec.rb +14 -0
- data/spec/acts_as_taggable_on/utils_spec.rb +2 -3
- data/spec/database.yml.sample +1 -1
- data/spec/generators/acts_as_taggable_on/migration/migration_generator_spec.rb +22 -0
- data/spec/models.rb +11 -2
- data/spec/schema.rb +7 -0
- data/spec/spec_helper.rb +1 -0
- metadata +95 -123
- data/VERSION +0 -1
- data/generators/acts_as_taggable_on_migration/acts_as_taggable_on_migration_generator.rb +0 -7
- data/generators/acts_as_taggable_on_migration/templates/migration.rb +0 -29
- data/lib/acts_as_taggable_on/compatibility/Gemfile +0 -8
- data/lib/acts_as_taggable_on/compatibility/active_record_backports.rb +0 -21
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
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
|
-
|
|
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
|
-
|
|
23
|
+
gem 'acts-as-taggable-on', '2.1.0'
|
|
42
24
|
|
|
43
|
-
|
|
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
|
|
58
|
-
directory, you can run the specs for RoR 3.
|
|
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
|
-
|
|
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
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
*
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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
|
data/acts-as-taggable-on.gemspec
CHANGED
|
@@ -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{
|
|
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', '
|
|
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'
|
data/lib/acts-as-taggable-on.rb
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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}.
|
|
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
|
|
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}.
|
|
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}.
|
|
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}.
|
|
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}.
|
|
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}.
|
|
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}.
|
|
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
|
-
|
|
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
|
-
|
|
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,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
|
-
|
|
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(
|
|
22
|
-
ActiveRecord::
|
|
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
|