sequel 3.22.0 → 3.23.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +28 -0
- data/README.rdoc +15 -1
- data/doc/association_basics.rdoc +121 -40
- data/doc/release_notes/3.23.0.txt +172 -0
- data/doc/virtual_rows.rdoc +2 -2
- data/lib/sequel/extensions/columns_introspection.rb +61 -0
- data/lib/sequel/extensions/migration.rb +2 -2
- data/lib/sequel/model/associations.rb +168 -32
- data/lib/sequel/model/base.rb +6 -6
- data/lib/sequel/plugins/identity_map.rb +2 -2
- data/lib/sequel/plugins/many_through_many.rb +28 -3
- data/lib/sequel/plugins/serialization_modification_detection.rb +51 -0
- data/lib/sequel/plugins/xml_serializer.rb +1 -1
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/spec_helper.rb +5 -0
- data/spec/core/spec_helper.rb +5 -0
- data/spec/extensions/columns_introspection_spec.rb +91 -0
- data/spec/extensions/many_through_many_spec.rb +21 -0
- data/spec/extensions/serialization_modification_detection_spec.rb +36 -0
- data/spec/extensions/spec_helper.rb +3 -1
- data/spec/extensions/xml_serializer_spec.rb +12 -0
- data/spec/integration/associations_test.rb +58 -0
- data/spec/integration/plugin_test.rb +15 -0
- data/spec/integration/spec_helper.rb +5 -0
- data/spec/model/associations_spec.rb +117 -0
- data/spec/model/eager_loading_spec.rb +269 -1
- data/spec/model/record_spec.rb +6 -0
- data/spec/model/spec_helper.rb +5 -0
- metadata +10 -4
data/CHANGELOG
CHANGED
@@ -1,3 +1,31 @@
|
|
1
|
+
=== 3.23.0 (2011-05-02)
|
2
|
+
|
3
|
+
* Migrate issue tracker from Google Code to GitHub Issues (jeremyevans)
|
4
|
+
|
5
|
+
* Add support for filtering by associations to model datasets (jeremyevans)
|
6
|
+
|
7
|
+
* Don't call insert_select when saving a model that doesn't select all columns of the table (jeremyevans)
|
8
|
+
|
9
|
+
* Fix bug when using :select=>[] option for a many_to_many association (jeremyevans)
|
10
|
+
|
11
|
+
* Add a columns_introspection extension that attempts to skip database queries by introspecting selected columns (jeremyevans)
|
12
|
+
|
13
|
+
* When combining old integer migrations and new timestamp migrations, make sure old integer migrations are all applied first (jeremyevans)
|
14
|
+
|
15
|
+
* Support dynamic callbacks to customize regular association loading at query time (jeremyevans)
|
16
|
+
|
17
|
+
* Support cascading of eager loading with dynamic callbacks for both eager and eager_graph (jeremyevans)
|
18
|
+
|
19
|
+
* Make the xml_serializer plugin handle namespaced models by using __ instead of / as a separator (jeremyevans)
|
20
|
+
|
21
|
+
* Allow the :eager_grapher association proc to accept a single hash instead of 3 arguments (jfirebaugh)
|
22
|
+
|
23
|
+
* Support dynamic callbacks to customize eager loading at query time (jfirebaugh, jeremyevans)
|
24
|
+
|
25
|
+
* Fix bug in the identity_map plugin for many_to_one associations when the association reflection hadn't been filled in yet (funny-falcon)
|
26
|
+
|
27
|
+
* Add serialization_modification_detection plugin for detecting changes in serialized columns (jeremyevans) (#333)
|
28
|
+
|
1
29
|
=== 3.22.0 (2011-04-01)
|
2
30
|
|
3
31
|
* Add disconnect detection to tinytds adapter, though correct behavior may require an update to tiny_tds (cult_hero)
|
data/README.rdoc
CHANGED
@@ -20,7 +20,7 @@ toolkit for Ruby.
|
|
20
20
|
* {Website}[http://sequel.rubyforge.org]
|
21
21
|
* {Blog}[http://sequel.heroku.com]
|
22
22
|
* {Source code}[http://github.com/jeremyevans/sequel]
|
23
|
-
* {Bug tracking}[http://
|
23
|
+
* {Bug tracking}[http://github.com/jeremyevans/sequel/issues]
|
24
24
|
* {Google group}[http://groups.google.com/group/sequel-talk]
|
25
25
|
* {RDoc}[http://sequel.rubyforge.org/rdoc]
|
26
26
|
|
@@ -685,6 +685,20 @@ Associations can be eagerly loaded via +eager+ and the <tt>:eager</tt> associati
|
|
685
685
|
|
686
686
|
In addition to using +eager+, you can also use +eager_graph+, which will use a single query to get the object and all associated objects. This may be necessary if you want to filter or order the result set based on columns in associated tables. It works with cascading as well, the syntax is exactly the same. Note that using eager_graph to eagerly load multiple *_to_many associations will cause the result set to be a cartesian product, so you should be very careful with your filters when using it in that case.
|
687
687
|
|
688
|
+
You can dynamically customize the eagerly loaded dataset by using using a proc. This proc is passed the dataset used for eager loading, and should return a modified copy of that dataset:
|
689
|
+
|
690
|
+
# Eagerly load only replies containing 'foo'
|
691
|
+
Post.eager(:replies=>proc{|ds| ds.filter(text.like('%foo%'))}).all
|
692
|
+
|
693
|
+
This also works when using +eager_graph+, in which case the proc is called with dataset to graph into the current dataset:
|
694
|
+
|
695
|
+
Post.eager_graph(:replies=>proc{|ds| ds.filter(text.like('%foo%'))}).all
|
696
|
+
|
697
|
+
You can dynamically customize eager loads for both +eager+ and +eager_graph+ while also cascading, by making the value a single entry hash with the proc as a key, and the cascaded associations as the value:
|
698
|
+
|
699
|
+
# Eagerly load only replies containing 'foo', and the person and tags for those replies
|
700
|
+
Post.eager(:replies=>{proc{|ds| ds.filter(text.like('%foo%'))}=>[:person, :tags]}).all
|
701
|
+
|
688
702
|
=== Extending the underlying dataset
|
689
703
|
|
690
704
|
The obvious way to add table-wide logic is to define class methods to the model class definition. That way you can define subsets of the underlying dataset, change the ordering, or perform actions on multiple records:
|
data/doc/association_basics.rdoc
CHANGED
@@ -293,32 +293,6 @@ Examples:
|
|
293
293
|
@artist.remove_album(@album)
|
294
294
|
@artist.remove_all_albums
|
295
295
|
|
296
|
-
== Dataset Method
|
297
|
-
|
298
|
-
In addition to the above methods, associations also add a instance method
|
299
|
-
ending in +_dataset+ that returns a dataset representing the objects in the associated table:
|
300
|
-
|
301
|
-
@album.artist_id
|
302
|
-
# 10
|
303
|
-
@album.artist_dataset
|
304
|
-
# SELECT * FROM artists WHERE (id = 10)
|
305
|
-
|
306
|
-
@artist.id
|
307
|
-
# 20
|
308
|
-
@artist.albums_dataset
|
309
|
-
# SELECT * FROM albums WHERE (artist_id = 20)
|
310
|
-
|
311
|
-
The association dataset is just like any other Sequel dataset, in that
|
312
|
-
it can be further filtered, ordered, etc.:
|
313
|
-
|
314
|
-
@artist.albums_dataset.
|
315
|
-
filter(:name.like('A%')).
|
316
|
-
order(:copies_sold).
|
317
|
-
limit(10)
|
318
|
-
# SELECT * FROM albums
|
319
|
-
# WHERE ((artist_id = 20) AND (name LIKE 'A%'))
|
320
|
-
# ORDER BY copies_sold LIMIT 10
|
321
|
-
|
322
296
|
== Caching
|
323
297
|
|
324
298
|
Associations are cached after being retrieved:
|
@@ -352,13 +326,112 @@ instance method:
|
|
352
326
|
@album.artists # [<Artist ...>, ...]
|
353
327
|
@album.associations[:artists] # [<Artist ...>, ...]
|
354
328
|
|
355
|
-
|
356
|
-
|
357
|
-
|
329
|
+
== Dataset Method
|
330
|
+
|
331
|
+
In addition to the above methods, associations also add a instance method
|
332
|
+
ending in +_dataset+ that returns a dataset representing the objects in the associated table:
|
333
|
+
|
334
|
+
@album.artist_id
|
335
|
+
# 10
|
336
|
+
@album.artist_dataset
|
337
|
+
# SELECT * FROM artists WHERE (id = 10)
|
338
|
+
|
339
|
+
@artist.id
|
340
|
+
# 20
|
341
|
+
@artist.albums_dataset
|
342
|
+
# SELECT * FROM albums WHERE (artist_id = 20)
|
343
|
+
|
344
|
+
The association dataset is just like any other Sequel dataset, in that
|
345
|
+
it can be further filtered, ordered, etc.:
|
346
|
+
|
347
|
+
@artist.albums_dataset.
|
348
|
+
filter(:name.like('A%')).
|
349
|
+
order(:copies_sold).
|
350
|
+
limit(10)
|
351
|
+
# SELECT * FROM albums
|
352
|
+
# WHERE ((artist_id = 20) AND (name LIKE 'A%'))
|
353
|
+
# ORDER BY copies_sold LIMIT 10
|
354
|
+
|
355
|
+
Records retrieved using the +_dataset+ method are not cached in the
|
356
|
+
associations cache.
|
358
357
|
|
359
358
|
@album.artists_dataset.all # [<Artist ...>, ...]
|
360
359
|
@album.associations[:artists] # nil
|
361
360
|
|
361
|
+
== Dynamic Association Modification
|
362
|
+
|
363
|
+
Similar to the +_dataset+ method, you can provide a block to the association
|
364
|
+
method to customize the dataset that will be used to retrieve the records. So
|
365
|
+
you can apply a filter in either of these two ways:
|
366
|
+
|
367
|
+
@artist.albums_dataset.filter(:name.like('A%'))
|
368
|
+
@artist.albums{|ds| ds.filter(:name.like('A%'))}
|
369
|
+
|
370
|
+
While they both apply the same filter, using the +_dataset+ method does not
|
371
|
+
apply any of the association callbacks or handle association reciprocals (see
|
372
|
+
below for details about callbacks and reciprocals). Using a block instead handles
|
373
|
+
all those things, and also caches its results in the associations cache (ignoring
|
374
|
+
any previously cached value).
|
375
|
+
|
376
|
+
Note that if you are using ruby 1.8.6, you can't pass a block to the association
|
377
|
+
method, you have to pass a proc as an argument:
|
378
|
+
|
379
|
+
@artist.albums(proc{|ds| ds.filter(:name.like('A%'))})
|
380
|
+
|
381
|
+
== Filtering By Associations
|
382
|
+
|
383
|
+
In addition to using the association method to get associated objects, you
|
384
|
+
can also use associated objects in filters. For example, while to get
|
385
|
+
all albums for a given artist, you would usually do:
|
386
|
+
|
387
|
+
@artist.albums
|
388
|
+
# or @artist.albums_dataset for a dataset
|
389
|
+
|
390
|
+
You can also do the following:
|
391
|
+
|
392
|
+
Album.filter(:artist=>@artist).all
|
393
|
+
# or leave off the .all for a dataset
|
394
|
+
|
395
|
+
For filtering by a single association, this isn't very useful. However, unlike
|
396
|
+
using the association method, using a filter allows you to filter by multiple
|
397
|
+
associations:
|
398
|
+
|
399
|
+
Album.filter(:artist=>@artist, :publisher=>@publisher)
|
400
|
+
|
401
|
+
This will return all albums by that artist and published by that publisher.
|
402
|
+
This isn't possible using just the association method approach, though you
|
403
|
+
can combine the approaches:
|
404
|
+
|
405
|
+
@artist.albums_dataset.filter(:publisher=>@publisher)
|
406
|
+
|
407
|
+
This doesn't just work for +many_to_one+ associations, it also works for
|
408
|
+
+one_to_one+, +one_to_many+, and +many_to_many+ associations:
|
409
|
+
|
410
|
+
Album.one_to_one :album_info
|
411
|
+
# The album related to that AlbumInfo instance
|
412
|
+
Album.filter(:album_info=>AlbumInfo[2])
|
413
|
+
|
414
|
+
Album.one_to_many :tracks
|
415
|
+
# The album related to that Track instance
|
416
|
+
Album.filter(:tracks=>Track[3])
|
417
|
+
|
418
|
+
Album.many_to_many :tags
|
419
|
+
# All albums related to that Tag instance
|
420
|
+
Album.filter(:tags=>Tag[4])
|
421
|
+
|
422
|
+
Note that for +one_to_many+ and +many_to_many+ associations, you still
|
423
|
+
use the plural form even though only a single model object is given.
|
424
|
+
You cannot use an array of model objects as the value, only a single model
|
425
|
+
object. To use separate model objects for the same association, you can
|
426
|
+
use the array form of condition specifiers:
|
427
|
+
|
428
|
+
Album.filter([[:tags, Tag[1]], [:tags, Tag[2]]])
|
429
|
+
|
430
|
+
That will return albums associated with both tag 1 and tag 2.
|
431
|
+
|
432
|
+
Note that filtering by associations only works correctly for simple
|
433
|
+
associations (ones without conditions).
|
434
|
+
|
362
435
|
== Name Collisions
|
363
436
|
|
364
437
|
Because associations create instance methods, it's possible to override
|
@@ -1221,22 +1294,30 @@ a JOIN USING or NATURAL JOIN for the graph:
|
|
1221
1294
|
==== :eager_grapher
|
1222
1295
|
|
1223
1296
|
Sets up a custom grapher to use when eager loading the objects via eager_graph.
|
1224
|
-
This is the eager_graph analogue to the :eager_loader option.
|
1297
|
+
This is the eager_graph analogue to the :eager_loader option. This isn't generally
|
1298
|
+
needed, as one of the other eager_graph related association options is usually sufficient.
|
1299
|
+
|
1300
|
+
If specified, should be a proc that accepts one or three three arguments.
|
1301
|
+
If the proc takes one argument, it will be given a hash with the following keys:
|
1225
1302
|
|
1226
|
-
|
1227
|
-
|
1228
|
-
was used for the current table (since you can cascade associations).
|
1229
|
-
|
1303
|
+
:self :: The dataset that is doing the eager loading
|
1304
|
+
:table_alias :: An alias to use for the table to graph for this association.
|
1305
|
+
:implicit_qualifier :: The alias that was used for the current table (since you can cascade associations).
|
1306
|
+
:callback :: A callback proc used to dynamically modify the dataset to graph into the
|
1307
|
+
current dataset, before such graphing is done. This is nil if no callback
|
1308
|
+
proc is used.
|
1309
|
+
|
1310
|
+
If the proc takes three arguments, it gets passed the :self, :association_alias,
|
1311
|
+
and :table_alias values. The 3 argument procs are allowed for backwards
|
1312
|
+
compatibility, and it is recommended to use the 1 argument proc format
|
1313
|
+
for new code.
|
1230
1314
|
|
1231
1315
|
Artist.one_to_many :self_title_albums, :class=>:Album,
|
1232
|
-
:eager_grapher=>(proc do |
|
1233
|
-
|
1234
|
-
:table_alias=>
|
1316
|
+
:eager_grapher=>(proc do |eo|
|
1317
|
+
eo[:self].graph(Album, {:artist_id=>:id, :name=>:name},
|
1318
|
+
:table_alias=>eo[:table_alias], :implicit_qualifier=>eo[:implicit_qualifier])
|
1235
1319
|
end)
|
1236
1320
|
|
1237
|
-
This isn't generally needed, as one of the other eager_graph related
|
1238
|
-
association options is usually sufficient.
|
1239
|
-
|
1240
1321
|
==== :order_eager_graph
|
1241
1322
|
|
1242
1323
|
Whether to add the order to the dataset's order when graphing via eager_graph.
|
@@ -0,0 +1,172 @@
|
|
1
|
+
= New Features
|
2
|
+
|
3
|
+
* Sequel now allows dynamic customization for eager loading.
|
4
|
+
Previously, the parameters for eager loading were fixed at
|
5
|
+
association creation time. Now, they can be modified at query
|
6
|
+
time. To dynamically modify an eager load, you use a hash with
|
7
|
+
the proc as the value. For example, if you have this code:
|
8
|
+
|
9
|
+
Artist.eager(:albums)
|
10
|
+
|
11
|
+
And you only want to eagerly load albums where the id is greater
|
12
|
+
than or equal to some number provided by the user, you do:
|
13
|
+
|
14
|
+
min = params[:min]
|
15
|
+
Artist.eager(:albums=>proc{|ds| ds.where{id > min}})
|
16
|
+
|
17
|
+
This also works when eager loading via eager_graph:
|
18
|
+
|
19
|
+
Artist.eager_graph(:albums=>proc{|ds| ds.where{id > min}})
|
20
|
+
|
21
|
+
For eager_graph, the dataset is the dataset to graph into the
|
22
|
+
current dataset, and filtering it will result in an SQL query
|
23
|
+
that joins to a subquery.
|
24
|
+
|
25
|
+
You can also use dynamic customization while cascading to also
|
26
|
+
eagerly load dependent associations, by making the hash value
|
27
|
+
a single entry hash with a proc key and the value being the
|
28
|
+
dependent associations to eagerly load. For example, if you want
|
29
|
+
to eagerly load tracks for those albums:
|
30
|
+
|
31
|
+
Artist.eager(:albums=>{proc{|ds| ds.where{id > min}}=>:tracks})
|
32
|
+
|
33
|
+
* Sequel also now allows dynamic customization for regular
|
34
|
+
association loading. Previously, this was possible by using the
|
35
|
+
association's dataset:
|
36
|
+
|
37
|
+
albums = artist.albums_dataset.filter{id > min}
|
38
|
+
|
39
|
+
However, then there was no handling of caching, callbacks, or
|
40
|
+
reciprocals. For example:
|
41
|
+
|
42
|
+
albums.each{|album| album.artist}
|
43
|
+
|
44
|
+
Would issue one query per album to get the artist, because the
|
45
|
+
reciprocal association was not set. Now you can provide a
|
46
|
+
block to the association method:
|
47
|
+
|
48
|
+
albums = artist.albums{|ds| ds.filter{id > min}}
|
49
|
+
|
50
|
+
This block is called with the dataset used to retrieve the
|
51
|
+
associated objects, and should return a modified version of that
|
52
|
+
dataset.
|
53
|
+
|
54
|
+
Note that ruby 1.8.6 doesn't allow blocks to take block arguments,
|
55
|
+
so you have to pass the block as a separate proc argument to the
|
56
|
+
association method if you are still using 1.8.6.
|
57
|
+
|
58
|
+
* Sequel now supports filtering by associations. This wasn't
|
59
|
+
previously supported as filtering is a dataset level feature and
|
60
|
+
associations are a model level feature, and datasets do not depend
|
61
|
+
on models. Now, model datasets have the ability to filter by
|
62
|
+
associations. For example, to get all albums for a given artist,
|
63
|
+
you could do:
|
64
|
+
|
65
|
+
artist = Artist[1]
|
66
|
+
Album.filter(:artist=>artist)
|
67
|
+
|
68
|
+
Since the above can also be accomplished with:
|
69
|
+
|
70
|
+
artist.albums
|
71
|
+
|
72
|
+
this may not seem like a big improvement, but it allows you to
|
73
|
+
filter on multiple associations simultaneously:
|
74
|
+
|
75
|
+
Album.filter(:artist=>artist, :publisher=>publisher)
|
76
|
+
|
77
|
+
For simple many_to_one associations, the above is just a simpler
|
78
|
+
way to do:
|
79
|
+
|
80
|
+
Album.filter(:artist_id=>artist.id, :publisher_id=>publisher.id)
|
81
|
+
|
82
|
+
Sequel supports this for all association types, including
|
83
|
+
many_to_many and many_through_many, where a subquery is used, and
|
84
|
+
it also works when composite key associations are used:
|
85
|
+
|
86
|
+
Album.filter(:artist=>artist, :tags=>tag)
|
87
|
+
|
88
|
+
This will give you the albums for that artist that are also tagged
|
89
|
+
with that tag. To provide multiple values for the same
|
90
|
+
association, mostly useful for many_to_many associations, you can
|
91
|
+
either use separate filter calls or specify the conditions as an
|
92
|
+
array:
|
93
|
+
|
94
|
+
Album.filter(:tags=>tag1).filter(:tags=>tag2)
|
95
|
+
Album.filter([[:tags, tag1], [:tags, tag2]])
|
96
|
+
|
97
|
+
* A columns_introspection extension has been added that makes
|
98
|
+
datasets attempt to guess their columns in some cases instead of
|
99
|
+
issuing a database query. This can improve performance in cases
|
100
|
+
where the columns are needed implicitly, such as graphing. After
|
101
|
+
loading the extension, you can enable the support for specific
|
102
|
+
datasets by extending them with Sequel::ColumnIntrospection. To
|
103
|
+
enable introspection for all datasets, use:
|
104
|
+
|
105
|
+
Sequel::Dataset.introspect_all_columns
|
106
|
+
|
107
|
+
* A serialization_modification_detection plugin has been added.
|
108
|
+
Previously, Sequel could not detect modifications made to
|
109
|
+
serialized objects. It could detect modification if you assigned
|
110
|
+
a new value:
|
111
|
+
|
112
|
+
model.hash_column = model.hash_column.merge(:foo=>:bar)
|
113
|
+
|
114
|
+
but not if you just modified the object directly:
|
115
|
+
|
116
|
+
model.hash_columns[:foo] = :bar
|
117
|
+
|
118
|
+
With this plugin, such modifications can be detected, at a
|
119
|
+
potentially significant performance cost.
|
120
|
+
|
121
|
+
= Other Improvements
|
122
|
+
|
123
|
+
* When using a migration directory containing both older integer
|
124
|
+
migrations and newer timestamp migrations, where some integer
|
125
|
+
migrations have not been applied, make sure to apply the remaining
|
126
|
+
integer migrations before the timestamp migrations. Previously,
|
127
|
+
they could be applied out of order due to a lexicographic sort
|
128
|
+
being used instead of a numeric sort.
|
129
|
+
|
130
|
+
* If a model does not select all columns from its table, the
|
131
|
+
insert_select optimization is no longer used. Previously,
|
132
|
+
creating a new model object for such a model could result in the
|
133
|
+
object containing columns that the model does not select.
|
134
|
+
|
135
|
+
* You can now use :select=>[] as an option for many_to_many
|
136
|
+
associations to select all columns from both the associated
|
137
|
+
table and the join table. Previously, this raised an error and
|
138
|
+
required you do :select=>'*'.lit as a workaround. The default
|
139
|
+
remains to select all columns in the associated table and none
|
140
|
+
from the join table.
|
141
|
+
|
142
|
+
* The xml_serializer plugin now handles namespaced models by
|
143
|
+
using __ instead of / as the namespace separator. Previously, /
|
144
|
+
was used and caused problems as it is not valid XML.
|
145
|
+
|
146
|
+
* The :eager_grapher association option can now accept a proc that
|
147
|
+
takes a single hash of options instead of a fixed 3 arguments.
|
148
|
+
This is the recommended way going forward of writing custom
|
149
|
+
:eager_graphers, and all of the internal ones have been converted.
|
150
|
+
The previous way of using 3 arguments is still supported.
|
151
|
+
|
152
|
+
* A bug in the identity_map plugin for many_to_one associations
|
153
|
+
without full association reflection information has been fixed.
|
154
|
+
|
155
|
+
* Sequel is now using GitHub Issues for issue tracking. Old issues
|
156
|
+
have been migrated from Google Code.
|
157
|
+
|
158
|
+
= Backwards Compatibility
|
159
|
+
|
160
|
+
* The filter by associations support breaks backward compatibilty for
|
161
|
+
users who previously added an sql_literal instance method to
|
162
|
+
Sequel::Model. Usually, that was done to for reasons similar to
|
163
|
+
but inferior than the filter by association support. The following
|
164
|
+
code can be used as a temporary workaround until you can modify
|
165
|
+
your program to use the new filter by associations support:
|
166
|
+
|
167
|
+
Sequel::Model::Associations::DatasetMethods.
|
168
|
+
send(:remove_method, :complex_expression_sql)
|
169
|
+
|
170
|
+
* The private Sequel::Model#_load_associated_objects method now takes
|
171
|
+
an additional, optional options hash. Plugins that override that
|
172
|
+
method need to be modified.
|
data/doc/virtual_rows.rdoc
CHANGED
@@ -40,7 +40,7 @@ available. For example, you are no longer able to do:
|
|
40
40
|
# WHERE a AND (b OR NOT c)
|
41
41
|
|
42
42
|
Because Symbol#&, Symbol#| and Symbol#~ are not defined when the core
|
43
|
-
extensions are turned off. However,
|
43
|
+
extensions are turned off. However, virtual rows allow almost the same
|
44
44
|
syntax even without the core extensions:
|
45
45
|
|
46
46
|
dataset.filter{a & (b | ~c)}
|
@@ -87,7 +87,7 @@ and second is the difference between the the use of "a". In the regular proc,
|
|
87
87
|
you couldn't call c without an explicit receiver in the proc, unless the self of the
|
88
88
|
surrounding scope responded to it. For a, note how ruby calls the method on
|
89
89
|
the receiver of the surrounding scope in the regular proc, which returns an integer,
|
90
|
-
and does the
|
90
|
+
and does the substitution before Sequel gets access to it. In the instance evaled
|
91
91
|
proc, calling a without a receiver calls the a method on the VirtualRow instance.
|
92
92
|
For b, note that it operates the same in both cases, as it is a local variable.
|
93
93
|
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# The columns_introspection extension attempts to introspect the
|
2
|
+
# selected columns for a dataset before issuing a query. If it
|
3
|
+
# thinks it can guess correctly at the columns the query will use,
|
4
|
+
# it will return the columns without issuing a database query.
|
5
|
+
# This method is not fool-proof, it's possible that some databases
|
6
|
+
# will use column names that Sequel does not expect.
|
7
|
+
#
|
8
|
+
# To enable this for a single dataset, extend the dataset with
|
9
|
+
# Sequel::ColumnIntrospection. To enable this for all datasets, run:
|
10
|
+
#
|
11
|
+
# Sequel::Dataset.introspect_all_columns
|
12
|
+
|
13
|
+
module Sequel
|
14
|
+
module ColumnsIntrospection
|
15
|
+
# Attempt to guess the columns that will be returned
|
16
|
+
# if there are columns selected, in order to skip a database
|
17
|
+
# query to retrieve the columns. This should work with
|
18
|
+
# Symbols, SQL::Identifiers, SQL::QualifiedIdentifiers, and
|
19
|
+
# SQL::AliasedExpressions.
|
20
|
+
def columns
|
21
|
+
return @columns if @columns
|
22
|
+
return columns_without_introspection unless cols = opts[:select] and !cols.empty?
|
23
|
+
probable_columns = cols.map{|c| probable_column_name(c)}
|
24
|
+
if probable_columns.all?
|
25
|
+
@columns = probable_columns
|
26
|
+
else
|
27
|
+
columns_without_introspection
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
# Return the probable name of the column, or nil if one
|
34
|
+
# cannot be determined.
|
35
|
+
def probable_column_name(c)
|
36
|
+
case c
|
37
|
+
when Symbol
|
38
|
+
_, c, a = split_symbol(c)
|
39
|
+
(a || c).to_sym
|
40
|
+
when SQL::Identifier
|
41
|
+
c.value.to_sym
|
42
|
+
when SQL::QualifiedIdentifier
|
43
|
+
col = c.column
|
44
|
+
col.is_a?(SQL::Identifier) ? col.value.to_sym : col.to_sym
|
45
|
+
when SQL::AliasedExpression
|
46
|
+
a = c.aliaz
|
47
|
+
a.is_a?(SQL::Identifier) ? a.value.to_sym : a.to_sym
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class Dataset
|
53
|
+
alias columns_without_introspection columns
|
54
|
+
|
55
|
+
# Enable column introspection for every dataset.
|
56
|
+
def self.introspect_all_columns
|
57
|
+
include ColumnsIntrospection
|
58
|
+
remove_method(:columns) if instance_methods(false).map{|x| x.to_s}.include?('columns')
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|