acts-as-taggable-on 2.0.0.pre1 → 2.0.0.pre3
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/Gemfile +4 -6
- data/README.rdoc +10 -0
- data/Rakefile +44 -16
- data/VERSION +1 -1
- data/lib/acts-as-taggable-on.rb +19 -16
- data/lib/acts_as_taggable_on/acts_as_taggable_on.rb +30 -415
- data/lib/acts_as_taggable_on/acts_as_taggable_on/aggregate.rb +90 -0
- data/lib/acts_as_taggable_on/acts_as_taggable_on/cache.rb +39 -0
- data/lib/acts_as_taggable_on/acts_as_taggable_on/core.rb +202 -0
- data/lib/acts_as_taggable_on/acts_as_taggable_on/ownership.rb +74 -0
- data/lib/acts_as_taggable_on/acts_as_taggable_on/related.rb +74 -0
- data/lib/acts_as_taggable_on/acts_as_tagger.rb +34 -44
- data/lib/acts_as_taggable_on/compatibility/Gemfile +6 -0
- data/lib/acts_as_taggable_on/compatibility/active_record_backports.rb +17 -0
- data/lib/acts_as_taggable_on/compatibility/tag.rb +3 -0
- data/lib/acts_as_taggable_on/compatibility/tagging.rb +3 -0
- data/lib/acts_as_taggable_on/tag.rb +16 -5
- data/lib/acts_as_taggable_on/tags_helper.rb +1 -1
- data/spec/acts_as_taggable_on/acts_as_taggable_on_spec.rb +37 -29
- data/spec/acts_as_taggable_on/tag_spec.rb +13 -2
- data/spec/acts_as_taggable_on/taggable_spec.rb +35 -21
- data/spec/acts_as_taggable_on/tagger_spec.rb +17 -1
- data/spec/acts_as_taggable_on/tagging_spec.rb +6 -1
- data/spec/acts_as_taggable_on/tags_helper_spec.rb +0 -2
- data/spec/models.rb +32 -0
- data/spec/schema.rb +3 -1
- data/spec/spec_helper.rb +18 -37
- metadata +14 -6
- data/lib/acts_as_taggable_on/group_helper.rb +0 -14
- data/spec/acts_as_taggable_on/group_helper_spec.rb +0 -21
@@ -1,60 +1,50 @@
|
|
1
|
-
module
|
2
|
-
module
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
base.extend ClassMethods
|
7
|
-
end
|
8
|
-
|
9
|
-
module ClassMethods
|
10
|
-
|
11
|
-
def acts_as_tagger(opts={})
|
12
|
-
has_many :owned_taggings, opts.merge(:as => :tagger, :dependent => :destroy,
|
13
|
-
:include => :tag, :class_name => "Tagging")
|
14
|
-
has_many :owned_tags, :through => :owned_taggings, :source => :tag, :uniq => true
|
15
|
-
|
16
|
-
include ActiveRecord::Acts::Tagger::InstanceMethods
|
17
|
-
extend ActiveRecord::Acts::Tagger::SingletonMethods
|
18
|
-
end
|
1
|
+
module ActsAsTaggableOn
|
2
|
+
module Tagger
|
3
|
+
def self.included(base)
|
4
|
+
base.extend ClassMethods
|
5
|
+
end
|
19
6
|
|
20
|
-
|
21
|
-
|
22
|
-
|
7
|
+
module ClassMethods
|
8
|
+
def acts_as_tagger(opts={})
|
9
|
+
has_many :owned_taggings, opts.merge(:as => :tagger, :dependent => :destroy,
|
10
|
+
:include => :tag, :class_name => "Tagging")
|
11
|
+
has_many :owned_tags, :through => :owned_taggings, :source => :tag, :uniq => true
|
23
12
|
|
13
|
+
include ActsAsTaggableOn::Tagger::InstanceMethods
|
14
|
+
extend ActsAsTaggableOn::Tagger::SingletonMethods
|
24
15
|
end
|
25
16
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
def tag(taggable, opts={})
|
32
|
-
opts.reverse_merge!(:force => true)
|
17
|
+
def is_tagger?
|
18
|
+
false
|
19
|
+
end
|
20
|
+
end
|
33
21
|
|
34
|
-
|
22
|
+
module InstanceMethods
|
23
|
+
def self.included(base)
|
24
|
+
end
|
35
25
|
|
36
|
-
|
37
|
-
|
38
|
-
raise "No context :#{opts[:on]} defined in #{taggable.class.to_s}" unless (opts[:force] || taggable.tag_types.include?(opts[:on]))
|
26
|
+
def tag(taggable, opts={})
|
27
|
+
opts.reverse_merge!(:force => true)
|
39
28
|
|
40
|
-
|
41
|
-
taggable.save
|
42
|
-
end
|
29
|
+
return false unless taggable.respond_to?(:is_taggable?) && taggable.is_taggable?
|
43
30
|
|
44
|
-
|
45
|
-
|
46
|
-
|
31
|
+
raise "You need to specify a tag context using :on" unless opts.has_key?(:on)
|
32
|
+
raise "You need to specify some tags using :with" unless opts.has_key?(:with)
|
33
|
+
raise "No context :#{opts[:on]} defined in #{taggable.class.to_s}" unless (opts[:force] || taggable.tag_types.include?(opts[:on]))
|
47
34
|
|
35
|
+
taggable.set_owner_tag_list_on(self, opts[:on].to_s, opts[:with])
|
36
|
+
taggable.save
|
48
37
|
end
|
49
38
|
|
50
|
-
|
51
|
-
|
52
|
-
def is_tagger?
|
53
|
-
true
|
54
|
-
end
|
55
|
-
|
39
|
+
def is_tagger?
|
40
|
+
self.class.is_tagger?
|
56
41
|
end
|
42
|
+
end
|
57
43
|
|
44
|
+
module SingletonMethods
|
45
|
+
def is_tagger?
|
46
|
+
true
|
47
|
+
end
|
58
48
|
end
|
59
49
|
end
|
60
50
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module ActsAsTaggableOn
|
2
|
+
module ActiveRecord
|
3
|
+
module Backports
|
4
|
+
def self.included(base)
|
5
|
+
base.class_eval do
|
6
|
+
named_scope :where, lambda { |conditions| { :conditions => conditions } }
|
7
|
+
named_scope :joins, lambda { |joins| { :joins => joins } }
|
8
|
+
named_scope :group, lambda { |group| { :group => group } }
|
9
|
+
named_scope :readonly, lambda { |readonly| { :readonly => readonly } }
|
10
|
+
named_scope :order, lambda { |order| { :order => order } }
|
11
|
+
named_scope :select, lambda { |select| { :select => select } }
|
12
|
+
named_scope :limit, lambda { |limit| { :limit => limit } }
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -11,12 +11,23 @@ class Tag < ActiveRecord::Base
|
|
11
11
|
validates_presence_of :name
|
12
12
|
validates_uniqueness_of :name
|
13
13
|
|
14
|
-
###
|
14
|
+
### SCOPES:
|
15
15
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
16
|
+
def self.named(name)
|
17
|
+
where(["name LIKE ?", name])
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.named_any(list)
|
21
|
+
where(list.map { |tag| sanitize_sql(["name LIKE ?", tag.to_s]) }.join(" OR "))
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.named_like(name)
|
25
|
+
where(["name LIKE ?", "%#{name}%"])
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.named_like_any(list)
|
29
|
+
where(list.map { |tag| sanitize_sql(["name LIKE ?", "%#{tag.to_s}%"]) }.join(" OR "))
|
30
|
+
end
|
20
31
|
|
21
32
|
### CLASS METHODS:
|
22
33
|
|
@@ -26,6 +26,10 @@ describe "Acts As Taggable On" do
|
|
26
26
|
it "should create an instance attribute for tag types" do
|
27
27
|
@taggable.should respond_to(:tag_types)
|
28
28
|
end
|
29
|
+
|
30
|
+
it "should have all tag types" do
|
31
|
+
@taggable.tag_types.should == [:tags, :languages, :skills, :needs, :offerings]
|
32
|
+
end
|
29
33
|
|
30
34
|
it "should generate an association for each tag type" do
|
31
35
|
@taggable.should respond_to(:tags, :skills, :languages)
|
@@ -48,6 +52,10 @@ describe "Acts As Taggable On" do
|
|
48
52
|
@taggable.should respond_to(:tag_list, :skill_list, :language_list)
|
49
53
|
@taggable.should respond_to(:tag_list=, :skill_list=, :language_list=)
|
50
54
|
end
|
55
|
+
|
56
|
+
it "should generate a tag_list accessor, that includes owned tags, for each tag type" do
|
57
|
+
@taggable.should respond_to(:all_tags_list, :all_skills_list, :all_languages_list)
|
58
|
+
end
|
51
59
|
end
|
52
60
|
|
53
61
|
describe "Single Table Inheritance" do
|
@@ -56,17 +64,17 @@ describe "Acts As Taggable On" do
|
|
56
64
|
@inherited_same = InheritingTaggableModel.new(:name => "inherited same")
|
57
65
|
@inherited_different = AlteredInheritingTaggableModel.new(:name => "inherited different")
|
58
66
|
end
|
59
|
-
|
67
|
+
|
60
68
|
it "should pass on tag contexts to STI-inherited models" do
|
61
69
|
@inherited_same.should respond_to(:tag_list, :skill_list, :language_list)
|
62
70
|
@inherited_different.should respond_to(:tag_list, :skill_list, :language_list)
|
63
71
|
end
|
64
|
-
|
72
|
+
|
65
73
|
it "should have tag contexts added in altered STI models" do
|
66
74
|
@inherited_different.should respond_to(:part_list)
|
67
75
|
end
|
68
76
|
end
|
69
|
-
|
77
|
+
|
70
78
|
describe "Reloading" do
|
71
79
|
it "should save a model instantiated by Model.find" do
|
72
80
|
taggable = TaggableModel.create!(:name => "Taggable")
|
@@ -80,48 +88,48 @@ describe "Acts As Taggable On" do
|
|
80
88
|
taggable1 = TaggableModel.create!(:name => "Taggable 1")
|
81
89
|
taggable2 = TaggableModel.create!(:name => "Taggable 2")
|
82
90
|
taggable3 = TaggableModel.create!(:name => "Taggable 3")
|
83
|
-
|
91
|
+
|
84
92
|
taggable1.tag_list = "one, two"
|
85
93
|
taggable1.save
|
86
|
-
|
94
|
+
|
87
95
|
taggable2.tag_list = "three, four"
|
88
96
|
taggable2.save
|
89
|
-
|
97
|
+
|
90
98
|
taggable3.tag_list = "one, four"
|
91
99
|
taggable3.save
|
92
|
-
|
100
|
+
|
93
101
|
taggable1.find_related_tags.should include(taggable3)
|
94
102
|
taggable1.find_related_tags.should_not include(taggable2)
|
95
103
|
end
|
96
|
-
|
104
|
+
|
97
105
|
it "should find other related objects based on tag names on context" do
|
98
106
|
taggable1 = TaggableModel.create!(:name => "Taggable 1")
|
99
107
|
taggable2 = OtherTaggableModel.create!(:name => "Taggable 2")
|
100
108
|
taggable3 = OtherTaggableModel.create!(:name => "Taggable 3")
|
101
|
-
|
109
|
+
|
102
110
|
taggable1.tag_list = "one, two"
|
103
111
|
taggable1.save
|
104
|
-
|
112
|
+
|
105
113
|
taggable2.tag_list = "three, four"
|
106
114
|
taggable2.save
|
107
|
-
|
115
|
+
|
108
116
|
taggable3.tag_list = "one, four"
|
109
117
|
taggable3.save
|
110
|
-
|
118
|
+
|
111
119
|
taggable1.find_related_tags_for(OtherTaggableModel).should include(taggable3)
|
112
120
|
taggable1.find_related_tags_for(OtherTaggableModel).should_not include(taggable2)
|
113
121
|
end
|
114
|
-
|
122
|
+
|
115
123
|
it "should not include the object itself in the list of related objects" do
|
116
124
|
taggable1 = TaggableModel.create!(:name => "Taggable 1")
|
117
125
|
taggable2 = TaggableModel.create!(:name => "Taggable 2")
|
118
|
-
|
126
|
+
|
119
127
|
taggable1.tag_list = "one"
|
120
128
|
taggable1.save
|
121
|
-
|
129
|
+
|
122
130
|
taggable2.tag_list = "one, two"
|
123
131
|
taggable2.save
|
124
|
-
|
132
|
+
|
125
133
|
taggable1.find_related_tags.should include(taggable2)
|
126
134
|
taggable1.find_related_tags.should_not include(taggable1)
|
127
135
|
end
|
@@ -132,48 +140,48 @@ describe "Acts As Taggable On" do
|
|
132
140
|
taggable1 = TaggableModel.create!(:name => "Taggable 1")
|
133
141
|
taggable2 = TaggableModel.create!(:name => "Taggable 2")
|
134
142
|
taggable3 = TaggableModel.create!(:name => "Taggable 3")
|
135
|
-
|
143
|
+
|
136
144
|
taggable1.offering_list = "one, two"
|
137
145
|
taggable1.save!
|
138
|
-
|
146
|
+
|
139
147
|
taggable2.need_list = "one, two"
|
140
148
|
taggable2.save!
|
141
|
-
|
149
|
+
|
142
150
|
taggable3.offering_list = "one, two"
|
143
151
|
taggable3.save!
|
144
|
-
|
152
|
+
|
145
153
|
taggable1.find_matching_contexts(:offerings, :needs).should include(taggable2)
|
146
154
|
taggable1.find_matching_contexts(:offerings, :needs).should_not include(taggable3)
|
147
155
|
end
|
148
|
-
|
156
|
+
|
149
157
|
it "should find other related objects with tags of matching contexts" do
|
150
158
|
taggable1 = TaggableModel.create!(:name => "Taggable 1")
|
151
159
|
taggable2 = OtherTaggableModel.create!(:name => "Taggable 2")
|
152
160
|
taggable3 = OtherTaggableModel.create!(:name => "Taggable 3")
|
153
|
-
|
161
|
+
|
154
162
|
taggable1.offering_list = "one, two"
|
155
163
|
taggable1.save
|
156
|
-
|
164
|
+
|
157
165
|
taggable2.need_list = "one, two"
|
158
166
|
taggable2.save
|
159
|
-
|
167
|
+
|
160
168
|
taggable3.offering_list = "one, two"
|
161
169
|
taggable3.save
|
162
|
-
|
170
|
+
|
163
171
|
taggable1.find_matching_contexts_for(OtherTaggableModel, :offerings, :needs).should include(taggable2)
|
164
172
|
taggable1.find_matching_contexts_for(OtherTaggableModel, :offerings, :needs).should_not include(taggable3)
|
165
173
|
end
|
166
|
-
|
174
|
+
|
167
175
|
it "should not include the object itself in the list of related objects" do
|
168
176
|
taggable1 = TaggableModel.create!(:name => "Taggable 1")
|
169
177
|
taggable2 = TaggableModel.create!(:name => "Taggable 2")
|
170
|
-
|
178
|
+
|
171
179
|
taggable1.tag_list = "one"
|
172
180
|
taggable1.save
|
173
|
-
|
181
|
+
|
174
182
|
taggable2.tag_list = "one, two"
|
175
183
|
taggable2.save
|
176
|
-
|
184
|
+
|
177
185
|
taggable1.find_related_tags.should include(taggable2)
|
178
186
|
taggable1.find_related_tags.should_not include(taggable1)
|
179
187
|
end
|
@@ -72,10 +72,21 @@ describe Tag do
|
|
72
72
|
|
73
73
|
it "should require a name" do
|
74
74
|
@tag.valid?
|
75
|
-
|
75
|
+
|
76
|
+
if ActiveRecord::VERSION::MAJOR >= 3
|
77
|
+
@tag.errors[:name].should == ["can't be blank"]
|
78
|
+
else
|
79
|
+
@tag.errors[:name].should == "can't be blank"
|
80
|
+
end
|
81
|
+
|
76
82
|
@tag.name = "something"
|
77
83
|
@tag.valid?
|
78
|
-
|
84
|
+
|
85
|
+
if ActiveRecord::VERSION::MAJOR >= 3
|
86
|
+
@tag.errors[:name].should == []
|
87
|
+
else
|
88
|
+
@tag.errors[:name].should be_nil
|
89
|
+
end
|
79
90
|
end
|
80
91
|
|
81
92
|
it "should equal a tag with the same name" do
|
@@ -7,10 +7,11 @@ describe "Taggable" do
|
|
7
7
|
end
|
8
8
|
|
9
9
|
it "should have tag types" do
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
10
|
+
[:tags, :languages, :skills, :needs, :offerings].each do |type|
|
11
|
+
TaggableModel.tag_types.should include type
|
12
|
+
end
|
13
|
+
|
14
|
+
@taggable.tag_types.should == TaggableModel.tag_types
|
14
15
|
end
|
15
16
|
|
16
17
|
it "should have tag_counts_on" do
|
@@ -25,18 +26,21 @@ describe "Taggable" do
|
|
25
26
|
|
26
27
|
it "should be able to create tags" do
|
27
28
|
@taggable.skill_list = "ruby, rails, css"
|
28
|
-
@taggable.instance_variable_get("@skill_list").instance_of?(
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
29
|
+
@taggable.instance_variable_get("@skill_list").instance_of?(TagList).should be_true
|
30
|
+
|
31
|
+
lambda {
|
32
|
+
@taggable.save
|
33
|
+
}.should change(Tag, :count).by(3)
|
33
34
|
end
|
34
35
|
|
35
36
|
it "should be able to create tags through the tag list directly" do
|
36
37
|
@taggable.tag_list_on(:test).add("hello")
|
37
38
|
@taggable.tag_list_cache_on(:test).should_not be_empty
|
39
|
+
@taggable.tag_list_on(:test).should == ["hello"]
|
40
|
+
|
38
41
|
@taggable.save
|
39
42
|
@taggable.save_tags
|
43
|
+
|
40
44
|
@taggable.reload
|
41
45
|
@taggable.tag_list_on(:test).should == ["hello"]
|
42
46
|
end
|
@@ -171,10 +175,10 @@ describe "Taggable" do
|
|
171
175
|
steve = TaggableModel.create(:name => 'Steve', :tag_list => 'fitter, happier, more productive', :skill_list => 'c++, java, python')
|
172
176
|
|
173
177
|
# Let's only find those productive Rails developers
|
174
|
-
TaggableModel.tagged_with('rails', :on => :skills, :order => 'taggable_models.name').should == [bob, frank]
|
175
|
-
TaggableModel.tagged_with('happier', :on => :tags, :order => 'taggable_models.name').should == [bob, steve]
|
176
|
-
TaggableModel.tagged_with('rails', :on => :skills).tagged_with('happier', :on => :tags).should == [bob]
|
177
|
-
TaggableModel.tagged_with('rails').tagged_with('happier', :on => :tags).should == [bob]
|
178
|
+
TaggableModel.tagged_with('rails', :on => :skills, :order => 'taggable_models.name').all.should == [bob, frank]
|
179
|
+
TaggableModel.tagged_with('happier', :on => :tags, :order => 'taggable_models.name').all.should == [bob, steve]
|
180
|
+
TaggableModel.tagged_with('rails', :on => :skills).tagged_with('happier', :on => :tags).all.should == [bob]
|
181
|
+
TaggableModel.tagged_with('rails').tagged_with('happier', :on => :tags).all.should == [bob]
|
178
182
|
end
|
179
183
|
|
180
184
|
it "should be able to find tagged with only the matching tags" do
|
@@ -202,6 +206,16 @@ describe "Taggable" do
|
|
202
206
|
}.should change(Tagging, :count).by(1)
|
203
207
|
end
|
204
208
|
|
209
|
+
describe "grouped_column_names_for method" do
|
210
|
+
it "should return all column names joined for Tag GROUP clause" do
|
211
|
+
@taggable.grouped_column_names_for(Tag).should == "tags.id, tags.name"
|
212
|
+
end
|
213
|
+
|
214
|
+
it "should return all column names joined for TaggableModel GROUP clause" do
|
215
|
+
@taggable.grouped_column_names_for(TaggableModel).should == "taggable_models.id, taggable_models.name, taggable_models.type, taggable_models.cached_tag_list"
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
205
219
|
describe "Single Table Inheritance" do
|
206
220
|
before do
|
207
221
|
[TaggableModel, Tag, Tagging, TaggableUser].each(&:delete_all)
|
@@ -209,44 +223,44 @@ describe "Taggable" do
|
|
209
223
|
@inherited_same = InheritingTaggableModel.new(:name => "inherited same")
|
210
224
|
@inherited_different = AlteredInheritingTaggableModel.new(:name => "inherited different")
|
211
225
|
end
|
212
|
-
|
226
|
+
|
213
227
|
it "should be able to save tags for inherited models" do
|
214
228
|
@inherited_same.tag_list = "bob, kelso"
|
215
229
|
@inherited_same.save
|
216
230
|
InheritingTaggableModel.tagged_with("bob").first.should == @inherited_same
|
217
231
|
end
|
218
|
-
|
232
|
+
|
219
233
|
it "should find STI tagged models on the superclass" do
|
220
234
|
@inherited_same.tag_list = "bob, kelso"
|
221
235
|
@inherited_same.save
|
222
236
|
TaggableModel.tagged_with("bob").first.should == @inherited_same
|
223
237
|
end
|
224
|
-
|
238
|
+
|
225
239
|
it "should be able to add on contexts only to some subclasses" do
|
226
240
|
@inherited_different.part_list = "fork, spoon"
|
227
241
|
@inherited_different.save
|
228
242
|
InheritingTaggableModel.tagged_with("fork", :on => :parts).should be_empty
|
229
243
|
AlteredInheritingTaggableModel.tagged_with("fork", :on => :parts).first.should == @inherited_different
|
230
244
|
end
|
231
|
-
|
245
|
+
|
232
246
|
it "should have different tag_counts_on for inherited models" do
|
233
247
|
@inherited_same.tag_list = "bob, kelso"
|
234
248
|
@inherited_same.save!
|
235
249
|
@inherited_different.tag_list = "fork, spoon"
|
236
250
|
@inherited_different.save!
|
237
|
-
|
251
|
+
|
238
252
|
InheritingTaggableModel.tag_counts_on(:tags).map(&:name).should == %w(bob kelso)
|
239
253
|
AlteredInheritingTaggableModel.tag_counts_on(:tags).map(&:name).should == %w(fork spoon)
|
240
254
|
TaggableModel.tag_counts_on(:tags).map(&:name).should == %w(bob kelso fork spoon)
|
241
255
|
end
|
242
|
-
|
256
|
+
|
243
257
|
it 'should store same tag without validation conflict' do
|
244
258
|
@taggable.tag_list = 'one'
|
245
259
|
@taggable.save!
|
246
|
-
|
260
|
+
|
247
261
|
@inherited_same.tag_list = 'one'
|
248
262
|
@inherited_same.save!
|
249
|
-
|
263
|
+
|
250
264
|
@inherited_same.update_attributes! :name => 'foo'
|
251
265
|
end
|
252
266
|
end
|