rocket_tag 0.3.1 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +2 -2
- data/Gemfile.lock +13 -13
- data/README.md +60 -49
- data/VERSION +1 -1
- data/lib/rocket_tag/taggable.rb +54 -12
- data/rocket_tag.gemspec +8 -8
- data/spec/rocket_tag/taggable_spec.rb +20 -2
- metadata +6 -6
data/Gemfile
CHANGED
@@ -3,14 +3,14 @@ source "http://rubygems.org"
|
|
3
3
|
# Example:
|
4
4
|
# gem "activesupport", ">= 2.3.5"
|
5
5
|
gem "activerecord", ">= 3.2.0"
|
6
|
-
gem "squeel", :require => false
|
6
|
+
gem "squeel", '~> 1.0.0', :require => false
|
7
7
|
|
8
8
|
# Add dependencies to develop your gem here.
|
9
9
|
# Include everything needed to run rake, tests, features, etc.
|
10
10
|
group :development do
|
11
11
|
gem "rspec", "~> 2.3.0"
|
12
12
|
gem "yard", "~> 0.6.0"
|
13
|
-
gem "bundler", "~> 1.
|
13
|
+
gem "bundler", "~> 1.1.0"
|
14
14
|
gem "jeweler", "~> 1.6.4"
|
15
15
|
#gem "rcov", ">= 0"
|
16
16
|
gem 'sqlite3'
|
data/Gemfile.lock
CHANGED
@@ -1,18 +1,18 @@
|
|
1
1
|
GEM
|
2
2
|
remote: http://rubygems.org/
|
3
3
|
specs:
|
4
|
-
activemodel (3.2.
|
5
|
-
activesupport (= 3.2.
|
4
|
+
activemodel (3.2.3)
|
5
|
+
activesupport (= 3.2.3)
|
6
6
|
builder (~> 3.0.0)
|
7
|
-
activerecord (3.2.
|
8
|
-
activemodel (= 3.2.
|
9
|
-
activesupport (= 3.2.
|
10
|
-
arel (~> 3.0.
|
7
|
+
activerecord (3.2.3)
|
8
|
+
activemodel (= 3.2.3)
|
9
|
+
activesupport (= 3.2.3)
|
10
|
+
arel (~> 3.0.2)
|
11
11
|
tzinfo (~> 0.3.29)
|
12
|
-
activesupport (3.2.
|
12
|
+
activesupport (3.2.3)
|
13
13
|
i18n (~> 0.6)
|
14
14
|
multi_json (~> 1.0)
|
15
|
-
arel (3.0.
|
15
|
+
arel (3.0.2)
|
16
16
|
builder (3.0.0)
|
17
17
|
diff-lcs (1.1.3)
|
18
18
|
git (1.2.5)
|
@@ -21,7 +21,7 @@ GEM
|
|
21
21
|
bundler (~> 1.0)
|
22
22
|
git (>= 1.2.5)
|
23
23
|
rake
|
24
|
-
multi_json (1.
|
24
|
+
multi_json (1.3.4)
|
25
25
|
polyamorous (0.5.0)
|
26
26
|
activerecord (~> 3.0)
|
27
27
|
rake (0.9.2.2)
|
@@ -34,11 +34,11 @@ GEM
|
|
34
34
|
diff-lcs (~> 1.1.2)
|
35
35
|
rspec-mocks (2.3.0)
|
36
36
|
sqlite3 (1.3.5)
|
37
|
-
squeel (0.
|
37
|
+
squeel (1.0.1)
|
38
38
|
activerecord (~> 3.0)
|
39
39
|
activesupport (~> 3.0)
|
40
40
|
polyamorous (~> 0.5.0)
|
41
|
-
tzinfo (0.3.
|
41
|
+
tzinfo (0.3.33)
|
42
42
|
yard (0.6.8)
|
43
43
|
|
44
44
|
PLATFORMS
|
@@ -46,10 +46,10 @@ PLATFORMS
|
|
46
46
|
|
47
47
|
DEPENDENCIES
|
48
48
|
activerecord (>= 3.2.0)
|
49
|
-
bundler (~> 1.
|
49
|
+
bundler (~> 1.1.0)
|
50
50
|
jeweler (~> 1.6.4)
|
51
51
|
rake
|
52
52
|
rspec (~> 2.3.0)
|
53
53
|
sqlite3
|
54
|
-
squeel
|
54
|
+
squeel (~> 1.0.0)
|
55
55
|
yard (~> 0.6.0)
|
data/README.md
CHANGED
@@ -33,60 +33,69 @@ Usage
|
|
33
33
|
item.habits = ["forking", "talking"]
|
34
34
|
|
35
35
|
|
36
|
-
|
37
|
-
TaggableModel.tagged_with ["forking", "kiting"]
|
36
|
+
Match any tag across any contexts
|
38
37
|
|
39
|
-
|
40
|
-
TaggableModel.tagged_with ["forking", "kiting"], :all => true
|
38
|
+
TaggableModel.tagged_with ["forking", "kiting"]
|
41
39
|
|
42
|
-
|
43
|
-
TaggableModel.tagged_with ["math", "kiting"], :on => "skills"
|
40
|
+
Match all tags across any contexts
|
44
41
|
|
45
|
-
|
46
|
-
|
42
|
+
TaggableModel.tagged_with ["forking", "kiting"], :all => true
|
43
|
+
|
44
|
+
Match any tag on a specific context
|
45
|
+
|
46
|
+
TaggableModel.tagged_with ["math", "kiting"], :on => "skills"
|
47
|
+
|
48
|
+
Match all tags on a specific context
|
49
|
+
|
50
|
+
TaggableModel.tagged_with ["math", "kiting"], :all => true, :on => "skills"
|
47
51
|
|
48
|
-
|
49
|
-
|
52
|
+
Match a miniumum number of tags
|
53
|
+
|
54
|
+
TaggableModel.tagged_with ["math", "kiting", "coding", "sleeping"], :min => 2, :on => "skills"
|
50
55
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
56
|
+
Take advantage of the tags_count synthetic column returned with every query
|
57
|
+
|
58
|
+
TaggableModel.tagged_with(["math", "kiting", "coding", "sleeping"], :on => "skills").where{tags_count>=2}
|
59
|
+
|
60
|
+
Mix with active relation
|
61
|
+
|
62
|
+
TaggableModel.tagged_with(["forking", "kiting"]).where( ["created_at > ?", Time.zone.now.ago(5.hours)])
|
63
|
+
|
64
|
+
Find similar models based on tags on a specific context and return in decending order
|
65
|
+
of 'tags_count'
|
66
|
+
|
67
|
+
model.tagged_similar :on => "skills"
|
68
|
+
model.tagged_similar :on => "habits"
|
69
|
+
|
70
|
+
Find similar models based on tags on every context and return in decending order
|
71
|
+
of 'tags_count'. Note that each tag is still scoped according to it's context
|
72
|
+
|
73
|
+
model.tagged_similar
|
74
|
+
|
75
|
+
For reference the SQL generated for model.tagged_similar when there are
|
76
|
+
context [:skills, :languages] available is
|
77
|
+
|
78
|
+
SELECT "taggable_models".* FROM
|
79
|
+
(
|
80
|
+
SELECT COUNT("taggable_models"."id") AS tags_count,
|
81
|
+
taggable_models.*
|
82
|
+
FROM "taggable_models"
|
83
|
+
INNER JOIN "taggings"
|
84
|
+
ON "taggings"."taggable_id" = "taggable_models"."id"
|
85
|
+
AND "taggings"."taggable_type" = 'TaggableModel'
|
86
|
+
INNER JOIN "tags"
|
87
|
+
ON "tags"."id" = "taggings"."tag_id"
|
88
|
+
WHERE "taggable_models"."id" != 2
|
89
|
+
AND (( ( "tags"."name" IN ( 'german', 'french' ) AND "taggings"."context" = 'languages' )
|
90
|
+
OR ( "tags"."name" IN ( 'a', 'b', 'x' ) AND "taggings"."context" = 'skills' )
|
91
|
+
))
|
92
|
+
GROUP BY "taggable_models"."id"
|
93
|
+
ORDER BY tags_count DESC
|
94
|
+
) taggable_models
|
95
|
+
|
96
|
+
|
97
|
+
Note the aliasing of the inner select to shield the GROUP BY from downstream active relation
|
98
|
+
queries
|
90
99
|
|
91
100
|
== Contributing to rocket_tag
|
92
101
|
|
@@ -103,3 +112,5 @@ Usage
|
|
103
112
|
Copyright (c) 2011 Brad Phelan. See LICENSE.txt for
|
104
113
|
further details.
|
105
114
|
|
115
|
+
Available for hire for your next ROR project at <a href="http://xtargets.com" title="XTargets: Ruby On Rails Solutions" rel="author">XTargets: Ruby On Rails Solutions</a>
|
116
|
+
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.4.0
|
data/lib/rocket_tag/taggable.rb
CHANGED
@@ -23,7 +23,7 @@ module RocketTag
|
|
23
23
|
# but must be
|
24
24
|
#
|
25
25
|
# hello,"foo"
|
26
|
-
|
26
|
+
return [] if list.empty?
|
27
27
|
list = list.gsub /,\s+"/, ',"'
|
28
28
|
list = list.parse_csv.map &:strip
|
29
29
|
else
|
@@ -52,13 +52,13 @@ module RocketTag
|
|
52
52
|
end
|
53
53
|
|
54
54
|
module InstanceMethods
|
55
|
-
def
|
56
|
-
super
|
55
|
+
def reload_with_tags(options = nil)
|
57
56
|
self.class.rocket_tag.contexts.each do |context|
|
58
57
|
write_context context, []
|
59
58
|
end
|
60
59
|
@tags_cached = false
|
61
60
|
cache_tags
|
61
|
+
reload_without_tags(options)
|
62
62
|
end
|
63
63
|
|
64
64
|
def cache_tags
|
@@ -114,7 +114,7 @@ module RocketTag
|
|
114
114
|
inner = self.class.select{count(~id).as(tags_count)}.
|
115
115
|
select("#{self.class.table_name}.*").
|
116
116
|
joins{tags}.where{condition}.
|
117
|
-
group{
|
117
|
+
group(self.class.column_names.map{|col| "#{self.class.table_name}.#{col}"}).
|
118
118
|
where{~id != my{id}}.
|
119
119
|
order("tags_count DESC")
|
120
120
|
|
@@ -153,12 +153,12 @@ module RocketTag
|
|
153
153
|
def tagged_with tags_list, options = {}
|
154
154
|
on = options.delete :on
|
155
155
|
|
156
|
-
inner = select{count(~id).as(tags_count)}
|
157
|
-
|
156
|
+
inner = select{count(~id).as(tags_count)}.
|
157
|
+
select("#{self.table_name}.*").
|
158
158
|
joins{tags}.
|
159
159
|
where{tags.name.in(my{tags_list})}.
|
160
160
|
_with_tag_context(on).
|
161
|
-
group{
|
161
|
+
group(self.column_names.map{|col| "#{self.table_name}.#{col}"})
|
162
162
|
|
163
163
|
# Wrap the inner query with an outer query to shield
|
164
164
|
# the group and aggregate clauses from downstream
|
@@ -183,6 +183,50 @@ module RocketTag
|
|
183
183
|
|
184
184
|
end
|
185
185
|
|
186
|
+
# Generates a query that returns list of popular tags
|
187
|
+
# for given model with an extra column :tags_count.
|
188
|
+
def popular_tags options={}
|
189
|
+
context = options.delete :on
|
190
|
+
if context
|
191
|
+
if context.class == Array
|
192
|
+
contexts = context
|
193
|
+
else
|
194
|
+
contexts = [context]
|
195
|
+
end
|
196
|
+
else
|
197
|
+
contexts = []
|
198
|
+
end
|
199
|
+
|
200
|
+
conditions = contexts.map do |context|
|
201
|
+
squeel do
|
202
|
+
(taggings.context == my{context})
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
condition = conditions.inject do |s, t|
|
207
|
+
s | t
|
208
|
+
end
|
209
|
+
inner = RocketTag::Tag.select{count(~id).as(tags_count)}.
|
210
|
+
select("#{RocketTag::Tag.table_name}.*").
|
211
|
+
joins{taggings.outer}.where{condition}.
|
212
|
+
where{ taggings.taggable_type == my{self.to_s} }.
|
213
|
+
group{~id} #.
|
214
|
+
#order("tags_count DESC")
|
215
|
+
r = from("(#{inner.to_sql}) #{table_name}")
|
216
|
+
|
217
|
+
if min = options.delete(:min)
|
218
|
+
r = r.where{tags_count>=my{min}}
|
219
|
+
end
|
220
|
+
|
221
|
+
if options.delete :sifter
|
222
|
+
squeel do
|
223
|
+
id.in(r.select{"id"})
|
224
|
+
end
|
225
|
+
else
|
226
|
+
r.select("*")
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
186
230
|
def setup_for_rocket_tag
|
187
231
|
unless @setup_for_rocket_tag
|
188
232
|
@setup_for_rocket_tag = true
|
@@ -227,11 +271,11 @@ module RocketTag
|
|
227
271
|
end
|
228
272
|
end
|
229
273
|
|
230
|
-
@@acts_as_rocket_tag = false
|
231
274
|
def attr_taggable *contexts
|
232
|
-
unless
|
275
|
+
unless class_variable_defined?(:@@acts_as_rocket_tag)
|
233
276
|
include RocketTag::Taggable::InstanceMethods
|
234
|
-
|
277
|
+
class_variable_set(:@@acts_as_rocket_tag, true)
|
278
|
+
alias_method_chain :reload, :tags
|
235
279
|
end
|
236
280
|
|
237
281
|
if contexts.blank?
|
@@ -284,8 +328,6 @@ module RocketTag
|
|
284
328
|
end
|
285
329
|
end
|
286
330
|
end
|
287
|
-
|
288
331
|
end
|
289
|
-
|
290
332
|
end
|
291
333
|
end
|
data/rocket_tag.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = "rocket_tag"
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.4.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Brad Phelan"]
|
12
|
-
s.date = "2012-
|
12
|
+
s.date = "2012-06-18"
|
13
13
|
s.description = ""
|
14
14
|
s.email = "bradphelan@xtargets.com"
|
15
15
|
s.extra_rdoc_files = [
|
@@ -50,29 +50,29 @@ Gem::Specification.new do |s|
|
|
50
50
|
|
51
51
|
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
52
52
|
s.add_runtime_dependency(%q<activerecord>, [">= 3.2.0"])
|
53
|
-
s.add_runtime_dependency(%q<squeel>, ["
|
53
|
+
s.add_runtime_dependency(%q<squeel>, ["~> 1.0.0"])
|
54
54
|
s.add_development_dependency(%q<rspec>, ["~> 2.3.0"])
|
55
55
|
s.add_development_dependency(%q<yard>, ["~> 0.6.0"])
|
56
|
-
s.add_development_dependency(%q<bundler>, ["~> 1.
|
56
|
+
s.add_development_dependency(%q<bundler>, ["~> 1.1.0"])
|
57
57
|
s.add_development_dependency(%q<jeweler>, ["~> 1.6.4"])
|
58
58
|
s.add_development_dependency(%q<sqlite3>, [">= 0"])
|
59
59
|
s.add_development_dependency(%q<rake>, [">= 0"])
|
60
60
|
else
|
61
61
|
s.add_dependency(%q<activerecord>, [">= 3.2.0"])
|
62
|
-
s.add_dependency(%q<squeel>, ["
|
62
|
+
s.add_dependency(%q<squeel>, ["~> 1.0.0"])
|
63
63
|
s.add_dependency(%q<rspec>, ["~> 2.3.0"])
|
64
64
|
s.add_dependency(%q<yard>, ["~> 0.6.0"])
|
65
|
-
s.add_dependency(%q<bundler>, ["~> 1.
|
65
|
+
s.add_dependency(%q<bundler>, ["~> 1.1.0"])
|
66
66
|
s.add_dependency(%q<jeweler>, ["~> 1.6.4"])
|
67
67
|
s.add_dependency(%q<sqlite3>, [">= 0"])
|
68
68
|
s.add_dependency(%q<rake>, [">= 0"])
|
69
69
|
end
|
70
70
|
else
|
71
71
|
s.add_dependency(%q<activerecord>, [">= 3.2.0"])
|
72
|
-
s.add_dependency(%q<squeel>, ["
|
72
|
+
s.add_dependency(%q<squeel>, ["~> 1.0.0"])
|
73
73
|
s.add_dependency(%q<rspec>, ["~> 2.3.0"])
|
74
74
|
s.add_dependency(%q<yard>, ["~> 0.6.0"])
|
75
|
-
s.add_dependency(%q<bundler>, ["~> 1.
|
75
|
+
s.add_dependency(%q<bundler>, ["~> 1.1.0"])
|
76
76
|
s.add_dependency(%q<jeweler>, ["~> 1.6.4"])
|
77
77
|
s.add_dependency(%q<sqlite3>, [">= 0"])
|
78
78
|
s.add_dependency(%q<rake>, [">= 0"])
|
@@ -14,6 +14,11 @@ describe TaggableModel do
|
|
14
14
|
m = TaggableModel.new :skills => %q%hello, "is it me, you are looking for", cat%
|
15
15
|
m.skills.should == ["hello", "is it me, you are looking for", "cat"]
|
16
16
|
end
|
17
|
+
|
18
|
+
it "parses an empty string to an empty array" do
|
19
|
+
m = TaggableModel.new :skills => ""
|
20
|
+
m.skills.should == []
|
21
|
+
end
|
17
22
|
end
|
18
23
|
|
19
24
|
describe "#save" do
|
@@ -317,8 +322,21 @@ describe TaggableModel do
|
|
317
322
|
end
|
318
323
|
|
319
324
|
end
|
320
|
-
|
325
|
+
|
326
|
+
describe "#popular_tags" do
|
327
|
+
it "should return correct list (and correctly ordered) of popular tags for class and context" do
|
328
|
+
TaggableModel.popular_tags.all.length.should == RocketTag::Tag.all.count
|
329
|
+
TaggableModel.popular_tags.limit(10).all.length.should == 10
|
330
|
+
TaggableModel.popular_tags.order('tags_count desc, name desc').first.name.should == 'c'
|
331
|
+
TaggableModel.popular_tags.order('id asc').first.name.should == 'a'
|
332
|
+
TaggableModel.popular_tags.order('id asc').last.name.should == 'jinglish'
|
333
|
+
TaggableModel.popular_tags(:on=>:skills).order('name asc').first.name.should == 'a'
|
334
|
+
TaggableModel.popular_tags(:on=>:skills).order('name asc').last.name.should == 'y'
|
335
|
+
TaggableModel.popular_tags(:on=>[:skills, :languages]).order('id asc').first.name.should == 'a'
|
336
|
+
TaggableModel.popular_tags(:on=>[:skills, :languages]).order('id asc').last.name.should == 'jinglish'
|
337
|
+
TaggableModel.popular_tags(:min=>2).all.length.should == 6 ## dirty!
|
338
|
+
end
|
339
|
+
end
|
321
340
|
end
|
322
341
|
end
|
323
342
|
end
|
324
|
-
|
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: rocket_tag
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 0.
|
5
|
+
version: 0.4.0
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Brad Phelan
|
@@ -10,7 +10,7 @@ autorequire:
|
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
|
13
|
-
date: 2012-
|
13
|
+
date: 2012-06-18 00:00:00 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: activerecord
|
@@ -28,9 +28,9 @@ dependencies:
|
|
28
28
|
requirement: &id002 !ruby/object:Gem::Requirement
|
29
29
|
none: false
|
30
30
|
requirements:
|
31
|
-
- -
|
31
|
+
- - ~>
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
33
|
+
version: 1.0.0
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: *id002
|
@@ -63,7 +63,7 @@ dependencies:
|
|
63
63
|
requirements:
|
64
64
|
- - ~>
|
65
65
|
- !ruby/object:Gem::Version
|
66
|
-
version: 1.
|
66
|
+
version: 1.1.0
|
67
67
|
type: :development
|
68
68
|
prerelease: false
|
69
69
|
version_requirements: *id005
|
@@ -144,7 +144,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
144
144
|
requirements:
|
145
145
|
- - ">="
|
146
146
|
- !ruby/object:Gem::Version
|
147
|
-
hash:
|
147
|
+
hash: -1357799826305458565
|
148
148
|
segments:
|
149
149
|
- 0
|
150
150
|
version: "0"
|