tagtical 1.1.3 → 1.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/README.rdoc +3 -0
- data/VERSION +1 -1
- data/lib/tagtical/tag.rb +41 -24
- data/lib/tagtical/tag_list.rb +1 -1
- data/lib/tagtical/taggable/cache.rb +1 -3
- data/lib/tagtical/taggable/core.rb +48 -24
- data/spec/tagtical/tag_list_spec.rb +1 -1
- data/spec/tagtical/taggable_spec.rb +35 -5
- metadata +14 -14
data/README.rdoc
CHANGED
@@ -115,6 +115,7 @@ rake spec:plugins
|
|
115
115
|
@user.save
|
116
116
|
|
117
117
|
# Cascade tag_list setters =)
|
118
|
+
# It will look at the "possible_values" if provided, and stuff the tags down at that level.
|
118
119
|
@user.set_activity_list(["clowning", "boxing"], :cascade => true)
|
119
120
|
@user.save!
|
120
121
|
@user.sport_list # => ["boxing"]
|
@@ -136,6 +137,8 @@ rake spec:plugins
|
|
136
137
|
|
137
138
|
@user.activities # => [<Tag::Activity value:"joking">,<Tag::Activity value:"clowning">,<Tag::Sport value:"boxing">]
|
138
139
|
@user.activities(:scope => :children) # => [<Tag::Sport value:"boxing">] - look at only the STI subclasses
|
140
|
+
@user.tags(:scope => :children, :except => :sports) # => [<Tag::Activity value:"joking">,<Tag::Activity value:"clowning">] - filter list
|
141
|
+
@user.tags(:scope => :children, :only => :sports) # => [<Tag::Sport value:"boxing">] - filter list
|
139
142
|
@user.activities(:scope => :<) # => [<Tag::Sport value:"boxing">] - look at only the STI subclasses
|
140
143
|
@user.activities(:scope => :current) # => [<Tag::Activity value:"joking">,<Tag::Activity value:"clowning">] - look at only the current STI class
|
141
144
|
@user.activities(:scope => :==) # => [<Tag::Activity value:"joking">,<Tag::Activity value:"clowning">] - look at only the current STI class
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.
|
1
|
+
1.2.0
|
data/lib/tagtical/tag.rb
CHANGED
@@ -105,10 +105,15 @@ module Tagtical
|
|
105
105
|
super || (object.is_a?(self.class) && value == object.value)
|
106
106
|
end
|
107
107
|
|
108
|
+
# Relevance is transferred through "taggings" join.
|
108
109
|
def relevance
|
109
110
|
(v = self[:relevance]) && v.to_f
|
110
111
|
end
|
111
112
|
|
113
|
+
def relevance=(relevance)
|
114
|
+
self[:relevance] = relevance
|
115
|
+
end
|
116
|
+
|
112
117
|
# Try to sort by the relevance if provided.
|
113
118
|
def <=>(tag)
|
114
119
|
if (r1 = relevance) && (r2 = tag.relevance)
|
@@ -229,31 +234,8 @@ module Tagtical
|
|
229
234
|
def finder_type_condition(*args)
|
230
235
|
scopes, options = convert_finder_type_arguments(*args)
|
231
236
|
|
232
|
-
# If we want [:current, :children] or [:current, :children, :parents] and we don't need the finder type condition,
|
233
|
-
# then that means we don't need a condition at all since we are at the top-level sti class and we are essentially
|
234
|
-
# searching the whole range of sti classes.
|
235
|
-
if klass && !klass.finder_needs_type_condition?
|
236
|
-
scopes.delete(:parents) # we are at the topmost level.
|
237
|
-
scopes = [] if scopes.sort==[:current, :children].sort # no condition is required if we want the current AND the children.
|
238
|
-
end
|
239
|
-
|
240
|
-
sti_names = []
|
241
|
-
if scopes.include?(:current)
|
242
|
-
sti_names << klass.sti_name
|
243
|
-
end
|
244
|
-
if scopes.include?(:children) && klass
|
245
|
-
sti_names.concat(klass.descendants.map(&:sti_name))
|
246
|
-
end
|
247
|
-
if scopes.include?(:parents) && klass # include searches up the STI chain
|
248
|
-
parent_class = klass.superclass
|
249
|
-
while parent_class <= Tagtical::Tag
|
250
|
-
sti_names << parent_class.sti_name
|
251
|
-
parent_class = parent_class.superclass
|
252
|
-
end
|
253
|
-
end
|
254
|
-
|
255
237
|
sti_column = Tagtical::Tag.arel_table[Tagtical::Tag.inheritance_column]
|
256
|
-
condition =
|
238
|
+
condition = expand_tag_types(scopes, options).map { |x| x.klass.sti_name }.inject(nil) do |conds, sti_name|
|
257
239
|
cond = sti_column.eq(sti_name)
|
258
240
|
conds.nil? ? cond : conds.or(cond)
|
259
241
|
end
|
@@ -372,9 +354,44 @@ module Tagtical
|
|
372
354
|
def convert_finder_type_arguments(*args)
|
373
355
|
options = args.extract_options!
|
374
356
|
scopes = convert_scope_options(args.presence || options[:scope])
|
357
|
+
scopes.delete(:parents) if klass && !klass.finder_needs_type_condition? # we are at the topmost level.
|
375
358
|
[scopes, options]
|
376
359
|
end
|
377
360
|
|
361
|
+
def expand_tag_types(scopes, options)
|
362
|
+
classes, types = [], []
|
363
|
+
types.concat(Array(options[:types]).map { |t| taggable_class.find_tag_type!(t) }) if options[:types]
|
364
|
+
|
365
|
+
if scopes.include?(:current)
|
366
|
+
classes << klass
|
367
|
+
end
|
368
|
+
if scopes.include?(:children)
|
369
|
+
classes.concat(klass.descendants)
|
370
|
+
end
|
371
|
+
if scopes.include?(:parents) # include searches up the STI chain
|
372
|
+
parent_class = klass.superclass
|
373
|
+
while parent_class <= Tagtical::Tag
|
374
|
+
classes << parent_class
|
375
|
+
parent_class = parent_class.superclass
|
376
|
+
end
|
377
|
+
end
|
378
|
+
|
379
|
+
if options[:only]
|
380
|
+
classes &= find_tag_types(options[:only])
|
381
|
+
elsif options[:except]
|
382
|
+
except = find_tag_types(options[:except])
|
383
|
+
classes.reject! { |t| except.any? { |e| t <= e }}
|
384
|
+
end
|
385
|
+
tag_types_by_classes = taggable_class.tag_types.index_by(&:klass)
|
386
|
+
types.concat(classes.map { |k| tag_types_by_classes[k] }.uniq.compact)
|
387
|
+
|
388
|
+
types # for clarity
|
389
|
+
end
|
390
|
+
|
391
|
+
def find_tag_types(input)
|
392
|
+
Array(input).map { |o| taggable_class.find_tag_type!(o).klass }
|
393
|
+
end
|
394
|
+
|
378
395
|
end
|
379
396
|
end
|
380
397
|
end
|
data/lib/tagtical/tag_list.rb
CHANGED
@@ -39,9 +39,7 @@ module Tagtical::Taggable
|
|
39
39
|
def save_cached_tag_list
|
40
40
|
tag_types.each do |tag_type|
|
41
41
|
if self.class.send("caching_#{tag_type.singularize}_list?")
|
42
|
-
|
43
|
-
self[tag_type.tag_list_name(:cached)] = tag_list.to_s
|
44
|
-
end
|
42
|
+
self[tag_type.tag_list_name(:cached)] = tag_list_on(tag_type).to_s if tag_list_on?(tag_type)
|
45
43
|
end
|
46
44
|
end
|
47
45
|
|
@@ -82,7 +82,7 @@ module Tagtical::Taggable
|
|
82
82
|
object.column_names.map { |column| "#{object.table_name}.#{column}" }.join(", ")
|
83
83
|
end
|
84
84
|
|
85
|
-
def find_tag_type!(input)
|
85
|
+
def find_tag_type!(input, options={})
|
86
86
|
(@tag_type ||= {})[input] ||= tag_types.find { |t| t.match?(input) } || raise("Cannot find tag type:'#{input}' in #{tag_types.inspect}")
|
87
87
|
end
|
88
88
|
|
@@ -205,12 +205,16 @@ module Tagtical::Taggable
|
|
205
205
|
# model.set_tag_list_on("skill", ["kung fu", "karate"], :scope => :==) # will not overwrite tags from inheriting tag classes
|
206
206
|
def set_tag_list_on(context, new_list, *args)
|
207
207
|
tag_list = Tagtical::TagList.from(new_list)
|
208
|
-
cascade_set_tag_list!(tag_list, context, *args)
|
209
|
-
tag_list_cache_on(context)[
|
208
|
+
cascade_set_tag_list!(tag_list, context, *args) if args[-1].is_a?(Hash) && args[-1].delete(:cascade)
|
209
|
+
tag_list_cache_on(context)[expand_tag_types(context, *args)] = tag_list
|
210
|
+
end
|
211
|
+
|
212
|
+
def tag_list_on?(context, *args)
|
213
|
+
!tag_list_cache_on(context)[expand_tag_types(context, *args)].nil?
|
210
214
|
end
|
211
215
|
|
212
216
|
def tag_list_on(context, *args)
|
213
|
-
tag_list_cache_on(context)[
|
217
|
+
tag_list_cache_on(context)[expand_tag_types(context, *args)] ||= Tagtical::TagList.new(tags_on(context, *args))
|
214
218
|
end
|
215
219
|
|
216
220
|
def tag_list_cache_on(context, prefix=nil)
|
@@ -219,7 +223,7 @@ module Tagtical::Taggable
|
|
219
223
|
end
|
220
224
|
|
221
225
|
def all_tags_list_on(context, *args)
|
222
|
-
tag_list_cache_on(context, :all)[
|
226
|
+
tag_list_cache_on(context, :all)[expand_tag_types(context, *args)] ||= Tagtical::TagList.new(all_tags_on(context, *args)).freeze
|
223
227
|
end
|
224
228
|
|
225
229
|
##
|
@@ -254,19 +258,21 @@ module Tagtical::Taggable
|
|
254
258
|
# Do the classes from top to bottom. We want the list from "tag" to run before "sub_tag" runs.
|
255
259
|
# Otherwise, we will end up removing taggings from "sub_tag" since they aren't on "tag'.
|
256
260
|
tag_types.sort_by(&:active_record_sti_level).each do |tag_type|
|
257
|
-
(tag_list_cache_on(tag_type) || {}).each do |
|
261
|
+
(tag_list_cache_on(tag_type) || {}).each do |expanded_tag_types, tag_list|
|
258
262
|
# Tag list saving only runs if its affecting the current scope or the current and children scope
|
259
|
-
next unless [:<=, :==].any? { |scope| scopes_for_tag_list(tag_type, scope)==scopes }
|
263
|
+
# next unless [:<=, :==].any? { |scope| scopes_for_tag_list(tag_type, scope)==scopes }
|
264
|
+
next unless expanded_tag_types.include?(tag_type)
|
260
265
|
tag_list = tag_list.uniq
|
261
266
|
|
262
267
|
# Find existing tags or create non-existing tags:
|
263
268
|
tag_value_lookup = tag_type.scoping { find_or_create_tags(tag_list) }
|
264
269
|
tags = tag_value_lookup.keys
|
265
270
|
|
266
|
-
|
271
|
+
|
272
|
+
current_tags = tags_on(tag_type, :types => expanded_tag_types, :scope => :parents) # add in the parents because we need them later on down.
|
267
273
|
old_tags = current_tags - tags
|
268
274
|
new_tags = tags - current_tags
|
269
|
-
|
275
|
+
|
270
276
|
unowned_taggings = taggings.where(:tagger_id => nil)
|
271
277
|
|
272
278
|
# If relevances are specified on current tags, make sure to update those
|
@@ -319,8 +325,11 @@ module Tagtical::Taggable
|
|
319
325
|
find_tag_type!(input).send(:convert_finder_type_arguments, *args)
|
320
326
|
end
|
321
327
|
|
322
|
-
def
|
323
|
-
|
328
|
+
def expand_tag_types(input, *args)
|
329
|
+
(@expand_tag_types ||= {})[[input, args]] ||= begin
|
330
|
+
scopes, options = finder_type_arguments_for_tag_list(input, *args)
|
331
|
+
find_tag_type!(input).send(:expand_tag_types, scopes, options)
|
332
|
+
end
|
324
333
|
end
|
325
334
|
|
326
335
|
# Lets say tag class A inherits from B and B has a tag with value "foo". If we tag A with value "foo",
|
@@ -332,24 +341,39 @@ module Tagtical::Taggable
|
|
332
341
|
end
|
333
342
|
end
|
334
343
|
|
344
|
+
# Extracts the valid tag types for the cascade option.
|
345
|
+
def extract_tag_types_from_cascade(input, base_tag_type)
|
346
|
+
case input
|
347
|
+
when Hash
|
348
|
+
if except = input[:except]
|
349
|
+
except = extract_tag_types_from_cascade(except, base_tag_type)
|
350
|
+
tag_types.reject { |t| except.any? { |e| t.klass <= e.klass }} # remove children as well.
|
351
|
+
elsif only = input[:only]
|
352
|
+
extract_tag_types_from_cascade(only, base_tag_type)
|
353
|
+
else raise("Please provide :except or :only")
|
354
|
+
end
|
355
|
+
when true
|
356
|
+
tag_types
|
357
|
+
else
|
358
|
+
Array(input).map { |c| find_tag_type!(c) }
|
359
|
+
end.select do |tag_type|
|
360
|
+
tag_type.klass <= base_tag_type.klass && tag_type.klass.possible_values
|
361
|
+
end
|
362
|
+
end
|
363
|
+
|
335
364
|
# If cascade tag types are specified, it will attempt to look at Tag subclasses with
|
336
365
|
# possible_values and try to set those tag_lists with values from the possible_values list.
|
337
366
|
def cascade_set_tag_list!(tag_list, context, *args)
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
tag_list.reject! do |tag_value|
|
346
|
-
if value = t.klass.detect_possible_value(tag_value)
|
347
|
-
new_tag_list << Tagtical::TagList::TagValue.new(value, tag_value.relevance)
|
348
|
-
true
|
349
|
-
end
|
367
|
+
expand_tag_types(context, *args).each do |tag_type|
|
368
|
+
if tag_type.klass.possible_values
|
369
|
+
new_tag_list = Tagtical::TagList.new
|
370
|
+
tag_list.reject! do |tag_value|
|
371
|
+
if value = tag_type.klass.detect_possible_value(tag_value)
|
372
|
+
new_tag_list << Tagtical::TagList::TagValue.new(value, tag_value.relevance)
|
373
|
+
true
|
350
374
|
end
|
351
|
-
tag_list_cache_on(t)[[:current]] = new_tag_list if !new_tag_list.empty?
|
352
375
|
end
|
376
|
+
set_tag_list_on(tag_type, new_tag_list, :current) if !new_tag_list.empty?
|
353
377
|
end
|
354
378
|
end
|
355
379
|
end
|
@@ -85,7 +85,7 @@ describe Tagtical::TagList do
|
|
85
85
|
|
86
86
|
it "should be able to add a list of tags" do
|
87
87
|
tags = [["foo", 1],["bar", 1.3],["car", 0.7]].map do |value, relevance|
|
88
|
-
Tagtical::Tag.new(:value => value
|
88
|
+
Tagtical::Tag.new(:value => value, :relevance => relevance)
|
89
89
|
end
|
90
90
|
@tag_list.add(tags)
|
91
91
|
tags.each do |tag|
|
@@ -24,7 +24,7 @@ describe Tagtical::Taggable do
|
|
24
24
|
|
25
25
|
it "should be able to create tags" do
|
26
26
|
@taggable.skill_list = "ruby, rails, css"
|
27
|
-
@taggable.
|
27
|
+
@taggable.tag_list_on(:skills).should be_an_instance_of(Tagtical::TagList)
|
28
28
|
|
29
29
|
lambda { @taggable.save }.should change(Tagtical::Tag, :count).by(3)
|
30
30
|
|
@@ -90,7 +90,7 @@ describe Tagtical::Taggable do
|
|
90
90
|
@taggable.tag_list.should have_same_elements %w{Ruby plain}
|
91
91
|
end
|
92
92
|
|
93
|
-
it "should set value on skill" do
|
93
|
+
it "should set value on skill even if different case" do
|
94
94
|
@taggable.skills.should have_only_tag_values %w{Ruby}
|
95
95
|
end
|
96
96
|
|
@@ -103,10 +103,9 @@ describe Tagtical::Taggable do
|
|
103
103
|
end
|
104
104
|
|
105
105
|
end
|
106
|
-
|
107
|
-
context "when :cascade => :craft" do
|
106
|
+
context "when :cascade only on :craft" do
|
108
107
|
before do
|
109
|
-
@taggable.set_tag_list(["ruby", "plain"], :cascade => :craft)
|
108
|
+
@taggable.set_tag_list(["ruby", "plain"], :cascade => true, :types => :craft)
|
110
109
|
@taggable.save!
|
111
110
|
@taggable.reload
|
112
111
|
end
|
@@ -124,6 +123,37 @@ describe Tagtical::Taggable do
|
|
124
123
|
end
|
125
124
|
|
126
125
|
end
|
126
|
+
context "Adding tags with Exclusion" do
|
127
|
+
|
128
|
+
before do
|
129
|
+
@taggable.set_tag_list "Ruby, plain", :cascade => true, :except => :skill
|
130
|
+
@taggable.save!
|
131
|
+
@taggable.reload
|
132
|
+
end
|
133
|
+
|
134
|
+
it "should not change the tags in skill" do
|
135
|
+
@taggable.skill_list.should have_same_elements ["basketball", "pottery"]
|
136
|
+
end
|
137
|
+
|
138
|
+
it "should have set the values at the tag level" do
|
139
|
+
@taggable.tag_list(:current).should have_same_elements ["Ruby", "plain"]
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
143
|
+
context "Getting tag_list with :except" do
|
144
|
+
|
145
|
+
before do
|
146
|
+
@taggable.set_tag_list "Ruby, plain", :cascade => true
|
147
|
+
@taggable.save!
|
148
|
+
@taggable.reload
|
149
|
+
end
|
150
|
+
|
151
|
+
it "should exclude all defined types" do
|
152
|
+
@taggable.tag_list(:except => :skill).should have(1).item
|
153
|
+
end
|
154
|
+
|
155
|
+
end
|
156
|
+
|
127
157
|
end
|
128
158
|
end
|
129
159
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tagtical
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2011-07-
|
12
|
+
date: 2011-07-23 00:00:00.000000000Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rails
|
16
|
-
requirement: &
|
16
|
+
requirement: &2156407420 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - <=
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: 3.0.5
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *2156407420
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: rspec
|
27
|
-
requirement: &
|
27
|
+
requirement: &2156406940 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - <=
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: 2.6.0
|
33
33
|
type: :runtime
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *2156406940
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: sqlite3-ruby
|
38
|
-
requirement: &
|
38
|
+
requirement: &2156406460 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ! '>='
|
@@ -43,10 +43,10 @@ dependencies:
|
|
43
43
|
version: '0'
|
44
44
|
type: :runtime
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *2156406460
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: mysql
|
49
|
-
requirement: &
|
49
|
+
requirement: &2156405980 !ruby/object:Gem::Requirement
|
50
50
|
none: false
|
51
51
|
requirements:
|
52
52
|
- - ! '>='
|
@@ -54,10 +54,10 @@ dependencies:
|
|
54
54
|
version: '0'
|
55
55
|
type: :runtime
|
56
56
|
prerelease: false
|
57
|
-
version_requirements: *
|
57
|
+
version_requirements: *2156405980
|
58
58
|
- !ruby/object:Gem::Dependency
|
59
59
|
name: jeweler
|
60
|
-
requirement: &
|
60
|
+
requirement: &2156405500 !ruby/object:Gem::Requirement
|
61
61
|
none: false
|
62
62
|
requirements:
|
63
63
|
- - ! '>='
|
@@ -65,10 +65,10 @@ dependencies:
|
|
65
65
|
version: '0'
|
66
66
|
type: :runtime
|
67
67
|
prerelease: false
|
68
|
-
version_requirements: *
|
68
|
+
version_requirements: *2156405500
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: rcov
|
71
|
-
requirement: &
|
71
|
+
requirement: &2156405020 !ruby/object:Gem::Requirement
|
72
72
|
none: false
|
73
73
|
requirements:
|
74
74
|
- - ! '>='
|
@@ -76,7 +76,7 @@ dependencies:
|
|
76
76
|
version: '0'
|
77
77
|
type: :runtime
|
78
78
|
prerelease: false
|
79
|
-
version_requirements: *
|
79
|
+
version_requirements: *2156405020
|
80
80
|
description: Tagtical allows you do create subclasses for Tag and add additional functionality
|
81
81
|
in an STI fashion. For example. You could do Tag::Color.find_by_name('blue').to_rgb.
|
82
82
|
It also supports storing weights or relevance on the taggings.
|