tagtical 1.2.0 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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.
|