tagtical 1.2.0 → 1.3.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 +32 -11
- data/VERSION +1 -1
- data/lib/tagtical/tag.rb +37 -35
- data/lib/tagtical/taggable/core.rb +9 -29
- data/spec/tagtical/taggable_spec.rb +31 -1
- metadata +13 -13
data/README.rdoc
CHANGED
@@ -35,7 +35,8 @@ parsed.
|
|
35
35
|
# would give you a tag of "red, blue" instead of "red" and "blue".
|
36
36
|
tag_list.add("red, blue", :parse => false)
|
37
37
|
|
38
|
-
7. Custom contexts are *not* supported since we use STI.
|
38
|
+
7. Custom contexts are *not* supported since we use STI. That is, you must define a set of tag types
|
39
|
+
ahead of time.
|
39
40
|
|
40
41
|
Additions include:
|
41
42
|
1. Scopes are created on Tag so you can do photo.tags.color and grab all the tags of type Tag::Color, for example.
|
@@ -108,10 +109,12 @@ rake spec:plugins
|
|
108
109
|
end
|
109
110
|
end
|
110
111
|
|
112
|
+
# Basic TagList Functionality
|
111
113
|
@user = User.new(:name => "Bobby")
|
112
114
|
@user.tag_list = "awesome, slick, hefty" # this should be familiar
|
113
|
-
@user.activity_list = "joking, clowning, boxing"
|
114
|
-
@user.activity_list
|
115
|
+
@user.activity_list = "joking, clowning, boxing"
|
116
|
+
@user.activity_list # => ["joking","clowning","boxing"] as TagList
|
117
|
+
@user.activity_list.to_s # => "joking, clowning, boxing"
|
115
118
|
@user.save
|
116
119
|
|
117
120
|
# Cascade tag_list setters =)
|
@@ -122,7 +125,6 @@ rake spec:plugins
|
|
122
125
|
|
123
126
|
@user.tags # => [<Tag value:"awesome">,<Tag value:"slick">,<Tag value:"hefty">]
|
124
127
|
@user.activities # => [<Tag::Activity value:"joking">,<Tag::Activity value:"clowning">,<Tag::Activity value:"boxing">]
|
125
|
-
@user.activities.first.athletic? # => false
|
126
128
|
|
127
129
|
@frankie = User.create(:name => "Frankie", :activity_list => "joking, flying, eating")
|
128
130
|
User.activity_counts # => [<Tag::Activity value="joking" count=2>,<Tag::Activity value="clowning" count=1>...]
|
@@ -132,20 +134,39 @@ rake spec:plugins
|
|
132
134
|
# from Activity to Sport and give it a relevance of 4.5.
|
133
135
|
@user.save
|
134
136
|
|
137
|
+
# Possible Values Checking
|
135
138
|
@user.sport_list = {"chess"}
|
136
|
-
@user.save! # <=== will throw an error, chess is not
|
139
|
+
@user.save! # <=== will throw an error, chess is not in possible_values from Tag::Sport
|
137
140
|
|
141
|
+
# Tagging Scopes
|
138
142
|
@user.activities # => [<Tag::Activity value:"joking">,<Tag::Activity value:"clowning">,<Tag::Sport value:"boxing">]
|
139
143
|
@user.activities(:scope => :children) # => [<Tag::Sport value:"boxing">] - look at only the STI subclasses
|
140
|
-
@user.
|
141
|
-
@user.tags(:scope => :children, :
|
142
|
-
|
144
|
+
@user.activities(:children) # => shorthand for above
|
145
|
+
@user.tags(:scope => :children, :except => :sports) # => [<Tag::Activity value:"joking">,<Tag::Activity value:"clowning">]
|
146
|
+
# - filter list by excluding
|
147
|
+
@user.tags(:scope => :children, :only => :sports) # => [<Tag::Sport value:"boxing">]
|
148
|
+
# - filter list by including
|
149
|
+
|
150
|
+
@user.activities(:scope => ) # => [<Tag::Sport value:"boxing">] - look at only the STI subclasses
|
143
151
|
@user.activities(:scope => :current) # => [<Tag::Activity value:"joking">,<Tag::Activity value:"clowning">] - look at only the current STI class
|
144
|
-
@user.activities(:scope => :==)
|
152
|
+
@user.activities(:scope => :==) # => [<Tag::Activity value:"joking">,<Tag::Activity value:"clowning">] - look at only the current STI class
|
145
153
|
|
154
|
+
# Questioner Methods
|
146
155
|
@user.activities.first.athletic? # => false
|
147
156
|
@user.sports.all(&:ball?) # => true
|
148
157
|
|
158
|
+
# Database Access Optimizations
|
159
|
+
** Sequence 1
|
160
|
+
@user.tags.to_a # load in the tags
|
161
|
+
@user.sports.to_a # will not trigger a database hit, instead will get the sports tags off tags
|
162
|
+
@user.activities.to_a # same as above
|
163
|
+
|
164
|
+
** Sequence 2
|
165
|
+
@user.activities.to_a # load in the tags
|
166
|
+
@user.sports.to_a # will not trigger a database hit, instead will get the sports tags off activities, since it inherits from activities
|
167
|
+
@user.sports(:conditions => "value='Soccer'") # will actually hit the database if arguments are passed in.
|
168
|
+
|
169
|
+
|
149
170
|
|
150
171
|
--- Defining Subclasses
|
151
172
|
|
@@ -196,8 +217,8 @@ compatibility with the will_paginate gem:
|
|
196
217
|
named_scope :by_join_date, :order => "created_at DESC"
|
197
218
|
end
|
198
219
|
|
199
|
-
User.tagged_with("awesome").
|
200
|
-
User.tagged_with("awesome").
|
220
|
+
User.tagged_with("awesome").by_join_date
|
221
|
+
User.tagged_with("awesome").by_join_date.paginate(:page => params[:page], :per_page => 20)
|
201
222
|
|
202
223
|
# Find a user with matching all tags, not just one
|
203
224
|
User.tagged_with(["awesome", "cool"], :match_all => :true)
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.
|
1
|
+
1.3.0
|
data/lib/tagtical/tag.rb
CHANGED
@@ -232,17 +232,17 @@ module Tagtical
|
|
232
232
|
# <tt>only</tt> - An array of the following: :parents, :current, :children. Will construct conditions to query the current, parent, and/or children STI classes.
|
233
233
|
#
|
234
234
|
def finder_type_condition(*args)
|
235
|
-
|
235
|
+
sql = args[-1].is_a?(Hash) && args[-1].delete(:sql)
|
236
236
|
|
237
237
|
sti_column = Tagtical::Tag.arel_table[Tagtical::Tag.inheritance_column]
|
238
|
-
condition = expand_tag_types(
|
238
|
+
condition = expand_tag_types(*args).map { |x| x.klass.sti_name }.inject(nil) do |conds, sti_name|
|
239
239
|
cond = sti_column.eq(sti_name)
|
240
240
|
conds.nil? ? cond : conds.or(cond)
|
241
241
|
end
|
242
242
|
|
243
|
-
if condition &&
|
243
|
+
if condition && sql
|
244
244
|
condition = condition.to_sql
|
245
|
-
condition.insert(0, " AND ") if
|
245
|
+
condition.insert(0, " AND ") if sql==:append
|
246
246
|
end
|
247
247
|
condition
|
248
248
|
end
|
@@ -301,6 +301,38 @@ module Tagtical
|
|
301
301
|
end
|
302
302
|
end
|
303
303
|
|
304
|
+
def expand_tag_types(*args)
|
305
|
+
scopes, options = convert_finder_type_arguments(*args)
|
306
|
+
classes, types = [], []
|
307
|
+
|
308
|
+
types.concat(Array(options[:types]).map { |t| taggable_class.find_tag_type!(t) }) if options[:types]
|
309
|
+
|
310
|
+
if scopes.include?(:current)
|
311
|
+
classes << klass
|
312
|
+
end
|
313
|
+
if scopes.include?(:children)
|
314
|
+
classes.concat(klass.descendants)
|
315
|
+
end
|
316
|
+
if scopes.include?(:parents) # include searches up the STI chain
|
317
|
+
parent_class = klass.superclass
|
318
|
+
while parent_class <= Tagtical::Tag
|
319
|
+
classes << parent_class
|
320
|
+
parent_class = parent_class.superclass
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
if options[:only]
|
325
|
+
classes &= find_tag_classes_for(options[:only])
|
326
|
+
elsif options[:except]
|
327
|
+
except = find_tag_classes_for(options[:except])
|
328
|
+
classes.reject! { |t| except.any? { |e| t <= e }}
|
329
|
+
end
|
330
|
+
tag_types_by_classes = taggable_class.tag_types.index_by(&:klass)
|
331
|
+
types.concat(classes.map { |k| tag_types_by_classes[k] }.uniq.compact)
|
332
|
+
|
333
|
+
types # for clarity
|
334
|
+
end
|
335
|
+
|
304
336
|
private
|
305
337
|
|
306
338
|
# Returns an array of potential class names for this specific type.
|
@@ -358,37 +390,7 @@ module Tagtical
|
|
358
390
|
[scopes, options]
|
359
391
|
end
|
360
392
|
|
361
|
-
def
|
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)
|
393
|
+
def find_tag_classes_for(input)
|
392
394
|
Array(input).map { |o| taggable_class.find_tag_type!(o).klass }
|
393
395
|
end
|
394
396
|
|
@@ -72,7 +72,14 @@ module Tagtical::Taggable
|
|
72
72
|
alias_method_chain :tags, :finder_type_options
|
73
73
|
else
|
74
74
|
define_method(tag_type.has_many_name) do |*args|
|
75
|
-
tags.scoped.merge(tag_type.scoping(*args))
|
75
|
+
tags.scoped.merge(tag_type.scoping(*args)).tap do |scope|
|
76
|
+
if args.empty? &&
|
77
|
+
(loaded_parent_scope = tag_type.expand_tag_types(:parents).map { |t| send(t.has_many_name) }.detect(&:loaded?))
|
78
|
+
|
79
|
+
scope.instance_variable_set(:@loaded, true)
|
80
|
+
scope.instance_variable_set(:@records, loaded_parent_scope.select { |t| t.class <= tag_type.klass })
|
81
|
+
end
|
82
|
+
end
|
76
83
|
end
|
77
84
|
end
|
78
85
|
end
|
@@ -321,15 +328,8 @@ module Tagtical::Taggable
|
|
321
328
|
self.class.find_tag_type!(input)
|
322
329
|
end
|
323
330
|
|
324
|
-
def finder_type_arguments_for_tag_list(input, *args)
|
325
|
-
find_tag_type!(input).send(:convert_finder_type_arguments, *args)
|
326
|
-
end
|
327
|
-
|
328
331
|
def expand_tag_types(input, *args)
|
329
|
-
(@expand_tag_types ||= {})[[input, args]] ||=
|
330
|
-
scopes, options = finder_type_arguments_for_tag_list(input, *args)
|
331
|
-
find_tag_type!(input).send(:expand_tag_types, scopes, options)
|
332
|
-
end
|
332
|
+
(@expand_tag_types ||= {})[[input, args]] ||= find_tag_type!(input).expand_tag_types(*args)
|
333
333
|
end
|
334
334
|
|
335
335
|
# Lets say tag class A inherits from B and B has a tag with value "foo". If we tag A with value "foo",
|
@@ -341,26 +341,6 @@ module Tagtical::Taggable
|
|
341
341
|
end
|
342
342
|
end
|
343
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
|
-
|
364
344
|
# If cascade tag types are specified, it will attempt to look at Tag subclasses with
|
365
345
|
# possible_values and try to set those tag_lists with values from the possible_values list.
|
366
346
|
def cascade_set_tag_list!(tag_list, context, *args)
|
@@ -53,6 +53,36 @@ describe Tagtical::Taggable do
|
|
53
53
|
@taggable.should have(2).skills
|
54
54
|
end
|
55
55
|
|
56
|
+
describe "Tag Type Scopes" do
|
57
|
+
before do
|
58
|
+
@taggable.update_attributes!(:tag_list => "tree, train", :skill_list => "basketball", :craft_list => "pottery")
|
59
|
+
@taggable.reload
|
60
|
+
end
|
61
|
+
|
62
|
+
describe "inherited tags scope optimizations" do
|
63
|
+
before do
|
64
|
+
@taggable.tags.to_a # load the target
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should not access the database when top level tags are already loaded" do
|
68
|
+
ActiveRecord::Base.connection.expects(:execute).never
|
69
|
+
@taggable.skills.to_a
|
70
|
+
@taggable.crafts.to_a
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should select the correct tags" do
|
74
|
+
@taggable.skills.each { |tag| tag.should be_skill }
|
75
|
+
@taggable.crafts.each { |tag| tag.should be_craft }
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should access the database when args are passed in" do
|
79
|
+
ActiveRecord::Base.connection.expects(:execute).once.returns([])
|
80
|
+
@taggable.skills(:conditions => "value='Foo'").to_a
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
56
86
|
when_possible_values_specified(:values => %w{Knitting Ruby Pottery}) do
|
57
87
|
|
58
88
|
before do
|
@@ -67,7 +97,7 @@ describe Tagtical::Taggable do
|
|
67
97
|
@taggable.craft_list.add("ruby", "pottery")
|
68
98
|
@taggable.save!
|
69
99
|
@taggable.reload
|
70
|
-
should have_only_tag_values %w{Knitting Ruby Pottery}
|
100
|
+
@taggable.should have_only_tag_values %w{Knitting Ruby Pottery}
|
71
101
|
end
|
72
102
|
|
73
103
|
end
|
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.3.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -13,7 +13,7 @@ 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: &2152023000 !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: *2152023000
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: rspec
|
27
|
-
requirement: &
|
27
|
+
requirement: &2152022520 !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: *2152022520
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: sqlite3-ruby
|
38
|
-
requirement: &
|
38
|
+
requirement: &2152022040 !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: *2152022040
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: mysql
|
49
|
-
requirement: &
|
49
|
+
requirement: &2152021560 !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: *2152021560
|
58
58
|
- !ruby/object:Gem::Dependency
|
59
59
|
name: jeweler
|
60
|
-
requirement: &
|
60
|
+
requirement: &2152021080 !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: *2152021080
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: rcov
|
71
|
-
requirement: &
|
71
|
+
requirement: &2152020600 !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: *2152020600
|
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.
|