squeel_rbg 0.8.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/.yardopts +3 -0
- data/Gemfile +13 -0
- data/LICENSE +20 -0
- data/README.md +398 -0
- data/Rakefile +19 -0
- data/lib/core_ext/hash.rb +13 -0
- data/lib/core_ext/symbol.rb +39 -0
- data/lib/squeel/adapters/active_record/3.0/association_preload.rb +15 -0
- data/lib/squeel/adapters/active_record/3.0/compat.rb +142 -0
- data/lib/squeel/adapters/active_record/3.0/context.rb +66 -0
- data/lib/squeel/adapters/active_record/3.0/join_association.rb +54 -0
- data/lib/squeel/adapters/active_record/3.0/join_dependency.rb +84 -0
- data/lib/squeel/adapters/active_record/3.0/relation.rb +327 -0
- data/lib/squeel/adapters/active_record/context.rb +66 -0
- data/lib/squeel/adapters/active_record/join_association.rb +44 -0
- data/lib/squeel/adapters/active_record/join_dependency.rb +83 -0
- data/lib/squeel/adapters/active_record/preloader.rb +21 -0
- data/lib/squeel/adapters/active_record/relation.rb +351 -0
- data/lib/squeel/adapters/active_record.rb +28 -0
- data/lib/squeel/configuration.rb +54 -0
- data/lib/squeel/constants.rb +24 -0
- data/lib/squeel/context.rb +67 -0
- data/lib/squeel/dsl.rb +86 -0
- data/lib/squeel/nodes/aliasing.rb +13 -0
- data/lib/squeel/nodes/and.rb +9 -0
- data/lib/squeel/nodes/as.rb +14 -0
- data/lib/squeel/nodes/binary.rb +32 -0
- data/lib/squeel/nodes/function.rb +66 -0
- data/lib/squeel/nodes/join.rb +113 -0
- data/lib/squeel/nodes/key_path.rb +192 -0
- data/lib/squeel/nodes/nary.rb +45 -0
- data/lib/squeel/nodes/not.rb +9 -0
- data/lib/squeel/nodes/operation.rb +32 -0
- data/lib/squeel/nodes/operators.rb +43 -0
- data/lib/squeel/nodes/or.rb +9 -0
- data/lib/squeel/nodes/order.rb +53 -0
- data/lib/squeel/nodes/predicate.rb +71 -0
- data/lib/squeel/nodes/predicate_operators.rb +29 -0
- data/lib/squeel/nodes/stub.rb +125 -0
- data/lib/squeel/nodes/unary.rb +28 -0
- data/lib/squeel/nodes.rb +17 -0
- data/lib/squeel/predicate_methods.rb +14 -0
- data/lib/squeel/version.rb +3 -0
- data/lib/squeel/visitors/attribute_visitor.rb +191 -0
- data/lib/squeel/visitors/base.rb +112 -0
- data/lib/squeel/visitors/predicate_visitor.rb +319 -0
- data/lib/squeel/visitors/symbol_visitor.rb +48 -0
- data/lib/squeel/visitors.rb +3 -0
- data/lib/squeel.rb +28 -0
- data/lib/squeel_rbg.rb +5 -0
- data/spec/blueprints/articles.rb +5 -0
- data/spec/blueprints/comments.rb +5 -0
- data/spec/blueprints/notes.rb +3 -0
- data/spec/blueprints/people.rb +4 -0
- data/spec/blueprints/tags.rb +3 -0
- data/spec/console.rb +22 -0
- data/spec/core_ext/symbol_spec.rb +75 -0
- data/spec/helpers/squeel_helper.rb +21 -0
- data/spec/spec_helper.rb +66 -0
- data/spec/squeel/adapters/active_record/context_spec.rb +44 -0
- data/spec/squeel/adapters/active_record/join_association_spec.rb +18 -0
- data/spec/squeel/adapters/active_record/join_dependency_spec.rb +66 -0
- data/spec/squeel/adapters/active_record/relation_spec.rb +627 -0
- data/spec/squeel/dsl_spec.rb +92 -0
- data/spec/squeel/nodes/function_spec.rb +149 -0
- data/spec/squeel/nodes/join_spec.rb +47 -0
- data/spec/squeel/nodes/key_path_spec.rb +100 -0
- data/spec/squeel/nodes/operation_spec.rb +149 -0
- data/spec/squeel/nodes/operators_spec.rb +87 -0
- data/spec/squeel/nodes/order_spec.rb +30 -0
- data/spec/squeel/nodes/predicate_operators_spec.rb +88 -0
- data/spec/squeel/nodes/predicate_spec.rb +50 -0
- data/spec/squeel/nodes/stub_spec.rb +198 -0
- data/spec/squeel/visitors/attribute_visitor_spec.rb +142 -0
- data/spec/squeel/visitors/predicate_visitor_spec.rb +342 -0
- data/spec/squeel/visitors/symbol_visitor_spec.rb +42 -0
- data/spec/support/schema.rb +104 -0
- data/squeel.gemspec +43 -0
- metadata +246 -0
data/.gitignore
ADDED
data/.yardopts
ADDED
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><</td>
|
144
|
+
<td>lt</td>
|
145
|
+
<td><</td>
|
146
|
+
<td></td>
|
147
|
+
</tr>
|
148
|
+
<tr>
|
149
|
+
<td><=</td>
|
150
|
+
<td>lteq</td>
|
151
|
+
<td><=</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><<</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 © 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
|