dm-taggings 0.11.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.rspec +2 -0
- data/Gemfile +25 -100
- data/Rakefile +6 -7
- data/VERSION +1 -1
- data/dm-taggings.gemspec +57 -54
- data/lib/dm-taggings.rb +5 -5
- data/lib/dm-taggings/is/taggable.rb +37 -35
- data/lib/dm-taggings/is/tagger.rb +12 -6
- data/lib/dm-taggings/is/tagging.rb +3 -3
- data/lib/dm-taggings/spec/taggable_shared_spec.rb +108 -114
- data/lib/dm-taggings/spec/tagger_shared_spec.rb +16 -15
- data/spec/fixtures/models.rb +28 -25
- data/spec/integration/post_spec.rb +5 -5
- data/spec/integration/tag_spec.rb +1 -1
- data/spec/integration/taggable_spec.rb +4 -4
- data/spec/integration/user_spec.rb +5 -8
- data/spec/spec_helper.rb +11 -1
- metadata +47 -50
- data/.gitignore +0 -9
- data/spec/spec.opts +0 -2
@@ -39,9 +39,6 @@ module DataMapper
|
|
39
39
|
|
40
40
|
# Add instance-methods
|
41
41
|
include DataMapper::Is::Tagger::InstanceMethods
|
42
|
-
|
43
|
-
cattr_accessor(:taggable_object_classes)
|
44
|
-
self.taggable_object_classes = []
|
45
42
|
end
|
46
43
|
|
47
44
|
raise "options[:for] is missing" unless options[:for]
|
@@ -50,6 +47,14 @@ module DataMapper
|
|
50
47
|
end
|
51
48
|
|
52
49
|
module ClassMethods
|
50
|
+
def self.extended(base)
|
51
|
+
base.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
52
|
+
def self.taggable_object_classes
|
53
|
+
@@taggable_object_classes ||= []
|
54
|
+
end
|
55
|
+
RUBY
|
56
|
+
end
|
57
|
+
|
53
58
|
# Return if a model is tagger
|
54
59
|
#
|
55
60
|
# @return [TrueClass]
|
@@ -71,10 +76,12 @@ module DataMapper
|
|
71
76
|
self.taggable_object_classes << taggable_object_class
|
72
77
|
|
73
78
|
has n, taggable_object_class.tagging_relationship_name,
|
79
|
+
:model => taggable_object_class.tagging_class,
|
74
80
|
:constraint => :destroy
|
75
81
|
|
76
82
|
has n, taggable_object_class.taggable_relationship_name,
|
77
|
-
:
|
83
|
+
:model => taggable_object_class.name,
|
84
|
+
:through => taggable_object_class.tagging_relationship_name,
|
78
85
|
:constraint => :destroy
|
79
86
|
end
|
80
87
|
end
|
@@ -94,7 +101,7 @@ module DataMapper
|
|
94
101
|
#
|
95
102
|
# @api public
|
96
103
|
def tag!(taggable, options={})
|
97
|
-
unless self.taggable_object_classes.include?(taggable.class)
|
104
|
+
unless self.class.taggable_object_classes.include?(taggable.class)
|
98
105
|
raise "Object of type #{taggable.class} isn't taggable!"
|
99
106
|
end
|
100
107
|
|
@@ -112,4 +119,3 @@ module DataMapper
|
|
112
119
|
end # Tagger
|
113
120
|
end # Is
|
114
121
|
end # DataMapper
|
115
|
-
|
@@ -2,12 +2,12 @@ module Tagging
|
|
2
2
|
include DataMapper::Resource
|
3
3
|
|
4
4
|
property :id, Serial
|
5
|
-
|
5
|
+
|
6
|
+
belongs_to :tag
|
6
7
|
|
7
8
|
is :remixable, :suffix => "tag"
|
8
9
|
|
9
10
|
def tagger=(tagger)
|
10
|
-
send("#{DataMapper::Inflector.underscore(tagger.class.name).to_sym}=", tagger)
|
11
|
+
send("#{DataMapper::Inflector.underscore(DataMapper::Inflector.demodulize(tagger.class.name)).to_sym}=", tagger)
|
11
12
|
end
|
12
13
|
end
|
13
|
-
|
@@ -1,23 +1,21 @@
|
|
1
1
|
share_examples_for 'A taggable resource' do
|
2
2
|
def create_taggable(attrs={})
|
3
|
-
|
3
|
+
taggable.create(@taggable_attributes.merge(attrs))
|
4
4
|
end
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
end
|
10
|
-
|
11
|
-
@taggable_attributes ||= {}
|
6
|
+
def taggable
|
7
|
+
raise "taggable should be defined"
|
8
|
+
end
|
12
9
|
|
13
|
-
|
14
|
-
|
10
|
+
let(:foo_tag) { Tag["foo"] }
|
11
|
+
let(:bar_tag) { Tag["bar"] }
|
15
12
|
|
16
|
-
|
13
|
+
before :all do
|
14
|
+
@taggable_attributes ||= {}
|
17
15
|
end
|
18
16
|
|
19
17
|
describe "public class methods" do
|
20
|
-
subject {
|
18
|
+
subject { taggable }
|
21
19
|
|
22
20
|
it { should respond_to(:is_taggable) }
|
23
21
|
it { should respond_to(:taggable?) }
|
@@ -27,40 +25,52 @@ share_examples_for 'A taggable resource' do
|
|
27
25
|
it { should respond_to(:tagging_class) }
|
28
26
|
it { should respond_to(:tagging_parent_name) }
|
29
27
|
it { should respond_to(:taggable_relationship_name) }
|
28
|
+
it { should respond_to(:taggable_options) }
|
29
|
+
|
30
|
+
describe ".taggable_options" do
|
31
|
+
let(:expected_options) { { :tag_list_separator => ',' } }
|
32
|
+
|
33
|
+
it "should set the defaults" do
|
34
|
+
taggable.taggable_options.should == expected_options
|
35
|
+
end
|
36
|
+
end
|
30
37
|
|
31
38
|
describe ".taggable?" do
|
32
39
|
it "should return true" do
|
33
|
-
|
40
|
+
taggable.taggable?.should be(true)
|
34
41
|
end
|
35
42
|
end
|
36
43
|
|
37
44
|
describe "relationships" do
|
38
|
-
subject {
|
45
|
+
subject { taggable.relationships.named?(taggable.tagging_relationship_name) }
|
39
46
|
|
40
|
-
it { should
|
47
|
+
it { should be(true) }
|
41
48
|
|
42
49
|
describe "tagging constraint" do
|
43
|
-
subject {
|
50
|
+
subject { taggable.tagging_relationship.constraint }
|
44
51
|
it { subject.should eql(:destroy!) }
|
45
52
|
end
|
46
53
|
end
|
47
54
|
|
48
55
|
describe ".tagged_with" do
|
49
|
-
|
50
|
-
|
51
|
-
|
56
|
+
let(:resource_one) { create_taggable(:tag_list => "red, green, blue") }
|
57
|
+
let(:resource_two) { create_taggable(:tag_list => "orange, yellow") }
|
58
|
+
|
59
|
+
before do
|
60
|
+
resource_one
|
61
|
+
resource_two
|
52
62
|
end
|
53
63
|
|
64
|
+
subject { taggable.tagged_with(["red", "yellow", "purple"]) }
|
65
|
+
|
54
66
|
it "should return correct resources" do
|
55
|
-
|
56
|
-
result.size.should eql(2)
|
57
|
-
result.should include(@resource_one, @resource_two)
|
67
|
+
should == [ resource_one, resource_two ]
|
58
68
|
end
|
59
69
|
end
|
60
70
|
end
|
61
71
|
|
62
72
|
describe "public instance methods" do
|
63
|
-
subject {
|
73
|
+
subject { taggable.new }
|
64
74
|
|
65
75
|
it { should respond_to(:tag) }
|
66
76
|
it { should respond_to(:tag!) }
|
@@ -70,189 +80,173 @@ share_examples_for 'A taggable resource' do
|
|
70
80
|
it { should respond_to(:taggings) }
|
71
81
|
|
72
82
|
describe ".tag" do
|
73
|
-
|
74
|
-
|
75
|
-
@taggings = @resource.tag([@foo_tag, @bar_tag])
|
76
|
-
end
|
83
|
+
let(:resource) { create_taggable }
|
84
|
+
let(:tags) { resource.tag([ foo_tag, bar_tag ]) }
|
77
85
|
|
78
|
-
it "should set new
|
79
|
-
|
86
|
+
it "should set new tags" do
|
87
|
+
tags.should == resource.tags
|
80
88
|
end
|
81
89
|
|
82
|
-
it "should not
|
83
|
-
|
90
|
+
it "should not save new taggings" do
|
91
|
+
resource.taggings.all? { |tagging| tagging.new? }.should be(true)
|
84
92
|
end
|
85
93
|
end
|
86
94
|
|
87
95
|
describe ".tag!" do
|
88
|
-
|
89
|
-
|
90
|
-
|
96
|
+
let(:resource) { create_taggable }
|
97
|
+
let(:new_tags) { [ foo_tag, bar_tag ] }
|
98
|
+
let(:tags) { resource.tag!(new_tags) }
|
99
|
+
|
100
|
+
|
101
|
+
it "returns added tags" do
|
102
|
+
tags.should == new_tags
|
91
103
|
end
|
92
104
|
|
93
|
-
it "
|
94
|
-
|
105
|
+
it "creates new taggings" do
|
106
|
+
resource.tags.should == tags
|
95
107
|
end
|
96
108
|
end
|
97
109
|
|
98
110
|
describe ".untag" do
|
99
|
-
|
100
|
-
|
101
|
-
@resource = create_taggable
|
102
|
-
@taggings = @resource.tag!([@foo_tag, @bar_tag])
|
111
|
+
let(:tags) { [ foo_tag, bar_tag ] }
|
112
|
+
let(:resource) { create_taggable(:tags => tags) }
|
103
113
|
|
104
|
-
|
114
|
+
describe "all" do
|
115
|
+
before do
|
116
|
+
resource.untag
|
105
117
|
end
|
106
118
|
|
107
|
-
it "
|
108
|
-
|
119
|
+
it "removes taggings from loaded collection" do
|
120
|
+
resource.tags.should be_empty
|
109
121
|
end
|
110
122
|
|
111
|
-
it "
|
112
|
-
|
123
|
+
it "doesn't destroy the taggings" do
|
124
|
+
resource.reload.tags.should == tags
|
113
125
|
end
|
114
126
|
end
|
115
127
|
|
116
|
-
describe "specific
|
117
|
-
before
|
118
|
-
|
119
|
-
@taggings = @resource.tag!([@foo_tag, @bar_tag])
|
120
|
-
|
121
|
-
@resource.untag([@foo_tag])
|
128
|
+
describe "specific tags" do
|
129
|
+
before do
|
130
|
+
resource.untag([ foo_tag ])
|
122
131
|
end
|
123
132
|
|
124
|
-
it "should remove the
|
125
|
-
|
126
|
-
end
|
127
|
-
|
128
|
-
it "should remove the related tag" do
|
129
|
-
@resource.tags.should_not include(@foo_tag)
|
133
|
+
it "should remove the tag from loaded collection" do
|
134
|
+
resource.tags.should == [ bar_tag ]
|
130
135
|
end
|
131
136
|
end
|
132
137
|
|
133
138
|
describe "when save is called" do
|
134
|
-
before
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
@resource.untag
|
139
|
-
end
|
140
|
-
|
141
|
-
it "should return true" do
|
142
|
-
pending "Currently DataMapper doesn't support saving an empty collection" do
|
143
|
-
@resource.save.should be(true)
|
144
|
-
end
|
139
|
+
before do
|
140
|
+
resource.untag
|
141
|
+
resource.save
|
142
|
+
resource.reload
|
145
143
|
end
|
146
144
|
|
147
145
|
it "should destroy taggings" do
|
148
|
-
|
149
|
-
@resource.reload.taggings.should be_empty
|
150
|
-
end
|
146
|
+
resource.taggings.should be_empty
|
151
147
|
end
|
152
148
|
|
153
149
|
it "should destroy tags" do
|
154
|
-
|
155
|
-
@resource.reload.tags.should be_empty
|
156
|
-
end
|
150
|
+
resource.tags.should be_empty
|
157
151
|
end
|
158
152
|
end
|
159
153
|
end
|
160
154
|
|
161
155
|
describe ".untag!" do
|
156
|
+
let(:tags) { [ foo_tag, bar_tag ] }
|
157
|
+
let(:resource) { create_taggable(:tags => tags) }
|
158
|
+
|
162
159
|
describe "all" do
|
163
|
-
before
|
164
|
-
@
|
165
|
-
|
160
|
+
before do
|
161
|
+
@result = resource.untag!
|
162
|
+
resource.reload
|
163
|
+
end
|
166
164
|
|
167
|
-
|
165
|
+
it "returns removed tags" do
|
166
|
+
@result == tags
|
168
167
|
end
|
169
168
|
|
170
|
-
it "
|
171
|
-
|
169
|
+
it "destroys the taggings" do
|
170
|
+
resource.tags.should be_empty
|
172
171
|
end
|
173
172
|
end
|
174
173
|
|
175
|
-
describe "specific
|
176
|
-
before
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
@resource.untag!([@foo_tag])
|
181
|
-
@resource.reload
|
174
|
+
describe "specific tags" do
|
175
|
+
before do
|
176
|
+
resource.untag!([ foo_tag ])
|
177
|
+
resource.reload
|
182
178
|
end
|
183
179
|
|
184
|
-
subject {
|
180
|
+
subject { resource.tags }
|
185
181
|
|
186
|
-
it { should_not include(
|
187
|
-
it { should include(
|
182
|
+
it { should_not include(foo_tag) }
|
183
|
+
it { should include(bar_tag) }
|
188
184
|
end
|
189
185
|
end
|
190
186
|
|
191
187
|
describe ".tag_list=" do
|
192
188
|
describe "with a list of tag names" do
|
193
189
|
describe "with blank values" do
|
194
|
-
|
195
|
-
@resource = create_taggable(:tag_list => "foo, , ,bar, , ")
|
196
|
-
end
|
190
|
+
let(:resource) { create_taggable(:tag_list => "foo, , ,bar, , ") }
|
197
191
|
|
198
192
|
it "should add new tags and reject blank names" do
|
199
|
-
|
193
|
+
resource.tags.should == [ Tag["foo"], Tag["bar"] ]
|
200
194
|
end
|
201
195
|
end
|
202
196
|
|
203
197
|
describe "when tags are removed and added" do
|
204
|
-
|
205
|
-
|
206
|
-
|
198
|
+
let(:resource) { create_taggable(:tag_list => "foo, bar") }
|
199
|
+
|
200
|
+
before do
|
201
|
+
resource.update(:tag_list => "foo, bar, pub")
|
207
202
|
end
|
208
203
|
|
209
204
|
it "should add new tags" do
|
210
|
-
|
205
|
+
resource.tags.should == [ Tag["foo"], Tag["bar"], Tag["pub"] ]
|
211
206
|
end
|
212
207
|
end
|
213
208
|
|
214
209
|
describe "when tags are added" do
|
215
|
-
|
216
|
-
|
217
|
-
|
210
|
+
let(:resource) { create_taggable(:tag_list => "foo, bar") }
|
211
|
+
|
212
|
+
before do
|
213
|
+
resource.update(:tag_list => "bar, pub")
|
218
214
|
end
|
219
215
|
|
220
216
|
it "should add new tags" do
|
221
|
-
|
217
|
+
resource.tags.should include(Tag["bar"], Tag["pub"])
|
222
218
|
end
|
223
219
|
|
224
220
|
it "should remove tags" do
|
225
|
-
|
221
|
+
resource.tags.should_not include(Tag["foo"])
|
226
222
|
end
|
227
223
|
end
|
228
224
|
end
|
229
225
|
|
230
226
|
describe "when no list of tag names is given" do
|
227
|
+
let(:resource) { create_taggable(:tag_list => "foo, bar") }
|
228
|
+
|
231
229
|
before :all do
|
232
|
-
|
233
|
-
@resource.update(:tag_list => "")
|
230
|
+
resource.update(:tag_list => "")
|
234
231
|
end
|
235
232
|
|
236
233
|
it "should destroy taggings" do
|
237
|
-
|
234
|
+
resource.reload.taggings.should be_blank
|
238
235
|
end
|
239
236
|
|
240
237
|
it "should remove the tags" do
|
241
|
-
|
238
|
+
resource.reload.tags.should be_blank
|
242
239
|
end
|
243
240
|
end
|
244
241
|
end
|
245
242
|
|
246
243
|
describe ".tag_list" do
|
247
|
-
|
248
|
-
|
249
|
-
@expected = @tag_names.join(', ')
|
250
|
-
@resource = create_taggable(:tag_list => @expected)
|
251
|
-
end
|
244
|
+
let(:expected_tag_names) { %w(nice cool awesome).join(',') }
|
245
|
+
let(:resource) { create_taggable(:tag_list => expected_tag_names) }
|
252
246
|
|
253
|
-
|
254
|
-
|
255
|
-
|
247
|
+
subject { resource.tag_list }
|
248
|
+
|
249
|
+
it { should == expected_tag_names }
|
256
250
|
end
|
257
251
|
end
|
258
252
|
end
|
@@ -1,32 +1,33 @@
|
|
1
1
|
share_examples_for 'A tagger resource' do
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
end
|
6
|
-
|
7
|
-
@foo_tag = Tag["foo"]
|
8
|
-
@bar_tag = Tag["bar"]
|
2
|
+
def taggable
|
3
|
+
raise "taggable should be defined"
|
4
|
+
end
|
9
5
|
|
10
|
-
|
6
|
+
def tagger
|
7
|
+
raise "tagger should be defined"
|
11
8
|
end
|
12
9
|
|
13
|
-
|
10
|
+
let(:foo_tag) { Tag["foo"] }
|
11
|
+
let(:bar_tag) { Tag["bar"] }
|
12
|
+
let(:tags) { [ foo_tag, bar_tag ] }
|
13
|
+
|
14
|
+
subject { tagger }
|
14
15
|
|
15
16
|
[ :is_tagger, :tagger?, :add_taggable_object_classes, :taggable_object_classes ].each do |method|
|
16
17
|
it { should respond_to(method) }
|
17
18
|
end
|
18
19
|
|
19
20
|
describe ".tagger?" do
|
20
|
-
subject {
|
21
|
+
subject { tagger.tagger? }
|
21
22
|
it { should be(true) }
|
22
23
|
end
|
23
24
|
|
24
25
|
describe "#tag!" do
|
25
26
|
before :all do
|
26
|
-
@tagger_resource =
|
27
|
-
@taggable_resource =
|
27
|
+
@tagger_resource = tagger.create
|
28
|
+
@taggable_resource = taggable.create
|
28
29
|
|
29
|
-
@tags = @tagger_resource.tag!(@taggable_resource, :with =>
|
30
|
+
@tags = @tagger_resource.tag!(@taggable_resource, :with => tags)
|
30
31
|
end
|
31
32
|
|
32
33
|
it "should tag the taggable resource" do
|
@@ -34,12 +35,12 @@ share_examples_for 'A tagger resource' do
|
|
34
35
|
end
|
35
36
|
|
36
37
|
it "should associate tagger with taggable" do
|
37
|
-
@tagger_resource.reload.send(DataMapper::Inflector.underscore(
|
38
|
+
@tagger_resource.reload.send(DataMapper::Inflector.pluralize(DataMapper::Inflector.underscore(DataMapper::Inflector.demodulize(taggable.name)))).should include(@taggable_resource)
|
38
39
|
end
|
39
40
|
|
40
41
|
it "should associate taggings with tagger" do
|
41
42
|
@taggable_resource.taggings.each do |tagging|
|
42
|
-
tagging.send(DataMapper::Inflector.underscore(
|
43
|
+
tagging.send(DataMapper::Inflector.underscore(DataMapper::Inflector.demodulize(tagger.name))).should eql(@tagger_resource)
|
43
44
|
end
|
44
45
|
end
|
45
46
|
end
|