sequel 3.37.0 → 3.38.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +56 -0
- data/README.rdoc +82 -58
- data/Rakefile +6 -5
- data/bin/sequel +1 -1
- data/doc/active_record.rdoc +67 -52
- data/doc/advanced_associations.rdoc +33 -48
- data/doc/association_basics.rdoc +41 -51
- data/doc/cheat_sheet.rdoc +21 -21
- data/doc/core_extensions.rdoc +374 -0
- data/doc/dataset_basics.rdoc +5 -5
- data/doc/dataset_filtering.rdoc +47 -43
- data/doc/mass_assignment.rdoc +1 -1
- data/doc/migration.rdoc +4 -5
- data/doc/model_hooks.rdoc +3 -3
- data/doc/object_model.rdoc +31 -25
- data/doc/opening_databases.rdoc +19 -5
- data/doc/prepared_statements.rdoc +2 -2
- data/doc/querying.rdoc +109 -52
- data/doc/reflection.rdoc +6 -6
- data/doc/release_notes/3.38.0.txt +234 -0
- data/doc/schema_modification.rdoc +22 -13
- data/doc/sharding.rdoc +8 -9
- data/doc/sql.rdoc +154 -112
- data/doc/testing.rdoc +47 -7
- data/doc/thread_safety.rdoc +1 -1
- data/doc/transactions.rdoc +1 -1
- data/doc/validations.rdoc +1 -1
- data/doc/virtual_rows.rdoc +29 -43
- data/lib/sequel/adapters/do/postgres.rb +1 -4
- data/lib/sequel/adapters/jdbc.rb +14 -3
- data/lib/sequel/adapters/jdbc/db2.rb +9 -0
- data/lib/sequel/adapters/jdbc/derby.rb +41 -4
- data/lib/sequel/adapters/jdbc/jtds.rb +11 -0
- data/lib/sequel/adapters/jdbc/postgresql.rb +3 -6
- data/lib/sequel/adapters/mock.rb +10 -4
- data/lib/sequel/adapters/postgres.rb +1 -28
- data/lib/sequel/adapters/shared/mssql.rb +23 -13
- data/lib/sequel/adapters/shared/postgres.rb +46 -0
- data/lib/sequel/adapters/swift.rb +21 -13
- data/lib/sequel/adapters/swift/mysql.rb +1 -0
- data/lib/sequel/adapters/swift/postgres.rb +4 -5
- data/lib/sequel/adapters/swift/sqlite.rb +2 -1
- data/lib/sequel/adapters/tinytds.rb +14 -2
- data/lib/sequel/adapters/utils/pg_types.rb +5 -0
- data/lib/sequel/core.rb +29 -17
- data/lib/sequel/database/query.rb +1 -1
- data/lib/sequel/database/schema_generator.rb +3 -0
- data/lib/sequel/dataset/actions.rb +5 -6
- data/lib/sequel/dataset/query.rb +7 -7
- data/lib/sequel/dataset/sql.rb +5 -18
- data/lib/sequel/extensions/core_extensions.rb +8 -12
- data/lib/sequel/extensions/pg_array.rb +59 -33
- data/lib/sequel/extensions/pg_array_ops.rb +32 -4
- data/lib/sequel/extensions/pg_auto_parameterize.rb +1 -1
- data/lib/sequel/extensions/pg_hstore.rb +32 -17
- data/lib/sequel/extensions/pg_hstore_ops.rb +32 -3
- data/lib/sequel/extensions/pg_inet.rb +1 -2
- data/lib/sequel/extensions/pg_interval.rb +0 -1
- data/lib/sequel/extensions/pg_json.rb +41 -23
- data/lib/sequel/extensions/pg_range.rb +36 -11
- data/lib/sequel/extensions/pg_range_ops.rb +32 -4
- data/lib/sequel/extensions/pg_row.rb +572 -0
- data/lib/sequel/extensions/pg_row_ops.rb +164 -0
- data/lib/sequel/extensions/query.rb +3 -3
- data/lib/sequel/extensions/schema_dumper.rb +7 -8
- data/lib/sequel/extensions/select_remove.rb +1 -1
- data/lib/sequel/model/base.rb +1 -0
- data/lib/sequel/no_core_ext.rb +1 -1
- data/lib/sequel/plugins/pg_row.rb +121 -0
- data/lib/sequel/plugins/pg_typecast_on_load.rb +65 -0
- data/lib/sequel/plugins/validation_helpers.rb +31 -0
- data/lib/sequel/sql.rb +64 -44
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/mssql_spec.rb +37 -12
- data/spec/adapters/mysql_spec.rb +39 -75
- data/spec/adapters/oracle_spec.rb +11 -11
- data/spec/adapters/postgres_spec.rb +414 -237
- data/spec/adapters/spec_helper.rb +1 -1
- data/spec/adapters/sqlite_spec.rb +14 -14
- data/spec/core/database_spec.rb +6 -6
- data/spec/core/dataset_spec.rb +169 -205
- data/spec/core/expression_filters_spec.rb +182 -295
- data/spec/core/object_graph_spec.rb +6 -6
- data/spec/core/schema_spec.rb +14 -14
- data/spec/core/spec_helper.rb +1 -0
- data/spec/{extensions/core_extensions_spec.rb → core_extensions_spec.rb} +208 -14
- data/spec/extensions/columns_introspection_spec.rb +5 -5
- data/spec/extensions/hook_class_methods_spec.rb +28 -36
- data/spec/extensions/many_through_many_spec.rb +4 -4
- data/spec/extensions/pg_array_ops_spec.rb +15 -7
- data/spec/extensions/pg_array_spec.rb +81 -48
- data/spec/extensions/pg_auto_parameterize_spec.rb +2 -2
- data/spec/extensions/pg_hstore_ops_spec.rb +13 -9
- data/spec/extensions/pg_hstore_spec.rb +66 -65
- data/spec/extensions/pg_inet_spec.rb +2 -4
- data/spec/extensions/pg_interval_spec.rb +2 -3
- data/spec/extensions/pg_json_spec.rb +20 -18
- data/spec/extensions/pg_range_ops_spec.rb +11 -4
- data/spec/extensions/pg_range_spec.rb +30 -7
- data/spec/extensions/pg_row_ops_spec.rb +48 -0
- data/spec/extensions/pg_row_plugin_spec.rb +45 -0
- data/spec/extensions/pg_row_spec.rb +323 -0
- data/spec/extensions/pg_typecast_on_load_spec.rb +58 -0
- data/spec/extensions/query_literals_spec.rb +11 -11
- data/spec/extensions/query_spec.rb +3 -3
- data/spec/extensions/schema_dumper_spec.rb +20 -4
- data/spec/extensions/schema_spec.rb +18 -41
- data/spec/extensions/select_remove_spec.rb +4 -4
- data/spec/extensions/spec_helper.rb +4 -8
- data/spec/extensions/to_dot_spec.rb +5 -5
- data/spec/extensions/validation_class_methods_spec.rb +28 -16
- data/spec/integration/associations_test.rb +20 -20
- data/spec/integration/dataset_test.rb +98 -98
- data/spec/integration/eager_loader_test.rb +13 -27
- data/spec/integration/plugin_test.rb +5 -5
- data/spec/integration/prepared_statement_test.rb +22 -13
- data/spec/integration/schema_test.rb +28 -18
- data/spec/integration/spec_helper.rb +1 -1
- data/spec/integration/timezone_test.rb +2 -2
- data/spec/integration/type_test.rb +15 -6
- data/spec/model/association_reflection_spec.rb +1 -1
- data/spec/model/associations_spec.rb +4 -4
- data/spec/model/base_spec.rb +5 -5
- data/spec/model/eager_loading_spec.rb +15 -15
- data/spec/model/model_spec.rb +32 -32
- data/spec/model/record_spec.rb +16 -0
- data/spec/model/spec_helper.rb +2 -6
- data/spec/model/validations_spec.rb +1 -1
- metadata +16 -4
data/CHANGELOG
CHANGED
@@ -1,3 +1,59 @@
|
|
1
|
+
=== 3.38.0 (2012-08-01)
|
2
|
+
|
3
|
+
* Sequel now recognizes the double(x, y) and double(x, y) unsigned MySQL types (Slike9, jeremyevans) (#528)
|
4
|
+
|
5
|
+
* The swift subadapters now require swift-db-* instead of swift itself (deepfryed, jeremyevans) (#526)
|
6
|
+
|
7
|
+
* Add :textsize option to tinytds adapter to override the default TEXTSIZE (jeremyevans, wardrop) (#525)
|
8
|
+
|
9
|
+
* Support an output identifier method in the swift adapter (jeremyevans)
|
10
|
+
|
11
|
+
* Add Model#to_hash as an alias to Model#values (jeremyevans)
|
12
|
+
|
13
|
+
* When loading multiple pg_* extensions via Database#extension, only reset the conversion procs once (jeremyevans)
|
14
|
+
|
15
|
+
* Don't allow model typecasting from string to postgres array, hstore, or composite types (jeremyevans)
|
16
|
+
|
17
|
+
* Add pg_typecast_on_load plugin for converting advanced PostgreSQL types on load the {jdbc,do,swift}/postgres adapters (jeremyevans)
|
18
|
+
|
19
|
+
* Make all adapters that connect to PostgreSQL store type conversion procs (jeremyevans)
|
20
|
+
|
21
|
+
* Add type oid to column schema on PostgreSQL (jeremyevans)
|
22
|
+
|
23
|
+
* Add pg_row plugin, for using Sequel::Model classes to represent PostgreSQL row-valued/composite types (jeremyevans)
|
24
|
+
|
25
|
+
* Add pg_row_ops extension for DSL support for PostgreSQL row-valued/composite types (jeremyevans)
|
26
|
+
|
27
|
+
* Add pg_row extension for dealing with PostgreSQL row-valued/composite types (jeremyevans)
|
28
|
+
|
29
|
+
* Allow custom registered array types in the pg_array extension to be Database instance specific (jeremyevans)
|
30
|
+
|
31
|
+
* Remove Sequel::SQL::IdentifierMethods (jeremyevans)
|
32
|
+
|
33
|
+
* Don't have the schema_dumper extension produce code that relies on the core_extensions (jeremyevans)
|
34
|
+
|
35
|
+
* Fix dropping of columns with constraints on Microsoft SQL Server (mluu, jeremyevans) (#515, #518)
|
36
|
+
|
37
|
+
* Don't have pg_* extensions add methods to core classes unless the core_extensions extension is loaded (jeremyevans)
|
38
|
+
|
39
|
+
* Use real boolean literals on derby 10.7+ (jeremyevans, matthauck) (#514)
|
40
|
+
|
41
|
+
* Work around JRuby 1.6 ruby 1.9 mode bug in Time#nsec for Time prepared statement arguments on jdbc (jeremyevans)
|
42
|
+
|
43
|
+
* Handle blob prepared statement arguments on jdbc/db2 and jdbc/oracle (jeremyevans)
|
44
|
+
|
45
|
+
* Handle blob values in the swift adapter (jeremyevans)
|
46
|
+
|
47
|
+
* Handle better nil prepared statement arguments on jdbc (jeremyevans) (#513)
|
48
|
+
|
49
|
+
* Make SQL::Blob objects handle as, cast, and lit methods even if the core extensions are not loaded (jeremyevans)
|
50
|
+
|
51
|
+
* Make #* with no arguments produce a ColumnAll for Identifier and QualifiedIdentifier (jeremyevans)
|
52
|
+
|
53
|
+
* Sequel.expr(:symbol) now returns Identifier, QualifiedIdentifier, or AliasedExpression instead of Wrapper (jeremyevans)
|
54
|
+
|
55
|
+
* Treat clob columns as string instead of blob on Derby (jeremyevans) (#509)
|
56
|
+
|
1
57
|
=== 3.37.0 (2012-07-02)
|
2
58
|
|
3
59
|
* Allow specifying eager_graph alias base on a per-call basis using an AliasedExpression (jeremyevans)
|
data/README.rdoc
CHANGED
@@ -171,7 +171,7 @@ You can also iterate through records one at a time using +each+:
|
|
171
171
|
|
172
172
|
Or perform more advanced stuff:
|
173
173
|
|
174
|
-
names_and_dates = posts.map
|
174
|
+
names_and_dates = posts.map([:name, :date])
|
175
175
|
old_posts, recent_posts = posts.partition{|r| r[:date] < Date.today - 7}
|
176
176
|
|
177
177
|
You can also retrieve the first record in a dataset:
|
@@ -191,29 +191,29 @@ If the dataset is ordered, you can also ask for the last record:
|
|
191
191
|
|
192
192
|
=== Filtering Records
|
193
193
|
|
194
|
-
An easy way to filter records is to provide a hash of values to match to +
|
194
|
+
An easy way to filter records is to provide a hash of values to match to +where+:
|
195
195
|
|
196
|
-
my_posts = posts.
|
196
|
+
my_posts = posts.where(:category => 'ruby', :author => 'david')
|
197
197
|
# WHERE category = 'ruby' AND author = 'david'
|
198
198
|
|
199
199
|
You can also specify ranges:
|
200
200
|
|
201
|
-
my_posts = posts.
|
201
|
+
my_posts = posts.where(:stamp => (Date.today - 14)..(Date.today - 7))
|
202
202
|
# WHERE stamp >= '2010-06-30' AND stamp <= '2010-07-07'
|
203
203
|
|
204
204
|
Or arrays of values:
|
205
205
|
|
206
|
-
my_posts = posts.
|
206
|
+
my_posts = posts.where(:category => ['ruby', 'postgres', 'linux'])
|
207
207
|
# WHERE category IN ('ruby', 'postgres', 'linux')
|
208
208
|
|
209
209
|
Sequel also accepts expressions:
|
210
210
|
|
211
|
-
my_posts = posts.
|
211
|
+
my_posts = posts.where{stamp > Date.today << 1}
|
212
212
|
# WHERE stamp > '2010-06-14'
|
213
213
|
|
214
214
|
Some adapters will also let you specify Regexps:
|
215
215
|
|
216
|
-
my_posts = posts.
|
216
|
+
my_posts = posts.where(:category => /ruby/i)
|
217
217
|
# WHERE category ~* 'ruby'
|
218
218
|
|
219
219
|
You can also use an inverse filter via +exclude+:
|
@@ -223,22 +223,21 @@ You can also use an inverse filter via +exclude+:
|
|
223
223
|
|
224
224
|
You can also specify a custom WHERE clause using a string:
|
225
225
|
|
226
|
-
posts.
|
226
|
+
posts.where('stamp IS NOT NULL')
|
227
227
|
# WHERE stamp IS NOT NULL
|
228
228
|
|
229
229
|
You can use parameters in your string, as well:
|
230
230
|
|
231
231
|
author_name = 'JKR'
|
232
|
-
posts.
|
232
|
+
posts.where('(stamp < ?) AND (author != ?)', Date.today - 3, author_name)
|
233
233
|
# WHERE (stamp < '2010-07-11') AND (author != 'JKR')
|
234
|
-
posts.filter{(stamp < Date.today - 3) & ~{:author => author_name}} # same as above
|
235
234
|
|
236
235
|
Datasets can also be used as subqueries:
|
237
236
|
|
238
|
-
DB[:items].
|
237
|
+
DB[:items].where('price > ?', DB[:items].select{avg(price) + 100})
|
239
238
|
# WHERE price > (SELECT avg(price) + 100 FROM items)
|
240
239
|
|
241
|
-
After filtering you can retrieve the matching records by using any of the retrieval methods:
|
240
|
+
After filtering, you can retrieve the matching records by using any of the retrieval methods:
|
242
241
|
|
243
242
|
my_posts.each{|row| p row}
|
244
243
|
|
@@ -248,7 +247,7 @@ See the doc/dataset_filtering.rdoc file for more details.
|
|
248
247
|
|
249
248
|
Counting records is easy using +count+:
|
250
249
|
|
251
|
-
posts.
|
250
|
+
posts.where(:category.like('%ruby%')).count
|
252
251
|
# SELECT COUNT(*) FROM posts WHERE category LIKE '%ruby%'
|
253
252
|
|
254
253
|
And you can also query maximum/minimum values via +max+ and +min+:
|
@@ -275,7 +274,7 @@ Ordering datasets is simple using +order+:
|
|
275
274
|
posts.order(:stamp, :name)
|
276
275
|
# ORDER BY stamp, name
|
277
276
|
|
278
|
-
Chaining +order+ doesn't work the same as +
|
277
|
+
Chaining +order+ doesn't work the same as +where+:
|
279
278
|
|
280
279
|
posts.order(:stamp).order(:name)
|
281
280
|
# ORDER BY name
|
@@ -285,10 +284,27 @@ The +order_append+ method chains this way, though:
|
|
285
284
|
posts.order(:stamp).order_append(:name)
|
286
285
|
# ORDER BY stamp, name
|
287
286
|
|
287
|
+
The +order_prepend+ method can be used as well:
|
288
|
+
|
289
|
+
posts.order(:stamp).order_prepend(:name)
|
290
|
+
# ORDER BY name, stamp
|
291
|
+
|
288
292
|
You can also specify descending order:
|
289
293
|
|
290
|
-
posts.
|
294
|
+
posts.reverse_order(:stamp)
|
291
295
|
# ORDER BY stamp DESC
|
296
|
+
posts.order(Sequel.desc(:stamp))
|
297
|
+
# ORDER BY stamp DESC
|
298
|
+
|
299
|
+
=== Core Extensions
|
300
|
+
|
301
|
+
Note the use of <tt>Sequel.desc(:stamp)</tt> in the above example. Much of Sequel's DSL uses this style, calling methods on the Sequel module that return SQL expression objects. Sequel also ships with a {core_extensions extension}[link:files/doc/core_extensions_rdoc.html]) that integrates Sequel's DSL better into the ruby language, allowing you to write:
|
302
|
+
|
303
|
+
:stamp.desc
|
304
|
+
|
305
|
+
instead of:
|
306
|
+
|
307
|
+
Sequel.desc(:stamp)
|
292
308
|
|
293
309
|
=== Selecting Columns
|
294
310
|
|
@@ -299,7 +315,7 @@ Selecting specific columns to be returned is also simple using +select+:
|
|
299
315
|
posts.select(:stamp, :name)
|
300
316
|
# SELECT stamp, name FROM posts
|
301
317
|
|
302
|
-
Chaining +select+ works like +order+, not +
|
318
|
+
Chaining +select+ works like +order+, not +where+:
|
303
319
|
|
304
320
|
posts.select(:stamp).select(:name)
|
305
321
|
# SELECT name FROM posts
|
@@ -313,16 +329,16 @@ As you might expect, there is an +order_append+ equivalent for +select+ called +
|
|
313
329
|
|
314
330
|
Deleting records from the table is done with +delete+:
|
315
331
|
|
316
|
-
posts.
|
332
|
+
posts.where('stamp < ?', Date.today - 3).delete
|
317
333
|
# DELETE FROM posts WHERE stamp < '2010-07-11'
|
318
334
|
|
319
335
|
Be very careful when deleting, as +delete+ affects all rows in the dataset.
|
320
|
-
+
|
336
|
+
Call +where+ first and +delete+ second:
|
321
337
|
|
322
338
|
# DO THIS:
|
323
|
-
posts.
|
339
|
+
posts.where('stamp < ?', Date.today - 7).delete
|
324
340
|
# NOT THIS:
|
325
|
-
posts.delete.
|
341
|
+
posts.delete.where('stamp < ?', Date.today - 7)
|
326
342
|
|
327
343
|
=== Inserting Records
|
328
344
|
|
@@ -335,21 +351,21 @@ Inserting records into the table is done with +insert+:
|
|
335
351
|
|
336
352
|
Updating records in the table is done with +update+:
|
337
353
|
|
338
|
-
posts.
|
354
|
+
posts.where('stamp < ?', Date.today - 7).update(:state => 'archived')
|
339
355
|
# UPDATE posts SET state = 'archived' WHERE stamp < '2010-07-07'
|
340
356
|
|
341
357
|
You can reference table columns when choosing what values to set:
|
342
358
|
|
343
|
-
posts.
|
359
|
+
posts.where{|o| o.stamp < Date.today - 7}.update(:backup_number => :backup_number + 1)
|
344
360
|
# UPDATE posts SET backup_number = backup_number + 1 WHERE stamp < '2010-07-07'
|
345
361
|
|
346
|
-
As with +delete+, +update+ affects all rows in the dataset, so +
|
347
|
-
+update+ second
|
362
|
+
As with +delete+, +update+ affects all rows in the dataset, so +where+ first,
|
363
|
+
+update+ second:
|
348
364
|
|
349
365
|
# DO THIS:
|
350
|
-
posts.
|
366
|
+
posts.where('stamp < ?', Date.today - 7).update(:state => 'archived')
|
351
367
|
# NOT THIS:
|
352
|
-
posts.update(:state => 'archived').
|
368
|
+
posts.update(:state => 'archived').where('stamp < ?', Date.today - 7)
|
353
369
|
|
354
370
|
=== Transactions
|
355
371
|
|
@@ -357,7 +373,7 @@ You can wrap some code in a database transaction using the <tt>Database#transact
|
|
357
373
|
|
358
374
|
DB.transaction do
|
359
375
|
posts.insert(:category => 'ruby', :author => 'david')
|
360
|
-
posts.
|
376
|
+
posts.where('stamp < ?', Date.today - 7).update(:state => 'archived')
|
361
377
|
end
|
362
378
|
|
363
379
|
If the block does not raise an exception, the transaction will be committed.
|
@@ -378,11 +394,15 @@ and not raise an exception outside the block, you can raise the
|
|
378
394
|
Sequel makes it easy to join tables:
|
379
395
|
|
380
396
|
order_items = DB[:items].join(:order_items, :item_id => :id).
|
381
|
-
|
397
|
+
where(:order_items__order_id => 1234)
|
382
398
|
# SELECT * FROM items INNER JOIN order_items
|
383
399
|
# ON order_items.item_id = items.id
|
384
400
|
# WHERE order_items.order_id = 1234
|
385
401
|
|
402
|
+
The important thing to note here is that item_id is automatically qualified with
|
403
|
+
the table being joined, and id is automatically qualified with the last table
|
404
|
+
joined.
|
405
|
+
|
386
406
|
You can then do anything you like with the dataset:
|
387
407
|
|
388
408
|
order_total = order_items.sum(:price)
|
@@ -406,14 +426,14 @@ Using +graph+, you can split the result hashes into subhashes, one per join:
|
|
406
426
|
|
407
427
|
Sequel expects column names to be specified using symbols. In addition, returned hashes always use symbols as their keys. This allows you to freely mix literal values and column references in many cases. For example, the two following lines produce equivalent SQL:
|
408
428
|
|
409
|
-
items.
|
429
|
+
items.where(:x => 1)
|
410
430
|
# SELECT * FROM items WHERE (x = 1)
|
411
|
-
items.
|
431
|
+
items.where(1 => :x)
|
412
432
|
# SELECT * FROM items WHERE (1 = x)"
|
413
433
|
|
414
434
|
Ruby strings are generally treated as SQL strings:
|
415
435
|
|
416
|
-
items.
|
436
|
+
items.where(:x => 'x')
|
417
437
|
# SELECT * FROM items WHERE (x = 'x')
|
418
438
|
|
419
439
|
=== Qualifying column names
|
@@ -423,9 +443,9 @@ Column references can be qualified by using the double underscore special notati
|
|
423
443
|
items.literal(:items__price)
|
424
444
|
# items.price
|
425
445
|
|
426
|
-
Another way to qualify columns is to use the
|
446
|
+
Another way to qualify columns is to use the <tt>Sequel.qualify</tt> method:
|
427
447
|
|
428
|
-
items.literal(
|
448
|
+
items.literal(Sequel.qualify(:items, :price))
|
429
449
|
# items.price
|
430
450
|
|
431
451
|
=== Column aliases
|
@@ -437,9 +457,9 @@ You can also alias columns by using the triple undersecore special notation <tt>
|
|
437
457
|
items.literal(:items__price___p)
|
438
458
|
# items.price AS p
|
439
459
|
|
440
|
-
Another way to alias columns is to use the
|
460
|
+
Another way to alias columns is to use the <tt>Sequel.as</tt> method:
|
441
461
|
|
442
|
-
items.literal(
|
462
|
+
items.literal(Sequel.as(:price, :p))
|
443
463
|
# price AS p
|
444
464
|
|
445
465
|
== Sequel Models
|
@@ -465,7 +485,7 @@ You can explicitly set the table name or even the dataset used:
|
|
465
485
|
|
466
486
|
If you call +set_dataset+ with a symbol, it assumes you are referring to the table with the same name. You can also call it with a dataset, which will set the defaults for all retrievals for that model:
|
467
487
|
|
468
|
-
Post.set_dataset DB[:my_posts].
|
488
|
+
Post.set_dataset DB[:my_posts].where(:category => 'ruby')
|
469
489
|
Post.set_dataset DB[:my_posts].select(:id, :name).order(:date)
|
470
490
|
|
471
491
|
=== Model instances
|
@@ -500,12 +520,12 @@ A single model instance can also be fetched by specifying a condition:
|
|
500
520
|
|
501
521
|
A model class lets you iterate over subsets of records by proxying many methods to the underlying dataset. This means that you can use most of the +Dataset+ API to create customized queries that return model instances, e.g.:
|
502
522
|
|
503
|
-
Post.
|
523
|
+
Post.where(:category => 'ruby').each{|post| p post}
|
504
524
|
|
505
525
|
You can also manipulate the records in the dataset:
|
506
526
|
|
507
|
-
Post.
|
508
|
-
Post.
|
527
|
+
Post.where{num_comments < 7}.delete
|
528
|
+
Post.where(Sequel.like(:title, /ruby/)).update(:category => 'ruby')
|
509
529
|
|
510
530
|
=== Accessing record values
|
511
531
|
|
@@ -523,10 +543,12 @@ If the record's attributes names are not valid columns in the model's dataset (m
|
|
523
543
|
post[:id] #=> 123
|
524
544
|
post[:title] #=> 'hello world'
|
525
545
|
|
526
|
-
You can also modify record values using attribute setters or the +set+ method:
|
546
|
+
You can also modify record values using attribute setters, the <tt>[]=</tt> method, or the +set+ method:
|
527
547
|
|
528
548
|
post.title = 'hey there'
|
529
549
|
# or
|
550
|
+
post[:title] = 'hey there'
|
551
|
+
# or
|
530
552
|
post.set(:title=>'hey there')
|
531
553
|
|
532
554
|
That will just change the value for the object, it will not update the row in the database. To update the database row, call the +save+ method:
|
@@ -575,7 +597,7 @@ You can execute custom code when creating, updating, or deleting records by defi
|
|
575
597
|
|
576
598
|
Note the use of +super+ if you define your own hook methods. Almost all <tt>Sequel::Model</tt> class and instance methods (not just hook methods) can be overridden safely, but you have to make sure to call +super+ when doing so, otherwise you risk breaking things.
|
577
599
|
|
578
|
-
For the example above, you should probably use a database trigger if you can. Hooks can be used for data integrity, but they will only enforce that integrity when you are modifying the database through model instances
|
600
|
+
For the example above, you should probably use a database trigger if you can. Hooks can be used for data integrity, but they will only enforce that integrity when you are modifying the database through model instances, and even then they are often subject to race conditions. It's best to use database triggers and constraints to enforce data integrity.
|
579
601
|
|
580
602
|
=== Deleting records
|
581
603
|
|
@@ -586,8 +608,8 @@ You can delete individual records by calling +delete+ or +destroy+. The only dif
|
|
586
608
|
|
587
609
|
Records can also be deleted en-masse by calling <tt>Model.delete</tt> and <tt>Model.destroy</tt>. As stated above, you can specify filters for the deleted records:
|
588
610
|
|
589
|
-
Post.
|
590
|
-
Post.
|
611
|
+
Post.where(:category => 32).delete # => bypasses hooks
|
612
|
+
Post.where(:category => 32).destroy # => runs hooks
|
591
613
|
|
592
614
|
Please note that if <tt>Model.destroy</tt> is called, each record is deleted
|
593
615
|
separately, but <tt>Model.delete</tt> deletes all matching records with a single
|
@@ -632,7 +654,7 @@ All associations add a dataset method that can be used to further filter or reor
|
|
632
654
|
post.comments_dataset.destroy
|
633
655
|
|
634
656
|
# Return all tags related to this post with no subscribers, ordered by the tag's name
|
635
|
-
post.tags_dataset.
|
657
|
+
post.tags_dataset.where(:subscribers=>0).order(:name).all
|
636
658
|
|
637
659
|
=== Eager Loading
|
638
660
|
|
@@ -663,7 +685,7 @@ Associations can be eagerly loaded via +eager+ and the <tt>:eager</tt> associati
|
|
663
685
|
Post.eager(:person).all
|
664
686
|
|
665
687
|
# eager is a dataset method, so it works with filters/orders/limits/etc.
|
666
|
-
Post.
|
688
|
+
Post.where{topic > 'M'}.order(:date).limit(5).eager(:person).all
|
667
689
|
|
668
690
|
person = Person.first
|
669
691
|
# Eager loading via :eager (will eagerly load the tags for this person's posts)
|
@@ -690,16 +712,16 @@ In addition to using +eager+, you can also use +eager_graph+, which will use a s
|
|
690
712
|
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:
|
691
713
|
|
692
714
|
# Eagerly load only replies containing 'foo'
|
693
|
-
Post.eager(:replies=>proc{|ds| ds.
|
715
|
+
Post.eager(:replies=>proc{|ds| ds.where(Sequel.like(text, '%foo%'))}).all
|
694
716
|
|
695
717
|
This also works when using +eager_graph+, in which case the proc is called with dataset to graph into the current dataset:
|
696
718
|
|
697
|
-
Post.eager_graph(:replies=>proc{|ds| ds.
|
719
|
+
Post.eager_graph(:replies=>proc{|ds| ds.where(Sequel.like(text, '%foo%'))}).all
|
698
720
|
|
699
721
|
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:
|
700
722
|
|
701
723
|
# Eagerly load only replies containing 'foo', and the person and tags for those replies
|
702
|
-
Post.eager(:replies=>{proc{|ds| ds.
|
724
|
+
Post.eager(:replies=>{proc{|ds| ds.where(Sequel.like(text, '%foo%'))}=>[:person, :tags]}).all
|
703
725
|
|
704
726
|
=== Extending the underlying dataset
|
705
727
|
|
@@ -707,7 +729,7 @@ The obvious way to add table-wide logic is to define class methods to the model
|
|
707
729
|
|
708
730
|
class Post < Sequel::Model
|
709
731
|
def self.posts_with_few_comments
|
710
|
-
|
732
|
+
where{num_comments < 30}
|
711
733
|
end
|
712
734
|
|
713
735
|
def self.clean_posts_with_few_comments
|
@@ -715,27 +737,29 @@ The obvious way to add table-wide logic is to define class methods to the model
|
|
715
737
|
end
|
716
738
|
end
|
717
739
|
|
718
|
-
You can also implement table-wide logic by defining methods on the dataset using +
|
740
|
+
You can also implement table-wide logic by defining methods on the dataset using +dataset_module+:
|
719
741
|
|
720
742
|
class Post < Sequel::Model
|
721
|
-
|
722
|
-
|
723
|
-
|
724
|
-
|
725
|
-
|
726
|
-
|
743
|
+
dataset_module do
|
744
|
+
def posts_with_few_comments
|
745
|
+
where{num_comments < 30}
|
746
|
+
end
|
747
|
+
|
748
|
+
def clean_posts_with_few_comments
|
749
|
+
posts_with_few_comments.delete
|
750
|
+
end
|
727
751
|
end
|
728
752
|
end
|
729
753
|
|
730
754
|
This is the recommended way of implementing table-wide operations, and allows you to have access to your model API from filtered datasets as well:
|
731
755
|
|
732
|
-
Post.
|
756
|
+
Post.where(:category => 'ruby').clean_posts_with_few_comments
|
733
757
|
|
734
758
|
Sequel models also provide a +subset+ class method that creates a dataset method with a simple filter:
|
735
759
|
|
736
760
|
class Post < Sequel::Model
|
737
761
|
subset(:posts_with_few_comments){num_comments < 30}
|
738
|
-
subset :invisible,
|
762
|
+
subset :invisible, Sequel.~(:visible)
|
739
763
|
end
|
740
764
|
|
741
765
|
=== Model Validations
|
data/Rakefile
CHANGED
@@ -29,7 +29,7 @@ end
|
|
29
29
|
|
30
30
|
desc "Upload sequel gem to gemcutter"
|
31
31
|
task :release=>[:package] do
|
32
|
-
sh %{gem push ./#{NAME}-#{VERS.call}.gem}
|
32
|
+
sh %{gem push ./#{NAME}-#{VERS.call}.gem}
|
33
33
|
end
|
34
34
|
|
35
35
|
### RDoc
|
@@ -132,14 +132,15 @@ begin
|
|
132
132
|
spec.call("spec_core", Dir["spec/core/*_spec.rb"], "Run core specs")
|
133
133
|
spec.call("spec_model", Dir["spec/model/*_spec.rb"], "Run model specs")
|
134
134
|
spec.call("_spec_model_no_assoc", Dir["spec/model/*_spec.rb"].delete_if{|f| f =~ /association|eager_loading/}, '')
|
135
|
+
spec_with_cov.call("spec_core_ext", ["spec/core_extensions_spec.rb"], "Run core extensions specs"){|t| t.rcov_opts.concat(%w'--exclude "lib/sequel/([a-z_]+\.rb|adapters|connection_pool|database|dataset|model)"')}
|
135
136
|
spec_with_cov.call("spec_plugin", Dir["spec/extensions/*_spec.rb"], "Run extension/plugin specs"){|t| t.rcov_opts.concat(%w'--exclude "lib/sequel/([a-z_]+\.rb|adapters|connection_pool|database|dataset|model)"')}
|
136
137
|
spec_with_cov.call("spec_integration", Dir["spec/integration/*_test.rb"], "Run integration tests")
|
137
|
-
|
138
|
+
|
138
139
|
%w'postgres sqlite mysql informix oracle firebird mssql db2'.each do |adapter|
|
139
140
|
spec_with_cov.call("spec_#{adapter}", ["spec/adapters/#{adapter}_spec.rb"] + Dir["spec/integration/*_test.rb"], "Run #{adapter} specs"){|t| t.rcov_opts.concat(%w'--exclude "lib/sequel/([a-z_]+\.rb|connection_pool|database|dataset|model|extensions|plugins)"')}
|
140
141
|
end
|
141
|
-
|
142
|
-
task :spec_travis=>[:spec, :spec_plugin, :spec_sqlite] do
|
142
|
+
|
143
|
+
task :spec_travis=>[:spec, :spec_plugin, :spec_core_ext, :spec_sqlite] do
|
143
144
|
if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby'
|
144
145
|
ENV['SEQUEL_PG_SPEC_DB'] = "jdbc:postgresql://localhost/sequel_test?user=postgres"
|
145
146
|
ENV['SEQUEL_MY_SPEC_DB'] = "jdbc:mysql://localhost/sequel_test?user=root"
|
@@ -173,7 +174,7 @@ end
|
|
173
174
|
desc "Report code statistics (KLOCs, etc) from the application"
|
174
175
|
task :stats do
|
175
176
|
STATS_DIRECTORIES = [%w(Code lib/), %w(Spec spec)].map{|name, dir| [ name, "./#{dir}" ] }.select { |name, dir| File.directory?(dir)}
|
176
|
-
require "extra/stats"
|
177
|
+
require "./extra/stats"
|
177
178
|
verbose = true
|
178
179
|
CodeStatistics.new(*STATS_DIRECTORIES).to_s
|
179
180
|
end
|