squeel_rbg 0.8.2

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.
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