rocket_tag 0.1.0 → 0.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/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"
|