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 CHANGED
@@ -1 +1 @@
1
- 0.1.0
1
+ 0.2.0
@@ -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
- lambda do |&block|
127
- if options.delete :where
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
- _with_min(min, tags_list)
140
- )
141
- end
135
+ group{~id}
142
136
 
143
- end
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
- # We need to overide the default count
146
- # and do a sub select as we are using gr
147
- def count
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
@@ -5,7 +5,7 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "rocket_tag"
8
- s.version = "0.1.0"
8
+ s.version = "0.2.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"]
@@ -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.1.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: -3706512005983090004
147
+ hash: -1107478710277863404
148
148
  segments:
149
149
  - 0
150
150
  version: "0"