dm-taggings 0.11.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|