squeel_rbg 0.8.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) hide show
  1. data/.gitignore +4 -0
  2. data/.yardopts +3 -0
  3. data/Gemfile +13 -0
  4. data/LICENSE +20 -0
  5. data/README.md +398 -0
  6. data/Rakefile +19 -0
  7. data/lib/core_ext/hash.rb +13 -0
  8. data/lib/core_ext/symbol.rb +39 -0
  9. data/lib/squeel/adapters/active_record/3.0/association_preload.rb +15 -0
  10. data/lib/squeel/adapters/active_record/3.0/compat.rb +142 -0
  11. data/lib/squeel/adapters/active_record/3.0/context.rb +66 -0
  12. data/lib/squeel/adapters/active_record/3.0/join_association.rb +54 -0
  13. data/lib/squeel/adapters/active_record/3.0/join_dependency.rb +84 -0
  14. data/lib/squeel/adapters/active_record/3.0/relation.rb +327 -0
  15. data/lib/squeel/adapters/active_record/context.rb +66 -0
  16. data/lib/squeel/adapters/active_record/join_association.rb +44 -0
  17. data/lib/squeel/adapters/active_record/join_dependency.rb +83 -0
  18. data/lib/squeel/adapters/active_record/preloader.rb +21 -0
  19. data/lib/squeel/adapters/active_record/relation.rb +351 -0
  20. data/lib/squeel/adapters/active_record.rb +28 -0
  21. data/lib/squeel/configuration.rb +54 -0
  22. data/lib/squeel/constants.rb +24 -0
  23. data/lib/squeel/context.rb +67 -0
  24. data/lib/squeel/dsl.rb +86 -0
  25. data/lib/squeel/nodes/aliasing.rb +13 -0
  26. data/lib/squeel/nodes/and.rb +9 -0
  27. data/lib/squeel/nodes/as.rb +14 -0
  28. data/lib/squeel/nodes/binary.rb +32 -0
  29. data/lib/squeel/nodes/function.rb +66 -0
  30. data/lib/squeel/nodes/join.rb +113 -0
  31. data/lib/squeel/nodes/key_path.rb +192 -0
  32. data/lib/squeel/nodes/nary.rb +45 -0
  33. data/lib/squeel/nodes/not.rb +9 -0
  34. data/lib/squeel/nodes/operation.rb +32 -0
  35. data/lib/squeel/nodes/operators.rb +43 -0
  36. data/lib/squeel/nodes/or.rb +9 -0
  37. data/lib/squeel/nodes/order.rb +53 -0
  38. data/lib/squeel/nodes/predicate.rb +71 -0
  39. data/lib/squeel/nodes/predicate_operators.rb +29 -0
  40. data/lib/squeel/nodes/stub.rb +125 -0
  41. data/lib/squeel/nodes/unary.rb +28 -0
  42. data/lib/squeel/nodes.rb +17 -0
  43. data/lib/squeel/predicate_methods.rb +14 -0
  44. data/lib/squeel/version.rb +3 -0
  45. data/lib/squeel/visitors/attribute_visitor.rb +191 -0
  46. data/lib/squeel/visitors/base.rb +112 -0
  47. data/lib/squeel/visitors/predicate_visitor.rb +319 -0
  48. data/lib/squeel/visitors/symbol_visitor.rb +48 -0
  49. data/lib/squeel/visitors.rb +3 -0
  50. data/lib/squeel.rb +28 -0
  51. data/lib/squeel_rbg.rb +5 -0
  52. data/spec/blueprints/articles.rb +5 -0
  53. data/spec/blueprints/comments.rb +5 -0
  54. data/spec/blueprints/notes.rb +3 -0
  55. data/spec/blueprints/people.rb +4 -0
  56. data/spec/blueprints/tags.rb +3 -0
  57. data/spec/console.rb +22 -0
  58. data/spec/core_ext/symbol_spec.rb +75 -0
  59. data/spec/helpers/squeel_helper.rb +21 -0
  60. data/spec/spec_helper.rb +66 -0
  61. data/spec/squeel/adapters/active_record/context_spec.rb +44 -0
  62. data/spec/squeel/adapters/active_record/join_association_spec.rb +18 -0
  63. data/spec/squeel/adapters/active_record/join_dependency_spec.rb +66 -0
  64. data/spec/squeel/adapters/active_record/relation_spec.rb +627 -0
  65. data/spec/squeel/dsl_spec.rb +92 -0
  66. data/spec/squeel/nodes/function_spec.rb +149 -0
  67. data/spec/squeel/nodes/join_spec.rb +47 -0
  68. data/spec/squeel/nodes/key_path_spec.rb +100 -0
  69. data/spec/squeel/nodes/operation_spec.rb +149 -0
  70. data/spec/squeel/nodes/operators_spec.rb +87 -0
  71. data/spec/squeel/nodes/order_spec.rb +30 -0
  72. data/spec/squeel/nodes/predicate_operators_spec.rb +88 -0
  73. data/spec/squeel/nodes/predicate_spec.rb +50 -0
  74. data/spec/squeel/nodes/stub_spec.rb +198 -0
  75. data/spec/squeel/visitors/attribute_visitor_spec.rb +142 -0
  76. data/spec/squeel/visitors/predicate_visitor_spec.rb +342 -0
  77. data/spec/squeel/visitors/symbol_visitor_spec.rb +42 -0
  78. data/spec/support/schema.rb +104 -0
  79. data/squeel.gemspec +43 -0
  80. metadata +246 -0
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/.yardopts ADDED
@@ -0,0 +1,3 @@
1
+ --main README.md
2
+ --private
3
+ --exclude compat
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ source "http://rubygems.org"
2
+ gemspec
3
+
4
+ if ENV['RAILS_VERSION'] == 'release'
5
+ gem 'activesupport'
6
+ gem 'activerecord'
7
+ else
8
+ gem 'arel', :git => 'git://github.com/rails/arel.git'
9
+ git 'git://github.com/rails/rails.git' do
10
+ gem 'activesupport'
11
+ gem 'activerecord'
12
+ end
13
+ end
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010-2011 Ernie Miller
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,398 @@
1
+ # Squeel
2
+
3
+ Squeel lets you write your ActiveRecord queries with with fewer strings, and more Ruby,
4
+ by making the ARel awesomeness that lies beneath ActiveRecord more accessible.
5
+
6
+ Squeel lets you rewrite...
7
+
8
+ Article.where ['created_at >= ?', 2.weeks.ago]
9
+
10
+ ...as...
11
+
12
+ Article.where{created_at >= 2.weeks.ago}
13
+
14
+ This is a _good thing_. If you don't agree, Squeel might not be for you. The above is
15
+ just a simple example -- Squeel's capable of a whole lot more. Keep reading.
16
+
17
+ ## Getting started
18
+
19
+ In your Gemfile:
20
+
21
+ gem "squeel" # Last officially released gem
22
+ # gem "squeel", :git => "git://github.com/ernie/squeel.git" # Track git repo
23
+
24
+ In an initializer:
25
+
26
+ Squeel.configure do |config|
27
+ # To load hash extensions (to allow for AND (&), OR (|), and NOT (-) against
28
+ # hashes of conditions)
29
+ config.load_core_extensions :hash
30
+
31
+ # To load symbol extensions (for a subset of the old MetaWhere functionality,
32
+ # via ARel predicate methods on Symbols: :name.matches, etc)
33
+ # config.load_core_extensions :symbol
34
+
35
+ # To load both hash and symbol extensions
36
+ # config.load_core_extensions :hash, :symbol
37
+ end
38
+
39
+ ## The Squeel Query DSL
40
+
41
+ Squeel enhances the normal ActiveRecord query methods by enabling them to accept
42
+ blocks. Inside a block, the Squeel query DSL can be used. Note the use of curly braces
43
+ in these examples instead of parentheses. `{}` denotes a Squeel DSL query.
44
+
45
+ Stubs and keypaths are the two primary building blocks used in a Squeel DSL query, so we'll
46
+ start by taking a look at them. Most of the other examples that follow will be based on
47
+ this "symbol-less" block syntax.
48
+
49
+ **An important gotcha, before we begin:** The Squeel DSL works its magic using `instance_eval`.
50
+ If you've been working with Ruby for a while, you'll know immediately that this means that
51
+ _inside_ a Squeel DSL block, `self` isn't the same thing that it is _outside_ the block.
52
+
53
+ This carries with it an important implication: <strong>Instance variables and instance methods
54
+ inside the block won't refer to your object's variables/methods.</strong>
55
+
56
+ Don't worry, Squeel's got you covered. Use one of the following methods to get access
57
+ to your object's methods and variables:
58
+
59
+ 1. Assign the variable locally before the DSL block, and access it as you would
60
+ normally.
61
+ 2. Supply an arity to the DSL block, as in `Person.where{|q| q.name == @my_name}`
62
+ Downside: You'll need to prefix stubs, keypaths, and functions (explained below)
63
+ with the DSL object.
64
+ 3. Wrap the method or instance variable inside the block with `my{}`.
65
+ `Person.where{name == my{some_method_to_return_a_name}}`
66
+
67
+ ### Stubs
68
+
69
+ Stubs are, for most intents and purposes, just like Symbols in a normal call to
70
+ `Relation#where` (note the need for doubling up on the curly braces here, the first ones
71
+ start the block, the second are the hash braces):
72
+
73
+ Person.where{{name => 'Ernie'}}
74
+ => SELECT "people".* FROM "people" WHERE "people"."name" = 'Ernie'
75
+
76
+ You normally wouldn't bother using the DSL in this case, as a simple hash would
77
+ suffice. However, stubs serve as a building block for keypaths, and keypaths are
78
+ very handy.
79
+
80
+ ### KeyPaths
81
+
82
+ A Squeel keypath is essentially a more concise and readable alternative to a
83
+ deeply nested hash. For instance, in standard ActiveRecord, you might join several
84
+ associations like this to perform a query:
85
+
86
+ Person.joins(:articles => {:comments => :person})
87
+ => SELECT "people".* FROM "people"
88
+ INNER JOIN "articles" ON "articles"."person_id" = "people"."id"
89
+ INNER JOIN "comments" ON "comments"."article_id" = "articles"."id"
90
+ INNER JOIN "people" "people_comments" ON "people_comments"."id" = "comments"."person_id"
91
+
92
+ With a keypath, this would look like:
93
+
94
+ Person.joins{articles.comments.person}
95
+
96
+ A keypath can exist in the context of a hash, and is normally interpreted relative to
97
+ the current level of nesting. It can be forced into an "absolute" path by anchoring it with
98
+ a ~, like:
99
+
100
+ ~articles.comments.person
101
+
102
+ This isn't quite so useful in the typical hash context, but can be very useful when it comes
103
+ to interpreting functions and the like. We'll cover those later.
104
+
105
+ ### Predicates
106
+
107
+ All of the ARel "predication" methods can be accessed inside the Squeel DSL, via
108
+ their method name, an alias, or an an operator, to create ARel predicates, which are
109
+ used in `WHERE` or `HAVING` clauses.
110
+
111
+ <table>
112
+ <tr>
113
+ <th>SQL</th>
114
+ <th>Predication</th>
115
+ <th>Operator</th>
116
+ <th>Alias</th>
117
+ </tr>
118
+ <tr>
119
+ <td>=</td>
120
+ <td>eq</td>
121
+ <td>==</td>
122
+ <td></td>
123
+ </tr>
124
+ <tr>
125
+ <td>!=</td>
126
+ <td>not_eq</td>
127
+ <td>!= (1.9 only), ^ (1.8)</td>
128
+ <td></td>
129
+ </tr>
130
+ <tr>
131
+ <td>LIKE</td>
132
+ <td>matches</td>
133
+ <td>=~</td>
134
+ <td>like</td>
135
+ </tr>
136
+ <tr>
137
+ <td>NOT LIKE</td>
138
+ <td>does_not_match</td>
139
+ <td>!~ (1.9 only)</td>
140
+ <td>not_like</td>
141
+ </tr>
142
+ <tr>
143
+ <td>&lt;</td>
144
+ <td>lt</td>
145
+ <td>&lt;</td>
146
+ <td></td>
147
+ </tr>
148
+ <tr>
149
+ <td>&lt;=</td>
150
+ <td>lteq</td>
151
+ <td>&lt;=</td>
152
+ <td>lte</td>
153
+ </tr>
154
+ <tr>
155
+ <td>></td>
156
+ <td>gt</td>
157
+ <td>></td>
158
+ <td></td>
159
+ </tr>
160
+ <tr>
161
+ <td>>=</td>
162
+ <td>gteq</td>
163
+ <td>>=</td>
164
+ <td>gte</td>
165
+ </tr>
166
+ <tr>
167
+ <td>IN</td>
168
+ <td>in</td>
169
+ <td>>></td>
170
+ <td></td>
171
+ </tr>
172
+ <tr>
173
+ <td>NOT IN</td>
174
+ <td>not_in</td>
175
+ <td>&lt;&lt;</td>
176
+ <td></td>
177
+ </tr>
178
+ </table>
179
+
180
+ Let's say we want to generate this simple query:
181
+
182
+ SELECT "people".* FROM people WHERE "people"."name" = 'Joe Blow'
183
+
184
+ All of the following will generate the above SQL:
185
+
186
+ Person.where(:name => 'Joe Blow')
187
+ Person.where{{name => 'Joe Blow'}}
188
+ Person.where{{name.eq => 'Joe Blow'}}
189
+ Person.where{name.eq 'Joe Blow'}
190
+ Person.where{name == 'Joe Blow'}
191
+
192
+ Not a very exciting example since equality is handled just fine via the
193
+ first example in standard ActiveRecord. But consider the following query:
194
+
195
+ SELECT "people".* FROM people
196
+ WHERE ("people"."name" LIKE 'Ernie%' AND "people"."salary" < 50000)
197
+ OR ("people"."name" LIKE 'Joe%' AND "people"."salary" > 100000)
198
+
199
+ To do this with standard ActiveRecord, we'd do something like:
200
+
201
+ Person.where(
202
+ '(name LIKE ? AND salary < ?) OR (name LIKE ? AND salary > ?)',
203
+ 'Ernie%', 50000, 'Joe%', 100000
204
+ )
205
+
206
+ With Squeel:
207
+
208
+ Person.where{(name =~ 'Ernie%') & (salary < 50000) | (name =~ 'Joe%') & (salary > 100000)}
209
+
210
+ Here, we're using `&` and `|` to generate `AND` and `OR`, respectively.
211
+
212
+ There are two obvious but important differences between these two code samples, and
213
+ both of them have to do with *context*.
214
+
215
+ 1. To read code with SQL interpolation, the structure of the SQL query must
216
+ first be considered, then we must cross-reference the values to be substituted
217
+ with their placeholders. This carries with it a small but perceptible (and
218
+ annoying!) context shift during which we stop thinking about the comparison being
219
+ performed, and instead play "count the arguments", or, in the case of
220
+ named/hash interpolations, "find the word". The Squeel syntax places
221
+ both sides of each comparison in proximity to one another, allowing us to
222
+ focus on what our code is doing.
223
+
224
+ 2. In the first example, we're starting off with Ruby, switching context to SQL,
225
+ and then back to Ruby, and while we spend time in SQL-land, we're stuck with
226
+ SQL syntax, whether or not it's the best way to express what we're trying to do.
227
+ With Squeel, we're writing Ruby from start to finish. And with Ruby syntax comes
228
+ flexibility to express the query in the way we see fit.
229
+
230
+ ### Predicate aliases
231
+
232
+ That last bit is important. We can mix and match predicate methods with operators
233
+ and take advantage of Ruby's operator precedence or parenthetical grouping to make
234
+ our intentions more clear, on the first read-through. And if we don't like the
235
+ way that the existing predications read, we can create our own aliases in a Squeel
236
+ configure block:
237
+
238
+ Squeel.configure do |config|
239
+ config.alias_predicate :is_less_than, :lt
240
+ end
241
+
242
+ Person.where{salary.is_less_than 50000}.to_sql
243
+ # => SELECT "people".* FROM "people" WHERE "people"."salary" < 50000
244
+
245
+ And while we're on the topic of helping you make your code more expressive...
246
+
247
+ ### Compound conditions
248
+
249
+ Let's say you want to check if a Person has a name like one of several possibilities.
250
+
251
+ names = ['Ernie%', 'Joe%', 'Mary%']
252
+ Person.where('name LIKE ? OR name LIKE ? OR name LIKE ?', *names)
253
+
254
+ But you're smart, and you know that you might want to check more or less than
255
+ 3 names, so you make your query flexible:
256
+
257
+ Person.where((['name LIKE ?'] * names.size).join(' OR '), *names)
258
+
259
+ Yeah... that's readable, all right. How about:
260
+
261
+ Person.where{name.like_any names}
262
+ # => SELECT "people".* FROM "people"
263
+ WHERE (("people"."name" LIKE 'Ernie%' OR "people"."name" LIKE 'Joe%' OR "people"."name" LIKE 'Mary%'))
264
+
265
+ I'm not sure about you, but I much prefer the latter. In short, you can add `_any` or
266
+ `_all` to any predicate method, and it would do what you expect, when given an array of
267
+ possibilities to compare against.
268
+
269
+ ### Subqueries
270
+
271
+ You can supply an `ActiveRecord::Relation` as a value for a predicate in order to use
272
+ a subquery. So, for example:
273
+
274
+ awesome_people = Person.where{awesome == true}
275
+ Article.where{author_id.in(awesome_people.select{id})}
276
+ # => SELECT "articles".* FROM "articles"
277
+ WHERE "articles"."author_id" IN (SELECT "people"."id" FROM "people" WHERE "people"."awesome" = 't')
278
+
279
+ ### Joins
280
+
281
+ Squeel adds a couple of enhancements to joins. First, keypaths can be used as shorthand for
282
+ nested association joins. Second, you can specify join types (inner and outer), and a class
283
+ in the case of a polymorphic belongs_to relationship.
284
+
285
+ Person.joins{articles.outer}
286
+ => SELECT "people".* FROM "people"
287
+ LEFT OUTER JOIN "articles" ON "articles"."person_id" = "people"."id"
288
+ Note.joins{notable(Person).outer}
289
+ => SELECT "notes".* FROM "notes"
290
+ LEFT OUTER JOIN "people"
291
+ ON "people"."id" = "notes"."notable_id"
292
+ AND "notes"."notable_type" = 'Person'
293
+
294
+ These can also be used inside keypaths:
295
+
296
+ Note.joins{notable(Person).articles}
297
+ => SELECT "notes".* FROM "notes"
298
+ INNER JOIN "people" ON "people"."id" = "notes"."notable_id"
299
+ AND "notes"."notable_type" = 'Person'
300
+ INNER JOIN "articles" ON "articles"."person_id" = "people"."id"
301
+
302
+ You can refer to these associations when constructing other parts of your query, and
303
+ they'll be automatically mapped to the proper table or table alias This is most noticeable
304
+ when using self-referential associations:
305
+
306
+ Person.joins{children.parent.children}.
307
+ where{
308
+ (children.name.like 'Ernie%') |
309
+ (children.parent.name.like 'Ernie%') |
310
+ (children.parent.children.name.like 'Ernie%')
311
+ }
312
+ => SELECT "people".* FROM "people"
313
+ INNER JOIN "people" "children_people" ON "children_people"."parent_id" = "people"."id"
314
+ INNER JOIN "people" "parents_people" ON "parents_people"."id" = "children_people"."parent_id"
315
+ INNER JOIN "people" "children_people_2" ON "children_people_2"."parent_id" = "parents_people"."id"
316
+ WHERE ((("children_people"."name" LIKE 'Ernie%'
317
+ OR "parents_people"."name" LIKE 'Ernie%')
318
+ OR "children_people_2"."name" LIKE 'Ernie%'))
319
+
320
+ Keypaths were used here for clarity, but nested hashes would work just as well.
321
+
322
+ ### Functions
323
+
324
+ You can call SQL functions just like you would call a method in Ruby...
325
+
326
+ Person.select{coalesce(name, '<no name given>')}
327
+ => SELECT coalesce("people"."name", '<no name given>') FROM "people"
328
+
329
+ ...and you can easily give it an alias:
330
+
331
+ person = Person.select{
332
+ coalesce(name, '<no name given>').as(name_with_default)
333
+ }.first
334
+ person.name_with_default # name or <no name given>, depending on data
335
+
336
+ When you use a stub, symbol, or keypath inside a function call, it'll be interpreted relative to
337
+ its place inside any nested associations:
338
+
339
+ Person.joins{articles}.group{articles.title}.having{{articles => {max(id) => id}}}
340
+ => SELECT "people".* FROM "people"
341
+ INNER JOIN "articles" ON "articles"."person_id" = "people"."id"
342
+ GROUP BY "articles"."title"
343
+ HAVING max("articles"."id") = "articles"."id"
344
+
345
+ If you want to use an attribute from a different branch of the hierarchy, use an absolute
346
+ keypath (~) as done here:
347
+
348
+ Person.joins{articles}.group{articles.title}.having{{articles => {max(~id) => id}}}
349
+ => SELECT "people".* FROM "people"
350
+ INNER JOIN "articles" ON "articles"."person_id" = "people"."id"
351
+ GROUP BY "articles"."title"
352
+ HAVING max("people"."id") = "articles"."id"
353
+
354
+ ### SQL Operators
355
+
356
+ You can use the standard mathematical operators (`+`, `-`, `*`, `/`) inside the Squeel DSL to
357
+ specify operators in the resulting SQL, or the `op` method to specify another
358
+ custom operator, such as the standard SQL concatenation operator, `||`:
359
+
360
+ p = Person.select{name.op('||', '-diddly').as(flanderized_name)}.first
361
+ p.flanderized_name
362
+ => "Aric Smith-diddly"
363
+
364
+ As you can see, just like functions, these operations can be given aliases.
365
+
366
+ ## Legacy compatibility
367
+
368
+ While the Squeel DSL is the preferred way to access advanced query functionality, you can
369
+ still enable methods on symbols to access ARel predications in a similar manner to MetaWhere:
370
+
371
+ Squeel.configure do |config|
372
+ config.load_core_extensions :symbol
373
+ end
374
+
375
+ Person.joins(:articles => :comments).
376
+ where(:articles => {:comments => {:body.matches => 'Hello!'}})
377
+ SELECT "people".* FROM "people"
378
+ INNER JOIN "articles" ON "articles"."person_id" = "people"."id"
379
+ INNER JOIN "comments" ON "comments"."article_id" = "articles"."id"
380
+ WHERE "comments"."body" LIKE 'Hello!'
381
+
382
+ This should help to smooth over the transition to the new DSL.
383
+
384
+ ## Contributions
385
+
386
+ If you'd like to support the continued development of Squeel, please consider
387
+ [making a donation](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=N7QP5N3UB76ME).
388
+
389
+ To support the project in other ways:
390
+
391
+ * Use Squeel in your apps, and let me know if you encounter anything that's broken or missing.
392
+ A failing spec is awesome. A pull request is even better!
393
+ * Spread the word on Twitter, Facebook, and elsewhere if Squeel's been useful to you. The more
394
+ people who are using the project, the quicker we can find and fix bugs!
395
+
396
+ ## Copyright
397
+
398
+ Copyright &copy; 2011 [Ernie Miller](http://twitter.com/erniemiller)
data/Rakefile ADDED
@@ -0,0 +1,19 @@
1
+ require 'bundler'
2
+ require 'rspec/core/rake_task'
3
+
4
+ Bundler::GemHelper.install_tasks
5
+
6
+ RSpec::Core::RakeTask.new(:spec) do |rspec|
7
+ rspec.rspec_opts = ['--backtrace']
8
+ end
9
+
10
+ task :default => :spec
11
+
12
+ desc "Open an irb session with Squeel and the sample data used in specs"
13
+ task :console do
14
+ require 'irb'
15
+ require 'irb/completion'
16
+ require 'console'
17
+ ARGV.clear
18
+ IRB.start
19
+ end
@@ -0,0 +1,13 @@
1
+ require 'squeel/nodes/predicate_operators'
2
+
3
+ # Hashes are "acceptable" by PredicateVisitor, so they
4
+ # can be treated like nodes for the purposes of and/or/not
5
+ # if you load these extensions.
6
+ #
7
+ # @example Load Hash extensions
8
+ # Squeel.configure do |config|
9
+ # config.load_core_extensions :hash
10
+ # end
11
+ class Hash
12
+ include Squeel::Nodes::PredicateOperators
13
+ end
@@ -0,0 +1,39 @@
1
+ require 'squeel/predicate_methods'
2
+ require 'squeel/nodes/aliasing'
3
+
4
+ # These extensions to Symbol are loaded optionally, mostly to provide
5
+ # a small amount of backwards compatibility with MetaWhere.
6
+ #
7
+ # @example Load Symbol extensions
8
+ # Squeel.configure do |config|
9
+ # config.load_core_extensions :symbol
10
+ # end
11
+ class Symbol
12
+ include Squeel::PredicateMethods
13
+ include Squeel::Nodes::Aliasing
14
+
15
+ def asc
16
+ Squeel::Nodes::Order.new self, 1
17
+ end
18
+
19
+ def desc
20
+ Squeel::Nodes::Order.new self, -1
21
+ end
22
+
23
+ def func(*args)
24
+ Squeel::Nodes::Function.new(self, args)
25
+ end
26
+
27
+ def inner
28
+ Squeel::Nodes::Join.new(self, Arel::InnerJoin)
29
+ end
30
+
31
+ def outer
32
+ Squeel::Nodes::Join.new(self, Arel::OuterJoin)
33
+ end
34
+
35
+ def of_class(klass)
36
+ Squeel::Nodes::Join.new(self, Arel::InnerJoin, klass)
37
+ end
38
+
39
+ end
@@ -0,0 +1,15 @@
1
+ module Squeel
2
+ module Adapters
3
+ module ActiveRecord
4
+ module AssociationPreload
5
+
6
+ def preload_associations(records, associations, preload_options={})
7
+ records = Array.wrap(records).compact.uniq
8
+ return if records.empty?
9
+ super(records, Visitors::SymbolVisitor.new.accept(associations), preload_options)
10
+ end
11
+
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,142 @@
1
+ module Arel
2
+
3
+ class Table
4
+ alias :table_name :name
5
+
6
+ def [] name
7
+ ::Arel::Attribute.new self, name.to_sym
8
+ end
9
+ end
10
+
11
+ module Nodes
12
+ class Node
13
+ def not
14
+ Nodes::Not.new self
15
+ end
16
+ end
17
+
18
+ remove_const :And
19
+ class And < Arel::Nodes::Node
20
+ attr_reader :children
21
+
22
+ def initialize children, right = nil
23
+ unless Array === children
24
+ children = [children, right]
25
+ end
26
+ @children = children
27
+ end
28
+
29
+ def left
30
+ children.first
31
+ end
32
+
33
+ def right
34
+ children[1]
35
+ end
36
+ end
37
+
38
+ class NamedFunction < Arel::Nodes::Function
39
+ attr_accessor :name, :distinct
40
+
41
+ include Arel::Predications
42
+
43
+ def initialize name, expr, aliaz = nil
44
+ super(expr, aliaz)
45
+ @name = name
46
+ @distinct = false
47
+ end
48
+ end
49
+
50
+ class InfixOperation < Binary
51
+ include Arel::Expressions
52
+ include Arel::Predications
53
+
54
+ attr_reader :operator
55
+
56
+ def initialize operator, left, right
57
+ super(left, right)
58
+ @operator = operator
59
+ end
60
+ end
61
+
62
+ class Multiplication < InfixOperation
63
+ def initialize left, right
64
+ super(:*, left, right)
65
+ end
66
+ end
67
+
68
+ class Division < InfixOperation
69
+ def initialize left, right
70
+ super(:/, left, right)
71
+ end
72
+ end
73
+
74
+ class Addition < InfixOperation
75
+ def initialize left, right
76
+ super(:+, left, right)
77
+ end
78
+ end
79
+
80
+ class Subtraction < InfixOperation
81
+ def initialize left, right
82
+ super(:-, left, right)
83
+ end
84
+ end
85
+ end
86
+
87
+ module Visitors
88
+ class ToSql
89
+ def column_for attr
90
+ name = attr.name.to_s
91
+ table = attr.relation.table_name
92
+
93
+ column_cache[table][name]
94
+ end
95
+
96
+ def column_cache
97
+ @column_cache ||= Hash.new do |hash, key|
98
+ hash[key] = Hash[
99
+ @engine.connection.columns(key, "#{key} Columns").map do |c|
100
+ [c.name, c]
101
+ end
102
+ ]
103
+ end
104
+ end
105
+
106
+ def visit_Arel_Nodes_InfixOperation o
107
+ "#{visit o.left} #{o.operator} #{visit o.right}"
108
+ end
109
+
110
+ def visit_Arel_Nodes_NamedFunction o
111
+ "#{o.name}(#{o.distinct ? 'DISTINCT ' : ''}#{o.expressions.map { |x|
112
+ visit x
113
+ }.join(', ')})#{o.alias ? " AS #{visit o.alias}" : ''}"
114
+ end
115
+
116
+ def visit_Arel_Nodes_And o
117
+ o.children.map { |x| visit x }.join ' AND '
118
+ end
119
+
120
+ def visit_Arel_Nodes_Not o
121
+ "NOT (#{visit o.expr})"
122
+ end
123
+
124
+ def visit_Arel_Nodes_Values o
125
+ "VALUES (#{o.expressions.zip(o.columns).map { |value, attr|
126
+ if Nodes::SqlLiteral === value
127
+ visit_Arel_Nodes_SqlLiteral value
128
+ else
129
+ quote(value, attr && column_for(attr))
130
+ end
131
+ }.join ', '})"
132
+ end
133
+ end
134
+ end
135
+
136
+ module Predications
137
+ def as other
138
+ Nodes::As.new self, Nodes::SqlLiteral.new(other)
139
+ end
140
+ end
141
+
142
+ end