rocket_tag 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/VERSION +1 -1
- data/lib/rocket_tag/taggable.rb +42 -38
- data/rocket_tag.gemspec +1 -1
- data/spec/rocket_tag/taggable_spec.rb +90 -15
- metadata +2 -2
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.2.0
|
data/lib/rocket_tag/taggable.rb
CHANGED
@@ -43,10 +43,6 @@ module RocketTag
|
|
43
43
|
end
|
44
44
|
end
|
45
45
|
|
46
|
-
def tags_for_context context
|
47
|
-
tags.where{taggings.context==my{context}}
|
48
|
-
end
|
49
|
-
|
50
46
|
def taggings_for_context context
|
51
47
|
taggings.where{taggings.context==my{context}}
|
52
48
|
end
|
@@ -86,6 +82,20 @@ module RocketTag
|
|
86
82
|
end
|
87
83
|
end
|
88
84
|
|
85
|
+
module InstanceMethods
|
86
|
+
def tagged_similar options = {}
|
87
|
+
context = options.delete :on
|
88
|
+
raise Exception.new("#{context} is not a valid tag context for #{self.class}") unless self.class.rocket_tag.contexts.include? context
|
89
|
+
if context
|
90
|
+
contexts = [context]
|
91
|
+
else
|
92
|
+
contexts = self.class.rocket_tag.contexts
|
93
|
+
end
|
94
|
+
tags = send context.to_sym
|
95
|
+
self.class.tagged_with(tags, options).where{id != my{id}}
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
89
99
|
module ClassMethods
|
90
100
|
|
91
101
|
def rocket_tag
|
@@ -100,14 +110,6 @@ module RocketTag
|
|
100
110
|
end
|
101
111
|
end
|
102
112
|
|
103
|
-
def _with_min min, tags_list
|
104
|
-
if min and min > 1
|
105
|
-
group{~id}.
|
106
|
-
having{count(~id)>=my{min}}
|
107
|
-
else
|
108
|
-
where{}
|
109
|
-
end
|
110
|
-
end
|
111
113
|
|
112
114
|
# Generates a sifter or a where clause depending on options.
|
113
115
|
# The sifter generates a subselect with the body of the
|
@@ -116,41 +118,43 @@ module RocketTag
|
|
116
118
|
#
|
117
119
|
# Query optimization is left up to the SQL engine.
|
118
120
|
def tagged_with_sifter tags_list, options = {}
|
121
|
+
options[:sifter] = true
|
122
|
+
tagged_with tags_list, options
|
123
|
+
end
|
124
|
+
|
125
|
+
# Generates a query that provides the matches
|
126
|
+
# along with an extra column :tags_count.
|
127
|
+
def tagged_with tags_list, options = {}
|
119
128
|
on = options.delete :on
|
120
|
-
all = options.delete :all
|
121
|
-
min = options.delete(:min)
|
122
|
-
if all
|
123
|
-
min = tags_list.length
|
124
|
-
end
|
125
129
|
|
126
|
-
|
127
|
-
|
128
|
-
where &block
|
129
|
-
else
|
130
|
-
squeel &block
|
131
|
-
end
|
132
|
-
end.call do
|
133
|
-
id.in(
|
134
|
-
my{self}.
|
135
|
-
select{id}.
|
130
|
+
inner = select{count(~id).as(tags_count)}
|
131
|
+
.select("#{self.table_name}.*").
|
136
132
|
joins{tags}.
|
137
133
|
where{tags.name.in(my{tags_list})}.
|
138
134
|
_with_tag_context(on).
|
139
|
-
|
140
|
-
)
|
141
|
-
end
|
135
|
+
group{~id}
|
142
136
|
|
143
|
-
|
137
|
+
# Wrap the inner query with an outer query to shield
|
138
|
+
# the group and aggregate clauses from downstream
|
139
|
+
# queries
|
140
|
+
r = from("(#{inner.to_sql}) #{table_name}")
|
144
141
|
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
end
|
142
|
+
if options.delete :all
|
143
|
+
r = r.where{tags_count==my{tags_list.length}}
|
144
|
+
end
|
149
145
|
|
146
|
+
if min = options.delete(:min)
|
147
|
+
r = r.where{tags_count>=my{min}}
|
148
|
+
end
|
149
|
+
|
150
|
+
if options.delete :sifter
|
151
|
+
squeel do
|
152
|
+
id.in(r.select{"id"})
|
153
|
+
end
|
154
|
+
else
|
155
|
+
r.select("*")
|
156
|
+
end
|
150
157
|
|
151
|
-
def tagged_with tags_list, options = {}
|
152
|
-
options[:where] = true
|
153
|
-
tagged_with_sifter(tags_list, options)
|
154
158
|
end
|
155
159
|
|
156
160
|
def setup_for_rocket_tag
|
data/rocket_tag.gemspec
CHANGED
@@ -121,6 +121,55 @@ describe TaggableModel do
|
|
121
121
|
pending "Need to figure out how to verify eager loading other than manually inspect the log file"
|
122
122
|
end
|
123
123
|
|
124
|
+
describe "#tagged_with" do
|
125
|
+
it "should count the number of matched tags" do
|
126
|
+
|
127
|
+
#<TaggableModel id: 2, name: "00", type: nil, foo: "A"> - 3 - german, french, a, b, x
|
128
|
+
#<TaggableModel id: 3, name: "01", type: nil, foo: "B"> - 3 - german, italian, a, b, y
|
129
|
+
#<TaggableModel id: 4, name: "10", type: nil, foo: "A"> - 1 - a, c
|
130
|
+
#<TaggableModel id: 5, name: "11", type: nil, foo: "B"> - 1 - a, c
|
131
|
+
#<TaggableModel id: 7, name: "21", type: nil, foo: "B"> - 1 - german, jinglish, c, d
|
132
|
+
|
133
|
+
# r = TaggableModel.tagged_with(["a", "b", "german"]).all.each do |m|
|
134
|
+
# puts "#{m.inspect} - #{m.tags_count} - #{m.tags.map(&:name).join ', '}"
|
135
|
+
# end
|
136
|
+
|
137
|
+
r = TaggableModel.tagged_with(["a", "b", "german"]).all
|
138
|
+
r.find{|i|i.name == "00"}.tags_count.should == 3
|
139
|
+
r.find{|i|i.name == "01"}.tags_count.should == 3
|
140
|
+
r.find{|i|i.name == "10"}.tags_count.should == 1
|
141
|
+
r.find{|i|i.name == "11"}.tags_count.should == 1
|
142
|
+
r.find{|i|i.name == "21"}.tags_count.should == 1
|
143
|
+
|
144
|
+
# The 'group by' operation to generate the count tags should
|
145
|
+
# be opaque to downstream operations. Thus count should
|
146
|
+
# return the correct number of records
|
147
|
+
r = TaggableModel.tagged_with(["a", "b", "german"]).count.should == 5
|
148
|
+
|
149
|
+
# It should be possible to cascade active relation queries on
|
150
|
+
# the
|
151
|
+
r = TaggableModel.tagged_with(["a", "b", "german"]).
|
152
|
+
where{tags_count>2}.count.should == 2
|
153
|
+
|
154
|
+
# The min option is a shortcut for a query on tags_count
|
155
|
+
r = TaggableModel.tagged_with(["a", "b", "german"], :min => 2).count.should == 2
|
156
|
+
|
157
|
+
r = TaggableModel.tagged_with(["a", "b", "german"], :on => :skills).all
|
158
|
+
r.find{|i|i.name == "00"}.tags_count.should == 2
|
159
|
+
r.find{|i|i.name == "01"}.tags_count.should == 2
|
160
|
+
r.find{|i|i.name == "10"}.tags_count.should == 1
|
161
|
+
r.find{|i|i.name == "11"}.tags_count.should == 1
|
162
|
+
r.find{|i|i.name == "21"}.should be_nil
|
163
|
+
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
describe "#tagged_similar" do
|
168
|
+
it "should work" do
|
169
|
+
@t00.tagged_similar(:on => :skills).count.should == 3
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
124
173
|
describe "#tagged_with" do
|
125
174
|
describe ":all => true" do
|
126
175
|
it "should return records where *all* tags match on any context" do
|
@@ -176,34 +225,60 @@ describe TaggableModel do
|
|
176
225
|
end
|
177
226
|
end
|
178
227
|
|
228
|
+
describe "Experiments with AREL" do
|
229
|
+
it "foo" do
|
230
|
+
u_t = Arel::Table::new :users
|
231
|
+
l_t = Arel::Table::new :logs
|
232
|
+
|
233
|
+
counts = l_t.
|
234
|
+
group(l_t[:user_id]).
|
235
|
+
project(
|
236
|
+
l_t[:user_id].as("user_id"),
|
237
|
+
l_t[:user_id].count.as("count_all")
|
238
|
+
).as "foo"
|
239
|
+
|
240
|
+
puts TaggableModel.joins("JOIN " + counts.to_sql ).to_sql
|
241
|
+
end
|
242
|
+
it "should" do
|
243
|
+
u_t = Arel::Table::new :users
|
244
|
+
l_t = Arel::Table::new :logs
|
245
|
+
|
246
|
+
counts = l_t.
|
247
|
+
group(l_t[:user_id]).
|
248
|
+
project(
|
249
|
+
l_t[:user_id].as("user_id"),
|
250
|
+
l_t[:user_id].count.as("count_all")
|
251
|
+
).as "foo"
|
252
|
+
|
253
|
+
users = u_t.
|
254
|
+
join(counts).
|
255
|
+
on(u_t[:id].
|
256
|
+
eq(counts[:user_id])).
|
257
|
+
project("*").project(counts[:count_all])
|
258
|
+
|
259
|
+
puts users.to_sql
|
260
|
+
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
179
264
|
describe "#tagged_with_sifter" do
|
180
265
|
it "should be the work horse of #tagged_with but returns a sifter that can be composed into other queries" do
|
181
266
|
TaggableModel.where do
|
182
267
|
TaggableModel.tagged_with_sifter(["a", "b"]) & TaggableModel.tagged_with_sifter(["c"])
|
183
268
|
end.count.should == 2
|
184
269
|
|
270
|
+
x = TaggableModel.where do
|
271
|
+
TaggableModel.tagged_with_sifter(["a", "b"]) & TaggableModel.tagged_with_sifter(["c"])
|
272
|
+
end.to_sql
|
273
|
+
puts x
|
274
|
+
|
185
275
|
TaggableModel.where do
|
186
276
|
TaggableModel.tagged_with_sifter(["a", "b"])
|
187
277
|
end.count.should == 4
|
188
278
|
end
|
189
279
|
|
190
|
-
it "should have the options from #tagged_with passed through" do
|
191
|
-
tags_list = ["a", "b"]
|
192
|
-
options = {:x=>10, :y=>20}
|
193
|
-
TaggableModel.should_receive(:tagged_with_sifter).with(tags_list, options)
|
194
|
-
TaggableModel.tagged_with(tags_list, options)
|
195
|
-
end
|
196
280
|
end
|
197
281
|
|
198
|
-
describe "option :min" do
|
199
|
-
it "should return records where the number of matching tags >= :min" do
|
200
|
-
TaggableModel.tagged_with(["a", "b", "x"], :on => :skills).count.should == 4
|
201
|
-
|
202
|
-
TaggableModel.tagged_with(["a", "b", "x"], :on => :skills, :min => 1).count.should == 4
|
203
|
-
TaggableModel.tagged_with(["a", "b", "x"], :on => :skills, :min => 2).count.should == 2
|
204
|
-
TaggableModel.tagged_with(["a", "b", "x"], :on => :skills, :min => 3).count.should == 1
|
205
|
-
end
|
206
|
-
end
|
207
282
|
end
|
208
283
|
end
|
209
284
|
end
|
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.2.0
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Brad Phelan
|
@@ -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: -1107478710277863404
|
148
148
|
segments:
|
149
149
|
- 0
|
150
150
|
version: "0"
|