dm-taggings 0.11.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 +9 -0
- data/Gemfile +142 -0
- data/History.txt +1 -0
- data/LICENSE +20 -0
- data/Manifest.txt +18 -0
- data/README.rdoc +155 -0
- data/Rakefile +45 -0
- data/TODO +5 -0
- data/VERSION +1 -0
- data/dm-taggings.gemspec +89 -0
- data/lib/dm-taggings.rb +19 -0
- data/lib/dm-taggings/is/tag.rb +42 -0
- data/lib/dm-taggings/is/taggable.rb +245 -0
- data/lib/dm-taggings/is/tagger.rb +115 -0
- data/lib/dm-taggings/is/tagging.rb +13 -0
- data/lib/dm-taggings/is/version.rb +7 -0
- data/lib/dm-taggings/spec/taggable_shared_spec.rb +259 -0
- data/lib/dm-taggings/spec/tagger_shared_spec.rb +47 -0
- data/spec/fixtures/models.rb +35 -0
- data/spec/integration/post_spec.rb +14 -0
- data/spec/integration/tag_spec.rb +24 -0
- data/spec/integration/taggable_spec.rb +39 -0
- data/spec/integration/user_spec.rb +18 -0
- data/spec/spec.opts +2 -0
- data/spec/spec_helper.rb +21 -0
- data/tasks/hoe.rb +39 -0
- data/tasks/yard.rake +9 -0
- data/tasks/yardstick.rake +19 -0
- metadata +171 -0
data/lib/dm-taggings.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
|
3
|
+
# Add all external dependencies for the plugin here
|
4
|
+
require 'dm-core'
|
5
|
+
require 'dm-constraints'
|
6
|
+
require 'dm-is-remixable'
|
7
|
+
|
8
|
+
# Require plugin-files
|
9
|
+
|
10
|
+
dir = Pathname(__FILE__).dirname.expand_path / 'dm-taggings' / 'is'
|
11
|
+
|
12
|
+
require dir / 'taggable.rb'
|
13
|
+
require dir / 'tag.rb'
|
14
|
+
require dir / 'tagging.rb'
|
15
|
+
require dir / 'tagger.rb'
|
16
|
+
|
17
|
+
# Include the plugin in Resource
|
18
|
+
DataMapper::Model.append_extensions DataMapper::Is::Taggable
|
19
|
+
DataMapper::Model.append_extensions DataMapper::Is::Tagger
|
@@ -0,0 +1,42 @@
|
|
1
|
+
class Tag
|
2
|
+
include DataMapper::Resource
|
3
|
+
|
4
|
+
property :id, Serial
|
5
|
+
property :name, String, :required => true, :unique => true
|
6
|
+
|
7
|
+
# Shortcut to build method
|
8
|
+
#
|
9
|
+
# @see Tag.build
|
10
|
+
#
|
11
|
+
# @api public
|
12
|
+
def self.[](name)
|
13
|
+
build(name)
|
14
|
+
end
|
15
|
+
|
16
|
+
# Find or create a tag with the give name
|
17
|
+
#
|
18
|
+
# @param [String] name
|
19
|
+
# A name of a tag
|
20
|
+
#
|
21
|
+
# @return [DataMapper::Resource]
|
22
|
+
# A tag resource instance
|
23
|
+
#
|
24
|
+
# @api public
|
25
|
+
def self.build(name)
|
26
|
+
Tag.first_or_create(:name => name.strip) if name
|
27
|
+
end
|
28
|
+
|
29
|
+
# An overridden name attribute setter that strips the value
|
30
|
+
#
|
31
|
+
# @param [String] value
|
32
|
+
# A value to be set as the tag name
|
33
|
+
#
|
34
|
+
# @return [String]
|
35
|
+
# The name
|
36
|
+
#
|
37
|
+
# @api public
|
38
|
+
def name=(value)
|
39
|
+
super(value.strip) if value
|
40
|
+
name
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,245 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module Is
|
3
|
+
module Taggable
|
4
|
+
|
5
|
+
# Make a resource taggable
|
6
|
+
#
|
7
|
+
# @example
|
8
|
+
#
|
9
|
+
# class Post
|
10
|
+
# include DataMapper::Resource
|
11
|
+
#
|
12
|
+
# property :id, Serial
|
13
|
+
# property :title, String
|
14
|
+
# property :content, Text
|
15
|
+
#
|
16
|
+
# is :taggable
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# @param [Hash] options(optional)
|
20
|
+
# A hash with options
|
21
|
+
# @option options [Array] :by
|
22
|
+
# A list of DataMapper models that should become taggers
|
23
|
+
#
|
24
|
+
# @api public
|
25
|
+
def is_taggable(options={})
|
26
|
+
|
27
|
+
# Add class-methods
|
28
|
+
extend DataMapper::Is::Taggable::ClassMethods
|
29
|
+
# Add instance-methods
|
30
|
+
include DataMapper::Is::Taggable::InstanceMethods
|
31
|
+
|
32
|
+
class << self
|
33
|
+
attr_reader :tagging_parent_name, :tagging_relationship_name, :tagging_relationship,
|
34
|
+
:tagging_class, :taggable_relationship_name
|
35
|
+
end
|
36
|
+
|
37
|
+
# Make the magic happen
|
38
|
+
options[:by] ||= []
|
39
|
+
|
40
|
+
remix n, :taggings
|
41
|
+
|
42
|
+
@tagging_parent_name = DataMapper::Inflector.underscore(name).to_sym
|
43
|
+
@tagging_relationship_name = "#{@tagging_parent_name}_tags".to_sym
|
44
|
+
@tagging_relationship = relationships[@tagging_relationship_name]
|
45
|
+
@tagging_class = @tagging_relationship.child_model
|
46
|
+
|
47
|
+
@taggable_relationship_name = DataMapper::Inflector.underscore(name).pluralize.to_sym
|
48
|
+
|
49
|
+
@tagging_relationship.add_constraint_option(
|
50
|
+
@taggable_relationship_name, @tagging_class, self, :constraint => :destroy!)
|
51
|
+
|
52
|
+
tagging_parent_name = @tagging_parent_name
|
53
|
+
|
54
|
+
enhance :taggings do
|
55
|
+
belongs_to :tag
|
56
|
+
belongs_to tagging_parent_name
|
57
|
+
|
58
|
+
options[:by].each do |tagger_class|
|
59
|
+
belongs_to DataMapper::Inflector.underscore(tagger_class.name), :required => false
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
has n, :tags, :through => @tagging_relationship_name, :constraint => :destroy!
|
64
|
+
|
65
|
+
Tag.has n, @tagging_relationship_name, :constraint => :destroy!
|
66
|
+
Tag.has n, @taggable_relationship_name, :through => @tagging_relationship_name
|
67
|
+
|
68
|
+
options[:by].each do |tagger_class|
|
69
|
+
tagger_class.is :tagger, :for => [self]
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
module ClassMethods
|
74
|
+
# @attr_reader [String] tagging_parent_name
|
75
|
+
# @attr_reader [String] tagging_relationship_name
|
76
|
+
# @attr_reader [DataMapper::Associations::OneToMany::Relationship] tagging_relationship
|
77
|
+
# @attr_reader [DataMapper::Resource] tagging_class
|
78
|
+
# @attr_reader [String] taggable_relationship_name
|
79
|
+
|
80
|
+
# @api public
|
81
|
+
def taggable?
|
82
|
+
true
|
83
|
+
end
|
84
|
+
|
85
|
+
# Return all the taggable resources that are tagged with the given list of tags.
|
86
|
+
#
|
87
|
+
# Can be chained, for instance:
|
88
|
+
#
|
89
|
+
# Post.tagged_with(["foo", "bar"]).all(:created_at.lt => 1.day.ago)
|
90
|
+
#
|
91
|
+
# @param [Array] tags_or_names
|
92
|
+
# A list of either tag resources or tag names
|
93
|
+
#
|
94
|
+
# @return [DataMapper::Collection]
|
95
|
+
# A collection of taggables
|
96
|
+
#
|
97
|
+
# @api public
|
98
|
+
def tagged_with(tags_or_names)
|
99
|
+
tags_or_names = [tags_or_names] unless tags_or_names.kind_of?(Array)
|
100
|
+
|
101
|
+
tag_ids = if tags_or_names.all? { |tag| tag.kind_of?(Tag) }
|
102
|
+
tags_or_names
|
103
|
+
else
|
104
|
+
Tag.all(:name => tags_or_names)
|
105
|
+
end.map { |tag| tag.id }
|
106
|
+
|
107
|
+
all("#{tagging_relationship_name}.tag_id" => tag_ids)
|
108
|
+
end
|
109
|
+
end # ClassMethods
|
110
|
+
|
111
|
+
module InstanceMethods
|
112
|
+
# Add tags to a resource but do not persist them.
|
113
|
+
#
|
114
|
+
# @param [Array] tags_or_names
|
115
|
+
# A list of either tag resources or tag names
|
116
|
+
#
|
117
|
+
# @return [DataMapper::Associations::OneToMany::Collection]
|
118
|
+
# A DataMapper collection of resource's tags
|
119
|
+
#
|
120
|
+
# @api public
|
121
|
+
def tag(tags_or_names)
|
122
|
+
tags = extract_tags_from_names(tags_or_names)
|
123
|
+
|
124
|
+
tags.each do |tag|
|
125
|
+
next if self.tags.include?(tag)
|
126
|
+
taggings.new(:tag => tag)
|
127
|
+
end
|
128
|
+
|
129
|
+
taggings
|
130
|
+
end
|
131
|
+
|
132
|
+
# Add tags to a resource and persists them.
|
133
|
+
#
|
134
|
+
# @param [Array] tags_or_names
|
135
|
+
# A list of either tag resources or tag names
|
136
|
+
#
|
137
|
+
# @return [DataMapper::Associations::OneToMany::Collection]
|
138
|
+
# A DataMapper collection of resource's tags
|
139
|
+
#
|
140
|
+
# @api public
|
141
|
+
def tag!(tags_or_names)
|
142
|
+
taggings = tag(tags_or_names)
|
143
|
+
taggings.save! unless new?
|
144
|
+
taggings
|
145
|
+
end
|
146
|
+
|
147
|
+
# Delete given tags from a resource collection without actually deleting
|
148
|
+
# them from the datastore. Everything will be deleted if no tags are given.
|
149
|
+
#
|
150
|
+
# @param [Array] tags_or_names (optional)
|
151
|
+
# A list of either tag resources or tag names
|
152
|
+
#
|
153
|
+
# @return [DataMapper::Associations::OneToMany::Collection]
|
154
|
+
# A DataMapper collection of resource's tags
|
155
|
+
#
|
156
|
+
# @api public
|
157
|
+
def untag(tags_or_names=nil)
|
158
|
+
tags = extract_tags_from_names(tags_or_names) if tags_or_names
|
159
|
+
|
160
|
+
taggings_to_destroy = if tags.blank?
|
161
|
+
taggings.all
|
162
|
+
else
|
163
|
+
taggings.all(:tag => tags)
|
164
|
+
end
|
165
|
+
|
166
|
+
self.taggings = taggings - taggings_to_destroy
|
167
|
+
|
168
|
+
taggings_to_destroy
|
169
|
+
end
|
170
|
+
|
171
|
+
# Same as untag but actually delete the tags from the datastore.
|
172
|
+
#
|
173
|
+
# @param [Array] tags_or_names (optional)
|
174
|
+
# A list of either tag resources or tag names
|
175
|
+
#
|
176
|
+
# @return [DataMapper::Associations::OneToMany::Collection]
|
177
|
+
# A DataMapper collection of resource's tags
|
178
|
+
#
|
179
|
+
# @api public
|
180
|
+
def untag!(tags_or_names=nil)
|
181
|
+
taggings_to_destroy = untag(tags_or_names)
|
182
|
+
taggings_to_destroy.destroy! unless new?
|
183
|
+
taggings_to_destroy
|
184
|
+
end
|
185
|
+
|
186
|
+
# Return a string representation of tags collection
|
187
|
+
#
|
188
|
+
# @return [String]
|
189
|
+
# A tag list separated by commas
|
190
|
+
#
|
191
|
+
# @api public
|
192
|
+
def tag_list
|
193
|
+
@tag_list ||= tags.collect { |tag| tag.name }.join(", ")
|
194
|
+
end
|
195
|
+
|
196
|
+
# Tag a resource using tag names from the give list separated by commas.
|
197
|
+
#
|
198
|
+
# @param [String]
|
199
|
+
# A tag list separated by commas
|
200
|
+
#
|
201
|
+
# @return [DataMapper::Associations::OneToMany::Collection]
|
202
|
+
# A DataMapper collection of resource's tags
|
203
|
+
def tag_list=(list)
|
204
|
+
@tag_list = list
|
205
|
+
|
206
|
+
tag_names = list.split(",").map { |name| name.blank? ? nil : name.strip }.compact
|
207
|
+
|
208
|
+
old_tag_names = taggings.map { |tagging| tagging.tag.name } - tag_names
|
209
|
+
|
210
|
+
untag!(old_tag_names)
|
211
|
+
tag(tag_names)
|
212
|
+
end
|
213
|
+
|
214
|
+
# @api public
|
215
|
+
def reload
|
216
|
+
@tag_list = nil
|
217
|
+
super
|
218
|
+
end
|
219
|
+
|
220
|
+
# @api public
|
221
|
+
def taggings
|
222
|
+
send(self.class.tagging_relationship_name)
|
223
|
+
end
|
224
|
+
|
225
|
+
# @api public
|
226
|
+
def taggings=(taggings)
|
227
|
+
send("#{self.class.tagging_relationship_name}=", taggings)
|
228
|
+
end
|
229
|
+
|
230
|
+
protected
|
231
|
+
|
232
|
+
# @api private
|
233
|
+
def extract_tags_from_names(tags_or_names)
|
234
|
+
tags_or_names = [tags_or_names] unless tags_or_names.kind_of?(Array)
|
235
|
+
|
236
|
+
tags_or_names.map do |tag_or_name|
|
237
|
+
tag_or_name.kind_of?(Tag) ? tag_or_name : Tag[tag_or_name]
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end # InstanceMethods
|
241
|
+
|
242
|
+
end # Taggable
|
243
|
+
end # Is
|
244
|
+
end # DataMapper
|
245
|
+
|
@@ -0,0 +1,115 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module Is
|
3
|
+
module Tagger
|
4
|
+
# Set up a resource as tagger
|
5
|
+
#
|
6
|
+
# @example
|
7
|
+
#
|
8
|
+
# class Song
|
9
|
+
# include DataMapper::Resource
|
10
|
+
#
|
11
|
+
# property :id, Serial
|
12
|
+
# property :title, String
|
13
|
+
#
|
14
|
+
# is :taggable, :by => [ User ]
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# class User
|
18
|
+
# include DataMapper::Resource
|
19
|
+
#
|
20
|
+
# property :id, Serial
|
21
|
+
# property :name, String
|
22
|
+
#
|
23
|
+
# is :tagger, :for => [ Song ]
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# @param [Hash] options
|
27
|
+
# A hash of options
|
28
|
+
# @option options [Array] :for
|
29
|
+
# A list of DataMapper taggable models
|
30
|
+
#
|
31
|
+
# @return [Array]
|
32
|
+
# A list of DataMapper taggable models
|
33
|
+
#
|
34
|
+
# @api public
|
35
|
+
def is_tagger(options={})
|
36
|
+
unless self.respond_to?(:tagger?)
|
37
|
+
# Add class-methods
|
38
|
+
extend DataMapper::Is::Tagger::ClassMethods
|
39
|
+
|
40
|
+
# Add instance-methods
|
41
|
+
include DataMapper::Is::Tagger::InstanceMethods
|
42
|
+
|
43
|
+
cattr_accessor(:taggable_object_classes)
|
44
|
+
self.taggable_object_classes = []
|
45
|
+
end
|
46
|
+
|
47
|
+
raise "options[:for] is missing" unless options[:for]
|
48
|
+
|
49
|
+
add_taggable_object_classes(options[:for])
|
50
|
+
end
|
51
|
+
|
52
|
+
module ClassMethods
|
53
|
+
# Return if a model is tagger
|
54
|
+
#
|
55
|
+
# @return [TrueClass]
|
56
|
+
# true
|
57
|
+
#
|
58
|
+
# @api public
|
59
|
+
def tagger?
|
60
|
+
true
|
61
|
+
end
|
62
|
+
|
63
|
+
# Register new taggables and set up relationships
|
64
|
+
#
|
65
|
+
# @param [Array] taggable_object_classes
|
66
|
+
# An array of taggable DataMapper models
|
67
|
+
#
|
68
|
+
# @api public
|
69
|
+
def add_taggable_object_classes(taggable_object_classes)
|
70
|
+
taggable_object_classes.each do |taggable_object_class|
|
71
|
+
self.taggable_object_classes << taggable_object_class
|
72
|
+
|
73
|
+
has n, taggable_object_class.tagging_relationship_name,
|
74
|
+
:constraint => :destroy
|
75
|
+
|
76
|
+
has n, taggable_object_class.taggable_relationship_name,
|
77
|
+
:through => taggable_object_class.tagging_relationship_name,
|
78
|
+
:constraint => :destroy
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end # ClassMethods
|
82
|
+
|
83
|
+
module InstanceMethods
|
84
|
+
# Tag a resource
|
85
|
+
#
|
86
|
+
# @param [DataMapper::Resource]
|
87
|
+
# An instance of a taggable resource
|
88
|
+
#
|
89
|
+
# @param [Hash] options (optional)
|
90
|
+
# A hash with options
|
91
|
+
#
|
92
|
+
# @return [DataMapper::Collection]
|
93
|
+
# A collection of tags that were associated with the resource
|
94
|
+
#
|
95
|
+
# @api public
|
96
|
+
def tag!(taggable, options={})
|
97
|
+
unless self.taggable_object_classes.include?(taggable.class)
|
98
|
+
raise "Object of type #{taggable.class} isn't taggable!"
|
99
|
+
end
|
100
|
+
|
101
|
+
tags = options[:with]
|
102
|
+
tags = [tags] unless tags.kind_of?(Array)
|
103
|
+
|
104
|
+
tags.each do |tag|
|
105
|
+
taggable.taggings.create(:tag => tag, :tagger => self)
|
106
|
+
end
|
107
|
+
|
108
|
+
tags
|
109
|
+
end
|
110
|
+
end # InstanceMethods
|
111
|
+
|
112
|
+
end # Tagger
|
113
|
+
end # Is
|
114
|
+
end # DataMapper
|
115
|
+
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Tagging
|
2
|
+
include DataMapper::Resource
|
3
|
+
|
4
|
+
property :id, Serial
|
5
|
+
property :tag_id, Integer, :min => 1, :required => true
|
6
|
+
|
7
|
+
is :remixable, :suffix => "tag"
|
8
|
+
|
9
|
+
def tagger=(tagger)
|
10
|
+
send("#{DataMapper::Inflector.underscore(tagger.class.name).to_sym}=", tagger)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
@@ -0,0 +1,259 @@
|
|
1
|
+
share_examples_for 'A taggable resource' do
|
2
|
+
def create_taggable(attrs={})
|
3
|
+
@taggable.create(@taggable_attributes.merge(attrs))
|
4
|
+
end
|
5
|
+
|
6
|
+
before :all do
|
7
|
+
%w[ @taggable ].each do |ivar|
|
8
|
+
raise "+#{ivar}+ should be defined in before block" unless instance_variable_defined?(ivar)
|
9
|
+
end
|
10
|
+
|
11
|
+
@taggable_attributes ||= {}
|
12
|
+
|
13
|
+
@foo_tag = Tag["foo"]
|
14
|
+
@bar_tag = Tag["bar"]
|
15
|
+
|
16
|
+
@tags = [@foo_tag, @bar_tag]
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "public class methods" do
|
20
|
+
subject { @taggable }
|
21
|
+
|
22
|
+
it { should respond_to(:is_taggable) }
|
23
|
+
it { should respond_to(:taggable?) }
|
24
|
+
it { should respond_to(:tagged_with) }
|
25
|
+
it { should respond_to(:tagging_relationship_name) }
|
26
|
+
it { should respond_to(:tagging_relationship) }
|
27
|
+
it { should respond_to(:tagging_class) }
|
28
|
+
it { should respond_to(:tagging_parent_name) }
|
29
|
+
it { should respond_to(:taggable_relationship_name) }
|
30
|
+
|
31
|
+
describe ".taggable?" do
|
32
|
+
it "should return true" do
|
33
|
+
@taggable.taggable?.should be(true)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "relationships" do
|
38
|
+
subject { @taggable.relationships }
|
39
|
+
|
40
|
+
it { should have_key(@taggable.tagging_relationship_name) }
|
41
|
+
|
42
|
+
describe "tagging constraint" do
|
43
|
+
subject { @taggable.tagging_relationship.constraint }
|
44
|
+
it { subject.should eql(:destroy!) }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe ".tagged_with" do
|
49
|
+
before :all do
|
50
|
+
@resource_one = create_taggable(:tag_list => "red, green, blue")
|
51
|
+
@resource_two = create_taggable(:tag_list => "orange, yellow")
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should return correct resources" do
|
55
|
+
result = @taggable.tagged_with(["red", "yellow", "purple"])
|
56
|
+
result.size.should eql(2)
|
57
|
+
result.should include(@resource_one, @resource_two)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe "public instance methods" do
|
63
|
+
subject { @taggable.new }
|
64
|
+
|
65
|
+
it { should respond_to(:tag) }
|
66
|
+
it { should respond_to(:tag!) }
|
67
|
+
it { should respond_to(:untag) }
|
68
|
+
it { should respond_to(:untag!) }
|
69
|
+
it { should respond_to(:tag_list) }
|
70
|
+
it { should respond_to(:taggings) }
|
71
|
+
|
72
|
+
describe ".tag" do
|
73
|
+
before :all do
|
74
|
+
@resource = create_taggable
|
75
|
+
@taggings = @resource.tag([@foo_tag, @bar_tag])
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should set new taggings" do
|
79
|
+
@taggings.should eql(@resource.taggings)
|
80
|
+
end
|
81
|
+
|
82
|
+
it "should not create new taggings" do
|
83
|
+
@resource.tags.should be_empty
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
describe ".tag!" do
|
88
|
+
before :all do
|
89
|
+
@resource = create_taggable
|
90
|
+
@taggings = @resource.tag!([@foo_tag, @bar_tag])
|
91
|
+
end
|
92
|
+
|
93
|
+
it "should create new taggings" do
|
94
|
+
@resource.reload.tags.should include(@foo_tag, @bar_tag)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
describe ".untag" do
|
99
|
+
describe "all" do
|
100
|
+
before :all do
|
101
|
+
@resource = create_taggable
|
102
|
+
@taggings = @resource.tag!([@foo_tag, @bar_tag])
|
103
|
+
|
104
|
+
@resource.untag
|
105
|
+
end
|
106
|
+
|
107
|
+
it "should remove the taggings from the collection" do
|
108
|
+
@resource.taggings.should be_empty
|
109
|
+
end
|
110
|
+
|
111
|
+
it "should not destroy the taggings" do
|
112
|
+
@resource.reload.tags.should_not be_empty
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
describe "specific names" do
|
117
|
+
before :all do
|
118
|
+
@resource = create_taggable
|
119
|
+
@taggings = @resource.tag!([@foo_tag, @bar_tag])
|
120
|
+
|
121
|
+
@resource.untag([@foo_tag])
|
122
|
+
end
|
123
|
+
|
124
|
+
it "should remove the related tagging from the collection" do
|
125
|
+
@resource.taggings.size.should eql(1)
|
126
|
+
end
|
127
|
+
|
128
|
+
it "should remove the related tag" do
|
129
|
+
@resource.tags.should_not include(@foo_tag)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
describe "when save is called" do
|
134
|
+
before :all do
|
135
|
+
@resource = create_taggable
|
136
|
+
@taggings = @resource.tag!([@foo_tag, @bar_tag])
|
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
|
145
|
+
end
|
146
|
+
|
147
|
+
it "should destroy taggings" do
|
148
|
+
pending "Currently DataMapper doesn't support saving an empty collection" do
|
149
|
+
@resource.reload.taggings.should be_empty
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
it "should destroy tags" do
|
154
|
+
pending "Currently DataMapper doesn't support saving an empty collection" do
|
155
|
+
@resource.reload.tags.should be_empty
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
describe ".untag!" do
|
162
|
+
describe "all" do
|
163
|
+
before :all do
|
164
|
+
@resource = create_taggable
|
165
|
+
@taggings = @resource.tag!([@foo_tag, @bar_tag])
|
166
|
+
|
167
|
+
@resource.untag!
|
168
|
+
end
|
169
|
+
|
170
|
+
it "should destroy the taggings" do
|
171
|
+
@resource.reload.taggings.should be_empty
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
describe "specific names" do
|
176
|
+
before :all do
|
177
|
+
@resource = create_taggable
|
178
|
+
@taggings = @resource.tag!([@foo_tag, @bar_tag])
|
179
|
+
|
180
|
+
@resource.untag!([@foo_tag])
|
181
|
+
@resource.reload
|
182
|
+
end
|
183
|
+
|
184
|
+
subject { @resource.tags }
|
185
|
+
|
186
|
+
it { should_not include(@foo_tag) }
|
187
|
+
it { should include(@bar_tag) }
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
describe ".tag_list=" do
|
192
|
+
describe "with a list of tag names" do
|
193
|
+
describe "with blank values" do
|
194
|
+
before :all do
|
195
|
+
@resource = create_taggable(:tag_list => "foo, , ,bar, , ")
|
196
|
+
end
|
197
|
+
|
198
|
+
it "should add new tags and reject blank names" do
|
199
|
+
@resource.reload.tags.should include(Tag["foo"], Tag["bar"])
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
describe "when tags are removed and added" do
|
204
|
+
before :all do
|
205
|
+
@resource = create_taggable(:tag_list => "foo, bar")
|
206
|
+
@resource.update(:tag_list => "foo, bar, pub")
|
207
|
+
end
|
208
|
+
|
209
|
+
it "should add new tags" do
|
210
|
+
@resource.reload.tags.should include(Tag["bar"], Tag["bar"], Tag["pub"])
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
describe "when tags are added" do
|
215
|
+
before :all do
|
216
|
+
@resource = create_taggable(:tag_list => "foo, bar")
|
217
|
+
@resource.update(:tag_list => "bar, pub")
|
218
|
+
end
|
219
|
+
|
220
|
+
it "should add new tags" do
|
221
|
+
@resource.reload.tags.should include(Tag["bar"], Tag["pub"])
|
222
|
+
end
|
223
|
+
|
224
|
+
it "should remove tags" do
|
225
|
+
@resource.reload.tags.should_not include(Tag["foo"])
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
describe "when no list of tag names is given" do
|
231
|
+
before :all do
|
232
|
+
@resource = create_taggable(:tag_list => "foo, bar")
|
233
|
+
@resource.update(:tag_list => "")
|
234
|
+
end
|
235
|
+
|
236
|
+
it "should destroy taggings" do
|
237
|
+
@resource.reload.taggings.should be_blank
|
238
|
+
end
|
239
|
+
|
240
|
+
it "should remove the tags" do
|
241
|
+
@resource.reload.tags.should be_blank
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
describe ".tag_list" do
|
247
|
+
before :all do
|
248
|
+
@tag_names = %w(red green blue)
|
249
|
+
@expected = @tag_names.join(', ')
|
250
|
+
@resource = create_taggable(:tag_list => @expected)
|
251
|
+
end
|
252
|
+
|
253
|
+
it "should return the list of tag names" do
|
254
|
+
@resource.tag_list.should eql(@expected)
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|