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 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://code.google.com/p/ruby-sequel/issues/list]
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:
@@ -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
- Note that while the association method caches associated objects, if you
356
- retrieve access through the association dataset directly, the results
357
- will not be cached:
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
- If specified, should be a proc that accepts three three arguments: a dataset,
1227
- an alias to use for the table to graph for this association, and the alias that
1228
- was used for the current table (since you can cascade associations). Should
1229
- return a modified copy of the dataset with the association graphed into it.
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 |ds, ta, iq|
1233
- ds.graph(Album, {:artist_id=>:id, :name=>:name},
1234
- :table_alias=>ta, :implicit_qualifier=>iq)
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.
@@ -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, with virtual rows allow almost the same
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 substraction before Sequel gets access to it. In the instance evaled
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