smart_tuple 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
data/README.html CHANGED
@@ -7,43 +7,61 @@
7
7
 
8
8
  <h2 id="introduction">Introduction</h2>
9
9
 
10
- <p>Sometimes we need to build SQL WHERE statements which are compound or conditional by nature. SmartTuple simplifies this task by letting us build statements of virtually unlimited complexity out of smaller ones.</p>
10
+ <p>Sometimes we need to build SQL <code>WHERE</code> statements which are compound or conditional by nature. <strong>SmartTuple</strong> simplifies this task by letting us build statements of virtually unlimited complexity out of smaller ones.</p>
11
11
 
12
12
  <p>SmartTuple is suitable for use with Ruby on Rails (ActiveRecord) and other Ruby frameworks and ORMs.</p>
13
13
 
14
- <h2 id="setup">Setup</h2>
14
+ <h2 id="setup-rails-3">Setup (Rails 3)</h2>
15
15
 
16
- <pre><code>$ gem sources --add http://rubygems.org
17
- $ gem install smart_tuple
16
+ <p>In your app’s <code>Gemfile</code>, add:</p>
17
+
18
+ <pre><code>gem "smart_tuple"
19
+ </code></pre>
20
+
21
+ <p>To install the gem with RDoc/ri documentation, do a:</p>
22
+
23
+ <pre><code>$ gem install smart_tuple
18
24
  </code></pre>
19
25
 
20
- <p>In your app's <code>config/environment.rb</code> do a:</p>
26
+ <p>Otherwise, do a <code>bundle install</code>.</p>
27
+
28
+ <h2 id="setup-rails-2">Setup (Rails 2)</h2>
29
+
30
+ <p>In your app’s <code>config/environment.rb</code> do a:</p>
21
31
 
22
- <pre><code>config.gem &quot;smart_tuple&quot;
32
+ <pre><code>config.gem "smart_tuple"
23
33
  </code></pre>
24
34
 
35
+ <p>To install the gem, do a:</p>
36
+
37
+ <pre><code>$ gem sources --add http://rubygems.org
38
+ $ gem install smart_tuple
39
+ </code></pre>
40
+
41
+ <p>, or use <code>rake gems:install</code>.</p>
42
+
25
43
  <h2 id="kickstart-demo">Kickstart Demo</h2>
26
44
 
27
- <pre><code>tup = SmartTuple.new(&quot; AND &quot;)
45
+ <pre><code>tup = SmartTuple.new(" AND ")
28
46
  tup &lt;&lt; {:brand =&gt; params[:brand]} if params[:brand].present?
29
- tup &lt;&lt; [&quot;min_price &gt;= ?&quot;, params[:min_price]] if params[:min_price].present?
30
- tup &lt;&lt; [&quot;max_price &lt;= ?&quot;, params[:max_price]] if params[:max_price].present?
47
+ tup &lt;&lt; ["min_price &gt;= ?", params[:min_price]] if params[:min_price].present?
48
+ tup &lt;&lt; ["max_price &lt;= ?", params[:max_price]] if params[:max_price].present?
31
49
 
32
50
  @phones = Phone.find(:all, :conditions =&gt; tup.compile)
33
51
  </code></pre>
34
52
 
35
- <p>There's a number of ways you can use SmartTuple depending on the situation. They are covered in the tutorial below.</p>
53
+ <p>Theres a number of ways you can use SmartTuple. Some of them is covered in the tutorial below.</p>
36
54
 
37
55
  <h2 id="tutorial">Tutorial</h2>
38
56
 
39
- <p>Suppose we've got a mobile phone catalog with a search form. We are starting with a price filter of two values: <code>min_price</code> and <code>max_price</code>, both optional.</p>
57
+ <p>Suppose weve got a mobile phone catalog with a search form. We are starting with a price filter of two values: <code>min_price</code> and <code>max_price</code>, both optional.</p>
40
58
 
41
59
  <p>Filter logic:</p>
42
60
 
43
61
  <ul>
44
- <li>If the user hasn't input anything, the filter has no conditions (allows any record).</li>
45
- <li>If the user has input <code>min_price</code>, it's used in filter condition.</li>
46
- <li>If the user has input <code>max_price</code>, it's used in filter condition.</li>
62
+ <li>If the user hasnt input anything, the filter has no conditions (allows any record).</li>
63
+ <li>If the user has input <code>min_price</code>, its used in filter condition.</li>
64
+ <li>If the user has input <code>max_price</code>, its used in filter condition.</li>
47
65
  <li>If the user has input <code>min_price</code> and <code>max_price</code>, they both are used in filter condition.</li>
48
66
  </ul>
49
67
 
@@ -53,155 +71,162 @@ tup &lt;&lt; [&quot;max_price &lt;= ?&quot;, params[:max_price]] if params[:max_
53
71
  params[:max_price] = 300 # Can be blank.
54
72
  </code></pre>
55
73
 
56
- <p>Now let's write condition-building code:</p>
74
+ <p>Now lets write condition-building code:</p>
57
75
 
58
- <pre><code># Start by creating a tuple whose statements are glued with &quot; AND &quot;.
59
- tup = SmartTuple.new(&quot; AND &quot;)
76
+ <pre><code># Start by creating a tuple whose statements are glued with " AND ".
77
+ tup = SmartTuple.new(" AND ")
60
78
 
61
79
  # If min_price is not blank, append its statement.
62
80
  if params[:min_price].present?
63
- tup &lt;&lt; [&quot;min_price &gt;= ?&quot;, params[:min_price]]
81
+ tup &lt;&lt; ["min_price &gt;= ?", params[:min_price]]
64
82
  end
65
83
 
66
84
  # Same for max_price.
67
85
  if params[:max_price].present?
68
- tup &lt;&lt; [&quot;max_price &lt;= ?&quot;, params[:max_price]]
86
+ tup &lt;&lt; ["max_price &lt;= ?", params[:max_price]]
69
87
  end
70
88
 
71
89
  # Finally, fire up the query.
72
90
  @phones = Phone.find(:all, {:conditions =&gt; tup.compile})
73
91
  </code></pre>
74
92
 
75
- <p>That's basically it. Now let's see how different <code>params</code> values affect the resulting <code>:conditions</code> value. Labelled <strong>p</strong> and <strong>c</strong> in this and following listings:</p>
93
+ <p>Thats basically it. Now lets see how different <code>params</code> values affect the resulting <code>:conditions</code> value. Labelled <strong>p</strong> and <strong>c</strong> in this and following listings:</p>
76
94
 
77
95
  <pre><code>p: {}
78
96
  c: []
79
97
 
80
98
  p: {:max_price=&gt;300}
81
- c: [&quot;max_price &lt;= ?&quot;, 300]
99
+ c: ["max_price &lt;= ?", 300]
82
100
 
83
101
  p: {:min_price=&gt;100, :max_price=&gt;300}
84
- c: [&quot;min_price &gt;= ? AND max_price &lt;= ?&quot;, 100, 300]
102
+ c: ["min_price &gt;= ? AND max_price &lt;= ?", 100, 300]
85
103
  </code></pre>
86
104
 
87
105
  <h3 id="plus-another-condition">Plus Another Condition</h3>
88
106
 
89
- <p>Let's make things a bit more user-friendly. Let user filter phones by brand. We do it by adding another field, let's call it <code>brand</code>, bearing a straight string value (that's just a simple tutorial, remember?).</p>
107
+ <p>Lets make things a bit more user-friendly. Let user filter phones by brand. We do it by adding another field, lets call it <code>brand</code>, bearing a straight string value (thats just a simple tutorial, remember?).</p>
90
108
 
91
109
  <p>Our <code>params</code> now becomes something like:</p>
92
110
 
93
- <pre><code>params[:brand] = &quot;Nokia&quot; # Can be blank.
111
+ <pre><code>params[:brand] = "Nokia" # Can be blank.
94
112
  params[:min_price] = 100 # Can be blank.
95
113
  params[:max_price] = 300 # Can be blank.
96
114
  </code></pre>
97
115
 
98
- <p>Let's build a tuple:</p>
116
+ <p>Lets build a tuple:</p>
99
117
 
100
- <pre><code>tup = SmartTuple.new(&quot; AND &quot;) +
118
+ <pre><code>tup = SmartTuple.new(" AND ") +
101
119
  ({:brand =&gt; params[:brand]} if params[:brand].present?) +
102
- ([&quot;min_price &gt;= ?&quot;, params[:min_price]] if params[:min_price].present?) +
103
- ([&quot;max_price &lt;= ?&quot;, params[:max_price]] if params[:max_price].present?)
120
+ (["min_price &gt;= ?", params[:min_price]] if params[:min_price].present?) +
121
+ (["max_price &lt;= ?", params[:max_price]] if params[:max_price].present?)
104
122
  </code></pre>
105
123
 
106
124
  <p>The above code shows that we can construct ready-made tuples with a single expression, using <code>+</code> operator. Also, if a condition is an equality test, we can use Hash notation: <code>{:brand =&gt; params[:brand]}</code>.</p>
107
125
 
108
126
  <p>A quick look at <code>params</code> and <code>:conditions</code>, again:</p>
109
127
 
110
- <pre><code>p: {:brand=&gt;&quot;Nokia&quot;}
111
- c: [&quot;brand = ?&quot;, &quot;Nokia&quot;]
128
+ <pre><code>p: {:brand=&gt;"Nokia"}
129
+ c: ["brand = ?", "Nokia"]
112
130
 
113
- p: {:brand=&gt;&quot;Nokia&quot;, :max_price=&gt;300}
114
- c: [&quot;brand = ? AND max_price &lt;= ?&quot;, &quot;Nokia&quot;, 300]
131
+ p: {:brand=&gt;"Nokia", :max_price=&gt;300}
132
+ c: ["brand = ? AND max_price &lt;= ?", "Nokia", 300]
115
133
 
116
- p: {:brand=&gt;&quot;Nokia&quot;, :min_price=&gt;100, :max_price=&gt;300}
117
- c: [&quot;brand = ? AND min_price &gt;= ? AND max_price &lt;= ?&quot;, &quot;Nokia&quot;, 100, 300]
134
+ p: {:brand=&gt;"Nokia", :min_price=&gt;100, :max_price=&gt;300}
135
+ c: ["brand = ? AND min_price &gt;= ? AND max_price &lt;= ?", "Nokia", 100, 300]
118
136
  </code></pre>
119
137
 
120
138
  <h3 id="we-want-more">We Want More!</h3>
121
139
 
122
- <p>Since we now see how easy it's to build compound conditions, we decide to further extend our search form. Now we want to:</p>
140
+ <p>Since we now see how easy its to build compound conditions, we decide to further extend our search form. Now we want to:</p>
123
141
 
124
142
  <ul>
125
143
  <li>Let user specify more than 1 brand.</li>
126
144
  <li>Let user specify a selection of colors.</li>
127
145
  </ul>
128
146
 
129
- <p>From <code>params</code> perspective that's something like:</p>
147
+ <p>From <code>params</code> perspective thats something like:</p>
130
148
 
131
- <pre><code>params[:brands] = [&quot;Nokia&quot;, &quot;Motorola&quot;] # Can be blank.
149
+ <pre><code>params[:brands] = ["Nokia", "Motorola"] # Can be blank.
132
150
  params[:min_price] = 100 # Can be blank.
133
151
  params[:max_price] = 300 # Can be blank.
134
- params[:colors] = [&quot;Black&quot;, &quot;Silver&quot;, &quot;Pink&quot;] # Can be blank.
152
+ params[:colors] = ["Black", "Silver", "Pink"] # Can be blank.
135
153
  </code></pre>
136
154
 
137
- <p>Quite obvious is that supplied values for brands and colors should be OR'ed. We're now facing the task of creating a &quot;sub-tuple&quot;, e.g. to match brand, and then merging this sub-tuple into main tuple. Doing it straight is something like:</p>
155
+ <p>Quite obvious is that supplied values for brands and colors should be ORed. Were now facing the task of creating a sub-tuple”, e.g. to match brand, and then merging this sub-tuple into main tuple. Doing it straight is something like:</p>
138
156
 
139
- <pre><code>tup = SmartTuple.new(&quot; AND &quot;)
157
+ <pre><code>tup = SmartTuple.new(" AND ")
140
158
 
141
159
  if params[:brands].present?
142
- subtup = SmartTuple.new(&quot; OR &quot;)
143
- params[:brands].each {|brand| subtup &lt;&lt; [&quot;brand = ?&quot;, brand]}
160
+ subtup = SmartTuple.new(" OR ")
161
+ params[:brands].each {|brand| subtup &lt;&lt; ["brand = ?", brand]}
144
162
  tup &lt;&lt; subtup
145
163
  end
146
164
  </code></pre>
147
165
 
148
166
  <p>Or, in a smarter way by utilizing <code>#add_each</code> method:</p>
149
167
 
150
- <pre><code>tup = SmartTuple.new(&quot; AND &quot;)
151
- tup &lt;&lt; SmartTuple.new(&quot; OR &quot;).add_each(params[:brands]) {|v| [&quot;brand = ?&quot;, v]} if params[:brands].present?
168
+ <pre><code>tup = SmartTuple.new(" AND ")
169
+ tup &lt;&lt; SmartTuple.new(" OR ").add_each(params[:brands]) {|v| ["brand = ?", v]} if params[:brands].present?
152
170
  </code></pre>
153
171
 
154
172
  <p>The final query:</p>
155
173
 
156
- <pre><code>Phone.find(:all, {:conditions =&gt; [SmartTuple.new(&quot; AND &quot;),
157
- (SmartTuple.new(&quot; OR &quot;).add_each(params[:brands]) {|v| [&quot;brand = ?&quot;, v]} if params[:brands].present?),
158
- ([&quot;min_price &gt;= ?&quot;, params[:min_price]] if params[:min_price].present?),
159
- ([&quot;max_price &lt;= ?&quot;, params[:max_price]] if params[:max_price].present?),
160
- (SmartTuple.new(&quot; OR &quot;).add_each(params[:colors]) {|v| [&quot;color = ?&quot;, v]} if params[:colors].present?),
174
+ <pre><code>Phone.find(:all, {:conditions =&gt; [SmartTuple.new(" AND "),
175
+ (SmartTuple.new(" OR ").add_each(params[:brands]) {|v| ["brand = ?", v]} if params[:brands].present?),
176
+ (["min_price &gt;= ?", params[:min_price]] if params[:min_price].present?),
177
+ (["max_price &lt;= ?", params[:max_price]] if params[:max_price].present?),
178
+ (SmartTuple.new(" OR ").add_each(params[:colors]) {|v| ["color = ?", v]} if params[:colors].present?),
161
179
  ].sum.compile})
162
180
  </code></pre>
163
181
 
164
182
  <blockquote>
165
- <p>NOTE: In the above sample I've used <code>Array#sum</code> (available in ActiveSupport) instead of <code>+</code> to add statements to the tuple. I prefer to write it like this since it allows to comment and swap lines without breaking the syntax.</p>
183
+ <p>NOTE: In the above sample Ive used <code>Array#sum</code> (available in ActiveSupport) instead of <code>+</code> to add statements to the tuple. I prefer to write it like this since it allows to comment and swap lines without breaking the syntax.</p>
184
+ </blockquote>
185
+
186
+ <blockquote>
187
+ <p>NOTE: Recommended Rails 3 usage is:</p>
188
+
189
+ <pre><code>Phone.where(...) # Pass a compiled SmartTuple object in place of `...`.
190
+ </code></pre>
166
191
  </blockquote>
167
192
 
168
193
  <p>Checking out <code>params</code> and <code>:conditions</code>:</p>
169
194
 
170
- <pre><code>p: {:brands=&gt;[&quot;Nokia&quot;], :max_price=&gt;300}
171
- c: [&quot;brand = ? AND max_price &lt;= ?&quot;, &quot;Nokia&quot;, 300]
195
+ <pre><code>p: {:brands=&gt;["Nokia"], :max_price=&gt;300}
196
+ c: ["brand = ? AND max_price &lt;= ?", "Nokia", 300]
172
197
 
173
- p: {:brands=&gt;[&quot;Nokia&quot;, &quot;Motorola&quot;], :max_price=&gt;300}
174
- c: [&quot;(brand = ? OR brand = ?) AND max_price &lt;= ?&quot;, &quot;Nokia&quot;, &quot;Motorola&quot;, 300]
198
+ p: {:brands=&gt;["Nokia", "Motorola"], :max_price=&gt;300}
199
+ c: ["(brand = ? OR brand = ?) AND max_price &lt;= ?", "Nokia", "Motorola", 300]
175
200
  ^-- ^-- note the auto brackets
176
201
 
177
- p: {:brands=&gt;[&quot;Nokia&quot;, &quot;Motorola&quot;], :max_price=&gt;300, :colors=&gt;[&quot;Black&quot;]}
178
- c: [&quot;(brand = ? OR brand = ?) AND max_price &lt;= ? AND color = ?&quot;, &quot;Nokia&quot;, &quot;Motorola&quot;, 300, &quot;Black&quot;]
202
+ p: {:brands=&gt;["Nokia", "Motorola"], :max_price=&gt;300, :colors=&gt;["Black"]}
203
+ c: ["(brand = ? OR brand = ?) AND max_price &lt;= ? AND color = ?", "Nokia", "Motorola", 300, "Black"]
179
204
 
180
- p: {:brands=&gt;[&quot;Nokia&quot;, &quot;Motorola&quot;], :colors=&gt;[&quot;Black&quot;, &quot;Silver&quot;, &quot;Pink&quot;]}
181
- c: [&quot;(brand = ? OR brand = ?) AND (color = ? OR color = ? OR color = ?)&quot;, &quot;Nokia&quot;, &quot;Motorola&quot;, &quot;Black&quot;, &quot;Silver&quot;, &quot;Pink&quot;]
205
+ p: {:brands=&gt;["Nokia", "Motorola"], :colors=&gt;["Black", "Silver", "Pink"]}
206
+ c: ["(brand = ? OR brand = ?) AND (color = ? OR color = ? OR color = ?)", "Nokia", "Motorola", "Black", "Silver", "Pink"]
182
207
  </code></pre>
183
208
 
184
- <p>That's the end of our tutorial. Hope now you've got an idea of what SmartTuple is.</p>
209
+ <p>Thats the end of our tutorial. Hope now youve got an idea of what SmartTuple is.</p>
185
210
 
186
211
  <h2 id="api-summary">API Summary</h2>
187
212
 
188
- <p>Here's a brief cheatsheet, which outlines main SmartTuple features.</p>
213
+ <p>Heres a brief cheatsheet, which outlines the main SmartTuple features.</p>
189
214
 
190
215
  <h3 id="appending-statements">Appending Statements</h3>
191
216
 
192
217
  <pre><code># Array.
193
- tup &lt;&lt; [&quot;brand = ?&quot;, &quot;Nokia&quot;]
194
- tup &lt;&lt; [&quot;brand = ? AND color = ?&quot;, &quot;Nokia&quot;, &quot;Black&quot;]
218
+ tup &lt;&lt; ["brand = ?", "Nokia"]
219
+ tup &lt;&lt; ["brand = ? AND color = ?", "Nokia", "Black"]
195
220
 
196
221
  # Hash.
197
- tup &lt;&lt; {:brand =&gt; &quot;Nokia&quot;}
198
- tup &lt;&lt; {:brand =&gt; &quot;Nokia&quot;, :color =&gt; &quot;Black&quot;}
222
+ tup &lt;&lt; {:brand =&gt; "Nokia"}
223
+ tup &lt;&lt; {:brand =&gt; "Nokia", :color =&gt; "Black"}
199
224
 
200
225
  # Another SmartTuple.
201
226
  tup &lt;&lt; other_tuple
202
227
 
203
- # String.
204
- tup &lt;&lt; &quot;brand IS NULL&quot;
228
+ # String. Generally NOT recommended.
229
+ tup &lt;&lt; "min_price &gt;= 75"
205
230
  </code></pre>
206
231
 
207
232
  <p>Appending empty or blank (where appropriate) statements has no effect on the receiver:</p>
@@ -210,24 +235,24 @@ tup &lt;&lt; &quot;brand IS NULL&quot;
210
235
  tup &lt;&lt; []
211
236
  tup &lt;&lt; {}
212
237
  tup &lt;&lt; an_empty_tuple
213
- tup &lt;&lt; &quot;&quot;
214
- tup &lt;&lt; &quot; &quot; # Will be treated as blank if ActiveSupport is on.
238
+ tup &lt;&lt; ""
239
+ tup &lt;&lt; " " # Will be treated as blank if ActiveSupport is on.
215
240
  </code></pre>
216
241
 
217
242
  <p>Another way to append something is to use <code>+</code>.</p>
218
243
 
219
- <pre><code>tup = SmartTuple.new(&quot; AND &quot;) + {:brand =&gt; &quot;Nokia&quot;} + [&quot;max_price &lt;= ?&quot;, 300]
244
+ <pre><code>tup = SmartTuple.new(" AND ") + {:brand =&gt; "Nokia"} + ["max_price &lt;= ?", 300]
220
245
  </code></pre>
221
246
 
222
247
  <p>Appending one statement per each collection item is easy through <code>#add_each</code>:</p>
223
248
 
224
- <pre><code>tup.add_each([&quot;Nokia&quot;, &quot;Motorola&quot;]) {|v| [&quot;brand = ?&quot;, v]}
249
+ <pre><code>tup.add_each(["Nokia", "Motorola"]) {|v| ["brand = ?", v]}
225
250
  </code></pre>
226
251
 
227
252
  <p>The latter can be made conditional. Remember, appending <code>nil</code> has no effect on the receiving tuple, which gives us freedom to use conditions whenever we want to:</p>
228
253
 
229
- <pre><code>tup.add_each([&quot;Nokia&quot;, &quot;Motorola&quot;]) do |v|
230
- [&quot;brand = ?&quot;, v] if v =~ /^Moto/
254
+ <pre><code>tup.add_each(["Nokia", "Motorola"]) do |v|
255
+ ["brand = ?", v] if v =~ /^Moto/
231
256
  end
232
257
  </code></pre>
233
258
 
@@ -235,7 +260,7 @@ end
235
260
 
236
261
  <p><em>This chapter still has to be written.</em></p>
237
262
 
238
- <pre><code>tup = SmartTuple.new(&quot; AND &quot;)
263
+ <pre><code>tup = SmartTuple.new(" AND ")
239
264
  tup.brackets
240
265
  =&gt; :auto
241
266
 
@@ -255,43 +280,44 @@ tup.brackets = :auto
255
280
 
256
281
  <p>Compiling is converting the tuple into something suitable for use as <code>:conditions</code> of an ActiveRecord call.</p>
257
282
 
258
- <p>It's as straight as:</p>
283
+ <p>Its as straight as:</p>
259
284
 
260
285
  <pre><code>tup.compile
261
286
  tup.to_a # An alias, does the same.
262
287
 
263
288
  # Go fetch!
264
- Phone.find(:all, :conditions =&gt; tup.compile)
289
+ Phone.find(:all, :conditions =&gt; tup.compile) # Rails 2
290
+ Phone.where(tup.compile) # Rails 3
265
291
  </code></pre>
266
292
 
267
293
  <h3 id="contents-and-size">Contents and Size</h3>
268
294
 
269
- <p>You can examine tuple's state with methods often found in other Ruby classes: <code>#empty?</code>, <code>#size</code>, and attribute accessors <code>#statements</code> and <code>#args</code>.</p>
295
+ <p>You can examine tuples state with methods often found in other Ruby classes: <code>#empty?</code>, <code>#size</code>, and attribute accessors <code>#statements</code> and <code>#args</code>.</p>
270
296
 
271
- <pre><code>tup = SmartTuple.new(&quot; AND &quot;)
297
+ <pre><code>tup = SmartTuple.new(" AND ")
272
298
  tup.empty?
273
299
  =&gt; true
274
300
  tup.size
275
301
  =&gt; 0
276
302
 
277
- tup &lt;&lt; [&quot;brand = ?&quot;, &quot;Nokia&quot;]
303
+ tup &lt;&lt; ["brand = ?", "Nokia"]
278
304
  tup.empty?
279
305
  =&gt; false
280
306
  tup.size
281
307
  =&gt; 1
282
308
 
283
- tup &lt;&lt; [&quot;max_price &gt;= ?&quot;, 300]
309
+ tup &lt;&lt; ["max_price &gt;= ?", 300]
284
310
  tup.size
285
311
  =&gt; 2
286
312
 
287
313
  tup.statements
288
- =&gt; [&quot;brand = ?&quot;, &quot;max_price &gt;= ?&quot;]
314
+ =&gt; ["brand = ?", "max_price &gt;= ?"]
289
315
  tup.args
290
- =&gt; [&quot;Nokia&quot;, 300]
316
+ =&gt; ["Nokia", 300]
291
317
  </code></pre>
292
318
 
293
319
  <h2 id="feedback">Feedback</h2>
294
320
 
295
- <p>Send bug reports, suggestions and criticisms through <a href="http://github.com/dadooda/smart_tuple">project's page on GitHub</a>.</p>
321
+ <p>Send bug reports, suggestions and criticisms through <a href="http://github.com/dadooda/smart_tuple">projects page on GitHub</a>.</p>
296
322
 
297
323
  <p>Licensed under the MIT License.</p>
data/README.md CHANGED
@@ -6,21 +6,39 @@ SmartTuple: A Simple Yet Smart SQL Conditions Builder
6
6
  Introduction
7
7
  ------------
8
8
 
9
- Sometimes we need to build SQL WHERE statements which are compound or conditional by nature. SmartTuple simplifies this task by letting us build statements of virtually unlimited complexity out of smaller ones.
9
+ Sometimes we need to build SQL `WHERE` statements which are compound or conditional by nature. **SmartTuple** simplifies this task by letting us build statements of virtually unlimited complexity out of smaller ones.
10
10
 
11
11
  SmartTuple is suitable for use with Ruby on Rails (ActiveRecord) and other Ruby frameworks and ORMs.
12
12
 
13
13
 
14
- Setup
15
- -----
14
+ Setup (Rails 3)
15
+ ---------------
16
+
17
+ In your app's `Gemfile`, add:
18
+
19
+ gem "smart_tuple"
20
+
21
+ To install the gem with RDoc/ri documentation, do a:
16
22
 
17
- $ gem sources --add http://rubygems.org
18
23
  $ gem install smart_tuple
19
24
 
25
+ Otherwise, do a `bundle install`.
26
+
27
+
28
+ Setup (Rails 2)
29
+ ---------------
30
+
20
31
  In your app's `config/environment.rb` do a:
21
32
 
22
33
  config.gem "smart_tuple"
23
34
 
35
+ To install the gem, do a:
36
+
37
+ $ gem sources --add http://rubygems.org
38
+ $ gem install smart_tuple
39
+
40
+ , or use `rake gems:install`.
41
+
24
42
 
25
43
  Kickstart Demo
26
44
  --------------
@@ -32,7 +50,7 @@ Kickstart Demo
32
50
 
33
51
  @phones = Phone.find(:all, :conditions => tup.compile)
34
52
 
35
- There's a number of ways you can use SmartTuple depending on the situation. They are covered in the tutorial below.
53
+ There's a number of ways you can use SmartTuple. Some of them is covered in the tutorial below.
36
54
 
37
55
 
38
56
  Tutorial
@@ -151,6 +169,10 @@ The final query:
151
169
 
152
170
  > NOTE: In the above sample I've used `Array#sum` (available in ActiveSupport) instead of `+` to add statements to the tuple. I prefer to write it like this since it allows to comment and swap lines without breaking the syntax.
153
171
 
172
+ > NOTE: Recommended Rails 3 usage is:
173
+ >
174
+ > Phone.where(...) # Pass a compiled SmartTuple object in place of `...`.
175
+
154
176
  Checking out `params` and `:conditions`:
155
177
 
156
178
  p: {:brands=>["Nokia"], :max_price=>300}
@@ -172,7 +194,7 @@ That's the end of our tutorial. Hope now you've got an idea of what SmartTuple i
172
194
  API Summary
173
195
  -----------
174
196
 
175
- Here's a brief cheatsheet, which outlines main SmartTuple features.
197
+ Here's a brief cheatsheet, which outlines the main SmartTuple features.
176
198
 
177
199
  ### Appending Statements ###
178
200
 
@@ -187,8 +209,8 @@ Here's a brief cheatsheet, which outlines main SmartTuple features.
187
209
  # Another SmartTuple.
188
210
  tup << other_tuple
189
211
 
190
- # String.
191
- tup << "brand IS NULL"
212
+ # String. Generally NOT recommended.
213
+ tup << "min_price >= 75"
192
214
 
193
215
  Appending empty or blank (where appropriate) statements has no effect on the receiver:
194
216
 
@@ -244,7 +266,8 @@ It's as straight as:
244
266
  tup.to_a # An alias, does the same.
245
267
 
246
268
  # Go fetch!
247
- Phone.find(:all, :conditions => tup.compile)
269
+ Phone.find(:all, :conditions => tup.compile) # Rails 2
270
+ Phone.where(tup.compile) # Rails 3
248
271
 
249
272
 
250
273
  ### Contents and Size ###
data/Rakefile CHANGED
@@ -1,4 +1,5 @@
1
1
  require "rake/rdoctask"
2
+ require "yaml"
2
3
 
3
4
  GEM_NAME = "smart_tuple"
4
5
 
@@ -14,9 +15,9 @@ begin
14
15
  gem.files = FileList[
15
16
  "[A-Z]*",
16
17
  "*.gemspec",
17
- "generators/**/*",
18
- "lib/**/*.rb",
19
18
  "init.rb",
19
+ "lib/**/*.rb",
20
+ "spec/**/*.rb",
20
21
  ]
21
22
  end
22
23
  rescue LoadError
@@ -26,13 +27,22 @@ end
26
27
  desc "Rebuild gemspec and package"
27
28
  task :rebuild => [:gemspec, :build]
28
29
 
29
- desc "Push (publish) gem to RubyGems (aka Gemcutter)"
30
- task :push => :rebuild do
31
- # Yet found no way to ask Jeweler forge a complete version string for us.
30
+ desc "Push (publish) gem to RubyGems.org"
31
+ task :push do
32
+ # NOTE: Yet found no way to ask Jeweler forge a complete version string for us.
32
33
  vh = YAML.load(File.read("VERSION.yml"))
33
- version = [vh[:major], vh[:minor], vh[:patch]].join(".")
34
- pkgfile = File.join("pkg", [GEM_NAME, "-", version, ".gem"].to_s)
35
- system("gem", "push", pkgfile)
34
+ version = [vh[:major], vh[:minor], vh[:patch], vh[:build]].compact.join(".")
35
+ pkgfile = File.join("pkg", "#{GEM_NAME}-#{version}.gem")
36
+ Kernel.system("gem", "push", pkgfile)
37
+ end
38
+
39
+ desc "Generate RDoc documentation"
40
+ Rake::RDocTask.new(:rdoc) do |rdoc|
41
+ rdoc.rdoc_dir = "doc"
42
+ rdoc.title = "SmartTuple"
43
+ #rdoc.options << "--line-numbers"
44
+ #rdoc.options << "--inline-source"
45
+ rdoc.rdoc_files.include("lib/**/*.rb")
36
46
  end
37
47
 
38
48
  desc "Compile README preview"
data/VERSION.yml CHANGED
@@ -1,4 +1,4 @@
1
1
  ---
2
2
  :major: 0
3
3
  :minor: 1
4
- :patch: 1
4
+ :patch: 2
data/init.rb CHANGED
@@ -1,3 +1,4 @@
1
+ # Rails plugin init.
1
2
  Dir[File.join(File.dirname(__FILE__), "lib/**/*.rb")].each do |fn|
2
3
  require fn
3
4
  end
data/lib/smart_tuple.rb CHANGED
@@ -1,34 +1,50 @@
1
+ # SQL condition builder.
2
+ #
3
+ # tup = SmartTuple.new(" AND ")
4
+ # tup << {:brand => "Nokia"}
5
+ # tup << ["min_price >= ?", 75]
6
+ # tup.compile # => ["brand = ? AND min_price >= ?", "Nokia", 75]
1
7
  class SmartTuple
8
+ # Array of SQL argument parts.
2
9
  attr_reader :args
10
+
11
+ # Put brackets around statements. <tt>true</tt>, <tt>false</tt> or <tt>:auto</tt>. Default:
12
+ #
13
+ # :auto
3
14
  attr_reader :brackets
15
+
16
+ # String to glue statements together.
4
17
  attr_accessor :glue
18
+
19
+ # Array of SQL statement parts.
5
20
  attr_reader :statements
6
21
 
22
+ # Initializer.
23
+ #
7
24
  # new(" AND ")
8
- # new(" OR ")
9
- # new(", ") # E.g. for a SET or UPDATE statement.
25
+ # new(" OR ", :brackets => true)
10
26
  def initialize(glue, attrs = {})
11
27
  @glue = glue
12
28
  clear
13
29
  attrs.each {|k, v| send("#{k}=", v)}
14
30
  end
15
31
 
16
- # We need it to control #dup behaviour.
17
- def initialize_copy(src)
32
+ # Service initializer for <tt>dup</tt>.
33
+ def initialize_copy(src) #:nodoc:
18
34
  @statements = src.statements.dup
19
35
  @args = src.args.dup
20
36
  end
21
37
 
22
- # NOTE: Alphabetical order below.
23
-
24
- # Add a sub-statement, return new object. See <tt>#&lt;&lt;</tt>.
38
+ # Add a statement, return new object. See #<<.
39
+ #
25
40
  # SmartTuple.new(" AND ") + {:brand => "Nokia"} + ["max_price <= ?", 300]
26
- def +(sub)
41
+ def +(arg)
27
42
  # Since #<< supports chaining, it boils down to this.
28
- dup << sub
43
+ dup << arg
29
44
  end
30
45
 
31
- # Append a sub-statement.
46
+ # Add a statement, return self.
47
+ #
32
48
  # # Array.
33
49
  # tup << ["brand = ?", "Nokia"]
34
50
  # tup << ["brand = ? AND color = ?", "Nokia", "Black"]
@@ -41,70 +57,71 @@ class SmartTuple
41
57
  # tup << other_tuple
42
58
  #
43
59
  # # String. Generally NOT recommended.
44
- # tup << "brand IS NULL"
60
+ # tup << "min_price >= 75"
61
+ #
62
+ # Adding anything empty or blank (where appropriate) has no effect on the receiver:
45
63
  #
46
- # Appending empty or blank (where appropriate) statements has no effect on the receiver:
47
64
  # tup << nil
48
65
  # tup << []
49
66
  # tup << {}
50
67
  # tup << another_empty_tuple
51
68
  # tup << ""
52
69
  # tup << " " # Will be treated as blank if ActiveSupport is on.
53
- def <<(sub)
54
- ##p "self.class", self.class
55
-
56
- # NOTE: Autobracketing help is placing [value] instead of (value) into @statements. #compile will take it into account.
70
+ def <<(arg)
71
+ # NOTE: Autobracketing is placing `[value]` instead of `value` into `@statements`. #compile understands it.
57
72
 
58
73
  # Chop off everything empty first time.
59
- if sub.nil? or (sub.empty? rescue false) or (sub.blank? rescue false)
60
- ##puts "-- empty"
61
- elsif sub.is_a? String or (sub.acts_like? :string rescue false)
62
- ##puts "-- is a string"
63
- @statements << sub.to_s
64
- elsif sub.is_a? Array
65
- # NOTE: If sub == [], the execution won't get here.
74
+ if arg.nil? or (arg.empty? rescue false) or (arg.blank? rescue false)
75
+ elsif arg.is_a? String or (arg.acts_like? :string rescue false)
76
+ @statements << arg.to_s
77
+ elsif arg.is_a? Array
78
+ # NOTE: If arg == [], the execution won't get here.
66
79
  # So, we've got at least one element. Therefore stmt will be scalar, and args -- DEFINITELY an array.
67
- stmt, args = sub[0], sub[1..-1]
68
- ##p "stmt", stmt
69
- ##p "args", args
80
+ stmt, args = arg[0], arg[1..-1]
70
81
  if not (stmt.nil? or (stmt.empty? rescue false) or (stmt.blank? rescue false))
71
- ##puts "-- stmt nempty"
72
82
  # Help do autobracketing later. Here we can ONLY judge by number of passed arguments.
73
83
  @statements << (args.size > 1 ? [stmt] : stmt)
74
84
  @args += args
75
85
  end
76
- elsif sub.is_a? Hash
77
- sub.each do |k, v|
78
- @statements << "#{k} = ?"
79
- @args << v
86
+ elsif arg.is_a? Hash
87
+ arg.each do |k, v|
88
+ if v.nil?
89
+ # NOTE: AR supports it for Hashes only. ["kk = ?", nil] will not be converted.
90
+ @statements << "#{k} IS NULL"
91
+ else
92
+ @statements << "#{k} = ?"
93
+ @args << v
94
+ end
80
95
  end
81
- elsif sub.is_a? self.class
82
- # NOTE: If sub is empty, the execution won't get here.
96
+ elsif arg.is_a? self.class
97
+ # NOTE: If arg is empty, the execution won't get here.
83
98
 
84
99
  # Autobrackets here are smarter, than in Array processing case.
85
- stmt = sub.compile[0]
86
- @statements << ((sub.size > 1 or sub.args.size > 1) ? [stmt] : stmt)
87
- @args += sub.args
100
+ stmt = arg.compile[0]
101
+ @statements << ((arg.size > 1 or arg.args.size > 1) ? [stmt] : stmt)
102
+ @args += arg.args
88
103
  else
89
- raise ArgumentError, "Invalid sub-statement #{sub.inspect}"
104
+ raise ArgumentError, "Invalid statement #{arg.inspect}"
90
105
  end
91
106
 
92
107
  # Return self, it's IMPORTANT to make chaining possible.
93
108
  self
94
109
  end
95
110
 
96
- # Iterate over collection and add block's result per each record.
111
+ # Iterate over collection and add block's result to self once per each record.
112
+ #
97
113
  # add_each(brands) do |v|
98
114
  # ["brand = ?", v]
99
115
  # end
100
116
  #
101
117
  # Can be conditional:
118
+ #
102
119
  # tup.add_each(["Nokia", "Motorola"]) do |v|
103
120
  # ["brand = ?", v] if v =~ /^Moto/
104
121
  # end
105
122
  def add_each(collection, &block)
106
123
  raise ArgumentError, "Code block expected" if not block
107
- ##p "collection", collection
124
+
108
125
  collection.each do |v|
109
126
  self << yield(v)
110
127
  end
@@ -114,6 +131,7 @@ class SmartTuple
114
131
  end
115
132
 
116
133
  # Set bracketing mode.
134
+ #
117
135
  # brackets = true # Put brackets around each sub-statement.
118
136
  # brackets = false # Don't put brackets.
119
137
  # brackets = :auto # Automatically put brackets around compound sub-statements.
@@ -122,23 +140,23 @@ class SmartTuple
122
140
  @brackets = value
123
141
  end
124
142
 
125
- # Set self into default state.
143
+ # Clear self.
126
144
  def clear
127
145
  @statements = []
128
146
  @args = []
129
147
  @brackets = :auto
130
148
 
131
- # Array does it like this. We do either.
149
+ # `Array` does it like this. We do either.
132
150
  self
133
151
  end
134
152
 
135
- # Compile self into an array.
153
+ # Compile self into an array. Empty self yields empty array.
154
+ #
155
+ # compile # => []
156
+ # compile # => ["brand = ? AND min_price >= ?", "Nokia", 75]
136
157
  def compile
137
158
  return [] if empty?
138
159
 
139
- ##p "@statements", @statements
140
- ##p "@args", @args
141
-
142
160
  # Build "bracketed" statements.
143
161
  bsta = @statements.map do |s|
144
162
  auto_brackets, scalar_s = s.is_a?(Array) ? [true, s[0]] : [false, s]
@@ -173,6 +191,10 @@ class SmartTuple
173
191
  end
174
192
  alias_method :to_a, :compile
175
193
 
194
+ # Return <tt>true</tt> if self is empty.
195
+ #
196
+ # tup = SmartTuple.new(" AND ")
197
+ # tup.empty? # => true
176
198
  def empty?
177
199
  @statements.empty?
178
200
  end
data/smart_tuple.gemspec CHANGED
@@ -1,43 +1,39 @@
1
1
  # Generated by jeweler
2
- # DO NOT EDIT THIS FILE
3
- # Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
4
  # -*- encoding: utf-8 -*-
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{smart_tuple}
8
- s.version = "0.1.1"
8
+ s.version = "0.1.2"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Alex Fortuna"]
12
- s.date = %q{2010-11-16}
12
+ s.date = %q{2011-07-14}
13
13
  s.description = %q{A Simple Yet Smart SQL Conditions Builder}
14
14
  s.email = %q{alex.r@askit.org}
15
15
  s.extra_rdoc_files = [
16
16
  "README.html",
17
- "README.md"
17
+ "README.md"
18
18
  ]
19
19
  s.files = [
20
20
  "MIT-LICENSE",
21
- "README.html",
22
- "README.md",
23
- "Rakefile",
24
- "VERSION.yml",
25
- "init.rb",
26
- "lib/smart_tuple.rb",
27
- "smart_tuple.gemspec"
21
+ "README.html",
22
+ "README.md",
23
+ "Rakefile",
24
+ "VERSION.yml",
25
+ "init.rb",
26
+ "lib/smart_tuple.rb",
27
+ "smart_tuple.gemspec",
28
+ "spec/smart_tuple_spec.rb",
29
+ "spec/spec_helper.rb"
28
30
  ]
29
31
  s.homepage = %q{http://github.com/dadooda/smart_tuple}
30
- s.rdoc_options = ["--charset=UTF-8"]
31
32
  s.require_paths = ["lib"]
32
- s.rubygems_version = %q{1.3.7}
33
+ s.rubygems_version = %q{1.6.2}
33
34
  s.summary = %q{A Simple Yet Smart SQL Conditions Builder}
34
- s.test_files = [
35
- "spec/spec_helper.rb",
36
- "spec/smart_tuple_spec.rb"
37
- ]
38
35
 
39
36
  if s.respond_to? :specification_version then
40
- current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
41
37
  s.specification_version = 3
42
38
 
43
39
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
@@ -46,3 +42,4 @@ Gem::Specification.new do |s|
46
42
  else
47
43
  end
48
44
  end
45
+
@@ -188,6 +188,7 @@ describe (klass = SmartTuple) do
188
188
  end
189
189
  end # #+
190
190
 
191
+ # Most tests are here, since arg conversion is performed right in `<<`.
191
192
  describe "#<<" do
192
193
  it "ignores nil/empty/blank objects" do
193
194
  objs = []
@@ -210,6 +211,14 @@ describe (klass = SmartTuple) do
210
211
  (r << ["is_male = ?", true]).should eql r
211
212
  end
212
213
 
214
+ it "supports IS NULL for Hash" do
215
+ r << {:kk => nil}
216
+ r.compile.should == ["kk IS NULL"]
217
+
218
+ r << {:mkk => 10}
219
+ r.compile.should == ["kk IS NULL AND mkk = ?", 10]
220
+ end
221
+
213
222
  it "supports chaining" do
214
223
  r << ["age >= ?", 18] << "created_at IS NULL"
215
224
  r.compile.should == ["age >= ? AND created_at IS NULL", 18]
metadata CHANGED
@@ -1,13 +1,8 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: smart_tuple
3
3
  version: !ruby/object:Gem::Version
4
- hash: 25
5
- prerelease: false
6
- segments:
7
- - 0
8
- - 1
9
- - 1
10
- version: 0.1.1
4
+ prerelease:
5
+ version: 0.1.2
11
6
  platform: ruby
12
7
  authors:
13
8
  - Alex Fortuna
@@ -15,7 +10,7 @@ autorequire:
15
10
  bindir: bin
16
11
  cert_chain: []
17
12
 
18
- date: 2010-11-16 00:00:00 +03:00
13
+ date: 2011-07-14 00:00:00 +04:00
19
14
  default_executable:
20
15
  dependencies: []
21
16
 
@@ -37,15 +32,15 @@ files:
37
32
  - init.rb
38
33
  - lib/smart_tuple.rb
39
34
  - smart_tuple.gemspec
40
- - spec/spec_helper.rb
41
35
  - spec/smart_tuple_spec.rb
36
+ - spec/spec_helper.rb
42
37
  has_rdoc: true
43
38
  homepage: http://github.com/dadooda/smart_tuple
44
39
  licenses: []
45
40
 
46
41
  post_install_message:
47
- rdoc_options:
48
- - --charset=UTF-8
42
+ rdoc_options: []
43
+
49
44
  require_paths:
50
45
  - lib
51
46
  required_ruby_version: !ruby/object:Gem::Requirement
@@ -53,26 +48,19 @@ required_ruby_version: !ruby/object:Gem::Requirement
53
48
  requirements:
54
49
  - - ">="
55
50
  - !ruby/object:Gem::Version
56
- hash: 3
57
- segments:
58
- - 0
59
51
  version: "0"
60
52
  required_rubygems_version: !ruby/object:Gem::Requirement
61
53
  none: false
62
54
  requirements:
63
55
  - - ">="
64
56
  - !ruby/object:Gem::Version
65
- hash: 3
66
- segments:
67
- - 0
68
57
  version: "0"
69
58
  requirements: []
70
59
 
71
60
  rubyforge_project:
72
- rubygems_version: 1.3.7
61
+ rubygems_version: 1.6.2
73
62
  signing_key:
74
63
  specification_version: 3
75
64
  summary: A Simple Yet Smart SQL Conditions Builder
76
- test_files:
77
- - spec/spec_helper.rb
78
- - spec/smart_tuple_spec.rb
65
+ test_files: []
66
+