sequel 3.23.0 → 3.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. data/CHANGELOG +64 -0
  2. data/doc/association_basics.rdoc +43 -5
  3. data/doc/model_hooks.rdoc +64 -27
  4. data/doc/prepared_statements.rdoc +8 -4
  5. data/doc/reflection.rdoc +8 -2
  6. data/doc/release_notes/3.23.0.txt +1 -1
  7. data/doc/release_notes/3.24.0.txt +420 -0
  8. data/lib/sequel/adapters/db2.rb +8 -1
  9. data/lib/sequel/adapters/firebird.rb +25 -9
  10. data/lib/sequel/adapters/informix.rb +4 -19
  11. data/lib/sequel/adapters/jdbc.rb +34 -17
  12. data/lib/sequel/adapters/jdbc/h2.rb +5 -0
  13. data/lib/sequel/adapters/jdbc/informix.rb +31 -0
  14. data/lib/sequel/adapters/jdbc/jtds.rb +34 -0
  15. data/lib/sequel/adapters/jdbc/mssql.rb +0 -32
  16. data/lib/sequel/adapters/jdbc/mysql.rb +9 -0
  17. data/lib/sequel/adapters/jdbc/sqlserver.rb +46 -0
  18. data/lib/sequel/adapters/postgres.rb +30 -1
  19. data/lib/sequel/adapters/shared/access.rb +10 -0
  20. data/lib/sequel/adapters/shared/informix.rb +45 -0
  21. data/lib/sequel/adapters/shared/mssql.rb +82 -8
  22. data/lib/sequel/adapters/shared/mysql.rb +25 -7
  23. data/lib/sequel/adapters/shared/postgres.rb +39 -6
  24. data/lib/sequel/adapters/shared/sqlite.rb +57 -5
  25. data/lib/sequel/adapters/sqlite.rb +8 -3
  26. data/lib/sequel/adapters/swift/mysql.rb +9 -0
  27. data/lib/sequel/ast_transformer.rb +190 -0
  28. data/lib/sequel/core.rb +1 -1
  29. data/lib/sequel/database/misc.rb +6 -0
  30. data/lib/sequel/database/query.rb +33 -3
  31. data/lib/sequel/database/schema_methods.rb +6 -2
  32. data/lib/sequel/dataset/features.rb +6 -0
  33. data/lib/sequel/dataset/prepared_statements.rb +17 -2
  34. data/lib/sequel/dataset/query.rb +17 -0
  35. data/lib/sequel/dataset/sql.rb +2 -53
  36. data/lib/sequel/exceptions.rb +4 -0
  37. data/lib/sequel/extensions/to_dot.rb +95 -83
  38. data/lib/sequel/model.rb +5 -0
  39. data/lib/sequel/model/associations.rb +80 -14
  40. data/lib/sequel/model/base.rb +182 -55
  41. data/lib/sequel/model/exceptions.rb +3 -1
  42. data/lib/sequel/plugins/association_pks.rb +6 -4
  43. data/lib/sequel/plugins/defaults_setter.rb +58 -0
  44. data/lib/sequel/plugins/many_through_many.rb +8 -3
  45. data/lib/sequel/plugins/prepared_statements.rb +140 -0
  46. data/lib/sequel/plugins/prepared_statements_associations.rb +84 -0
  47. data/lib/sequel/plugins/prepared_statements_safe.rb +72 -0
  48. data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
  49. data/lib/sequel/sql.rb +8 -0
  50. data/lib/sequel/version.rb +1 -1
  51. data/spec/adapters/postgres_spec.rb +43 -18
  52. data/spec/core/connection_pool_spec.rb +56 -77
  53. data/spec/core/database_spec.rb +25 -0
  54. data/spec/core/dataset_spec.rb +127 -16
  55. data/spec/core/expression_filters_spec.rb +13 -0
  56. data/spec/core/schema_spec.rb +6 -1
  57. data/spec/extensions/association_pks_spec.rb +7 -0
  58. data/spec/extensions/defaults_setter_spec.rb +64 -0
  59. data/spec/extensions/many_through_many_spec.rb +60 -4
  60. data/spec/extensions/nested_attributes_spec.rb +1 -0
  61. data/spec/extensions/prepared_statements_associations_spec.rb +126 -0
  62. data/spec/extensions/prepared_statements_safe_spec.rb +69 -0
  63. data/spec/extensions/prepared_statements_spec.rb +72 -0
  64. data/spec/extensions/prepared_statements_with_pk_spec.rb +38 -0
  65. data/spec/extensions/to_dot_spec.rb +3 -5
  66. data/spec/integration/associations_test.rb +155 -1
  67. data/spec/integration/dataset_test.rb +8 -1
  68. data/spec/integration/plugin_test.rb +119 -0
  69. data/spec/integration/prepared_statement_test.rb +72 -1
  70. data/spec/integration/schema_test.rb +66 -8
  71. data/spec/integration/transaction_test.rb +40 -0
  72. data/spec/model/associations_spec.rb +349 -8
  73. data/spec/model/base_spec.rb +59 -0
  74. data/spec/model/hooks_spec.rb +161 -0
  75. data/spec/model/record_spec.rb +24 -0
  76. metadata +21 -4
@@ -271,7 +271,8 @@ module Sequel
271
271
  db
272
272
  begin
273
273
  if self == Model || !@dataset
274
- subclass.set_dataset(subclass.implicit_table_name) unless subclass.name.empty?
274
+ n = subclass.name
275
+ subclass.set_dataset(subclass.implicit_table_name) unless n.nil? || n.empty?
275
276
  elsif @dataset
276
277
  subclass.set_dataset(@dataset.clone, :inherited=>true)
277
278
  end
@@ -686,10 +687,10 @@ module Sequel
686
687
 
687
688
  # Sequel::Model instance methods that implement basic model functionality.
688
689
  #
689
- # * All of the methods in +HOOKS+ create instance methods that are called
690
+ # * All of the methods in +HOOKS+ and +AROUND_HOOKS+ create instance methods that are called
690
691
  # by Sequel when the appropriate action occurs. For example, when destroying
691
- # a model object, Sequel will call +before_destroy+, do the destroy,
692
- # and then call +after_destroy+.
692
+ # a model object, Sequel will call +around_destory+, which will call +before_destroy+, do
693
+ # the destroy, and then call +after_destroy+.
693
694
  # * The following instance_methods all call the class method of the same
694
695
  # name: columns, dataset, db, primary_key, db_schema.
695
696
  # * All of the methods in +BOOLEAN_SETTINGS+ create attr_writers allowing you
@@ -698,6 +699,7 @@ module Sequel
698
699
  # gets the default value from the class by calling the class method of the same name.
699
700
  module InstanceMethods
700
701
  HOOKS.each{|h| class_eval("def #{h}; end", __FILE__, __LINE__)}
702
+ AROUND_HOOKS.each{|h| class_eval("def #{h}; yield end", __FILE__, __LINE__)}
701
703
 
702
704
  # Define instance method(s) that calls class method(s) of the
703
705
  # same name, caching the result in an instance variable. Define
@@ -750,7 +752,7 @@ module Sequel
750
752
  @values = {}
751
753
  @new = true
752
754
  @modified = true
753
- set(values)
755
+ initialize_set(values)
754
756
  changed_columns.clear
755
757
  yield self if block_given?
756
758
  end
@@ -1016,7 +1018,7 @@ module Sequel
1016
1018
  #
1017
1019
  # If +save+ fails and either raise_on_save_failure or the
1018
1020
  # :raise_on_failure option is true, it raises ValidationFailed
1019
- # or BeforeHookFailed. Otherwise it returns nil.
1021
+ # or HookFailed. Otherwise it returns nil.
1020
1022
  #
1021
1023
  # If it succeeds, it returns self.
1022
1024
  #
@@ -1033,9 +1035,11 @@ module Sequel
1033
1035
  # raise_on_save_failure setting
1034
1036
  def save(*columns)
1035
1037
  opts = columns.last.is_a?(Hash) ? columns.pop : {}
1036
- if opts[:validate] != false and !valid?(opts)
1037
- raise(ValidationFailed.new(errors)) if raise_on_failure?(opts)
1038
- return
1038
+ if opts[:validate] != false
1039
+ unless checked_save_failure(opts){_valid?(true, opts)}
1040
+ raise(ValidationFailed.new(errors)) if raise_on_failure?(opts)
1041
+ return
1042
+ end
1039
1043
  end
1040
1044
  checked_save_failure(opts){checked_transaction(opts){_save(columns, opts)}}
1041
1045
  end
@@ -1185,21 +1189,15 @@ module Sequel
1185
1189
  # artist(:name=>'Invalid').valid? # => false
1186
1190
  # artist.errors.full_messages # => ['name cannot be Invalid']
1187
1191
  def valid?(opts = {})
1188
- errors.clear
1189
- if before_validation == false
1190
- raise_hook_failure(:validation) if raise_on_failure?(opts)
1191
- return false
1192
- end
1193
- validate
1194
- after_validation
1195
- errors.empty?
1192
+ _valid?(false, opts)
1196
1193
  end
1197
1194
 
1198
1195
  private
1199
1196
 
1200
- # Actually do the deletion of the object's dataset.
1197
+ # Do the deletion of the object's dataset, and check that the row
1198
+ # was actually deleted.
1201
1199
  def _delete
1202
- n = _delete_dataset.delete
1200
+ n = _delete_without_checking
1203
1201
  raise(NoExistingObject, "Attempt to delete object did not result in a single row modification (Rows Deleted: #{n}, SQL: #{_delete_dataset.delete_sql})") if require_modification && n != 1
1204
1202
  n
1205
1203
  end
@@ -1210,12 +1208,24 @@ module Sequel
1210
1208
  this
1211
1209
  end
1212
1210
 
1211
+ # Actually do the deletion of the object's dataset. Return the
1212
+ # number of rows modified.
1213
+ def _delete_without_checking
1214
+ _delete_dataset.delete
1215
+ end
1216
+
1213
1217
  # Internal destroy method, separted from destroy to
1214
1218
  # allow running inside a transaction
1215
1219
  def _destroy(opts)
1216
- raise_hook_failure(:destroy) if before_destroy == false
1217
- _destroy_delete
1218
- after_destroy
1220
+ called = false
1221
+ around_destroy do
1222
+ called = true
1223
+ raise_hook_failure(:destroy) if before_destroy == false
1224
+ _destroy_delete
1225
+ after_destroy
1226
+ true
1227
+ end
1228
+ raise_hook_failure(:destroy) unless called
1219
1229
  self
1220
1230
  end
1221
1231
 
@@ -1230,11 +1240,11 @@ module Sequel
1230
1240
  # the record should be refreshed from the database.
1231
1241
  def _insert
1232
1242
  ds = _insert_dataset
1233
- if !ds.opts[:select] and ds.respond_to?(:insert_select) and h = ds.insert_select(@values)
1243
+ if !ds.opts[:select] and ds.supports_insert_select? and h = _insert_select_raw(ds)
1234
1244
  @values = h
1235
1245
  nil
1236
1246
  else
1237
- iid = ds.insert(@values)
1247
+ iid = _insert_raw(ds)
1238
1248
  # if we have a regular primary key and it's not set in @values,
1239
1249
  # we assume it's the last inserted id
1240
1250
  if (pk = autoincrementing_primary_key) && pk.is_a?(Symbol) && !@values[pk]
@@ -1243,52 +1253,89 @@ module Sequel
1243
1253
  pk
1244
1254
  end
1245
1255
  end
1246
-
1256
+
1247
1257
  # The dataset to use when inserting a new object. The same as the model's
1248
1258
  # dataset by default.
1249
1259
  def _insert_dataset
1250
1260
  model.dataset
1251
1261
  end
1252
1262
 
1263
+ # Insert into the given dataset and return the primary key created (if any).
1264
+ def _insert_raw(ds)
1265
+ ds.insert(@values)
1266
+ end
1267
+
1268
+ # Insert into the given dataset and return the hash of column values.
1269
+ def _insert_select_raw(ds)
1270
+ ds.insert_select(@values)
1271
+ end
1272
+
1253
1273
  # Refresh using a particular dataset, used inside save to make sure the same server
1254
1274
  # is used for reading newly inserted values from the database
1255
1275
  def _refresh(dataset)
1256
- set_values(dataset.first || raise(Error, "Record not found"))
1276
+ set_values(_refresh_get(dataset) || raise(Error, "Record not found"))
1257
1277
  changed_columns.clear
1258
1278
  self
1259
1279
  end
1280
+
1281
+ # Get the row of column data from the database.
1282
+ def _refresh_get(dataset)
1283
+ dataset.first
1284
+ end
1260
1285
 
1261
1286
  # Internal version of save, split from save to allow running inside
1262
1287
  # it's own transaction.
1263
1288
  def _save(columns, opts)
1264
- raise_hook_failure(:save) if before_save == false
1265
- if new?
1266
- raise_hook_failure(:create) if before_create == false
1267
- pk = _insert
1268
- @this = nil
1269
- @new = false
1270
- @was_new = true
1271
- after_create
1289
+ was_new = false
1290
+ pk = nil
1291
+ called_save = false
1292
+ called_cu = false
1293
+ around_save do
1294
+ called_save = true
1295
+ raise_hook_failure(:save) if before_save == false
1296
+ if new?
1297
+ was_new = true
1298
+ around_create do
1299
+ called_cu = true
1300
+ raise_hook_failure(:create) if before_create == false
1301
+ pk = _insert
1302
+ @this = nil
1303
+ @new = false
1304
+ @was_new = true
1305
+ after_create
1306
+ true
1307
+ end
1308
+ raise_hook_failure(:create) unless called_cu
1309
+ else
1310
+ around_update do
1311
+ called_cu = true
1312
+ raise_hook_failure(:update) if before_update == false
1313
+ if columns.empty?
1314
+ @columns_updated = if opts[:changed]
1315
+ @values.reject{|k,v| !changed_columns.include?(k)}
1316
+ else
1317
+ _save_update_all_columns_hash
1318
+ end
1319
+ changed_columns.clear
1320
+ else # update only the specified columns
1321
+ @columns_updated = @values.reject{|k, v| !columns.include?(k)}
1322
+ changed_columns.reject!{|c| columns.include?(c)}
1323
+ end
1324
+ _update_columns(@columns_updated)
1325
+ @this = nil
1326
+ after_update
1327
+ true
1328
+ end
1329
+ raise_hook_failure(:update) unless called_cu
1330
+ end
1272
1331
  after_save
1332
+ true
1333
+ end
1334
+ raise_hook_failure(:save) unless called_save
1335
+ if was_new
1273
1336
  @was_new = nil
1274
1337
  pk ? _save_refresh : changed_columns.clear
1275
1338
  else
1276
- raise_hook_failure(:update) if before_update == false
1277
- if columns.empty?
1278
- @columns_updated = if opts[:changed]
1279
- @values.reject{|k,v| !changed_columns.include?(k)}
1280
- else
1281
- _save_update_all_columns_hash
1282
- end
1283
- changed_columns.clear
1284
- else # update only the specified columns
1285
- @columns_updated = @values.reject{|k, v| !columns.include?(k)}
1286
- changed_columns.reject!{|c| columns.include?(c)}
1287
- end
1288
- _update_columns(@columns_updated)
1289
- @this = nil
1290
- after_update
1291
- after_save
1292
1339
  @columns_updated = nil
1293
1340
  end
1294
1341
  @modified = false
@@ -1321,9 +1368,10 @@ module Sequel
1321
1368
  _update(columns) unless columns.empty?
1322
1369
  end
1323
1370
 
1324
- # Update this instance's dataset with the supplied column hash.
1371
+ # Update this instance's dataset with the supplied column hash,
1372
+ # checking that only a single row was modified.
1325
1373
  def _update(columns)
1326
- n = _update_dataset.update(columns)
1374
+ n = _update_without_checking(columns)
1327
1375
  raise(NoExistingObject, "Attempt to update object did not result in a single row modification (SQL: #{_update_dataset.update_sql(columns)})") if require_modification && n != 1
1328
1376
  n
1329
1377
  end
@@ -1334,7 +1382,46 @@ module Sequel
1334
1382
  this
1335
1383
  end
1336
1384
 
1337
- # If not raising on failure, check for BeforeHookFailed
1385
+ # Update this instances dataset with the supplied column hash.
1386
+ def _update_without_checking(columns)
1387
+ _update_dataset.update(columns)
1388
+ end
1389
+
1390
+ # Internal validation method. If +raise_errors+ is +true+, hook
1391
+ # failures will be raised as HookFailure exceptions. If it is
1392
+ # +false+, +false+ will be returned instead.
1393
+ def _valid?(raise_errors, opts)
1394
+ errors.clear
1395
+ called = false
1396
+ error = false
1397
+ around_validation do
1398
+ called = true
1399
+ if before_validation == false
1400
+ if raise_errors
1401
+ raise_hook_failure(:validation)
1402
+ else
1403
+ error = true
1404
+ end
1405
+ false
1406
+ else
1407
+ validate
1408
+ after_validation
1409
+ errors.empty?
1410
+ end
1411
+ end
1412
+ error = true unless called
1413
+ if error
1414
+ if raise_errors
1415
+ raise_hook_failure(:validation)
1416
+ else
1417
+ false
1418
+ end
1419
+ else
1420
+ errors.empty?
1421
+ end
1422
+ end
1423
+
1424
+ # If not raising on failure, check for HookFailed
1338
1425
  # being raised by yielding and swallow it.
1339
1426
  def checked_save_failure(opts)
1340
1427
  if raise_on_failure?(opts)
@@ -1342,7 +1429,7 @@ module Sequel
1342
1429
  else
1343
1430
  begin
1344
1431
  yield
1345
- rescue BeforeHookFailed
1432
+ rescue HookFailed
1346
1433
  nil
1347
1434
  end
1348
1435
  end
@@ -1353,6 +1440,13 @@ module Sequel
1353
1440
  use_transaction?(opts) ? db.transaction(opts){yield} : yield
1354
1441
  end
1355
1442
 
1443
+ # Set the columns with the given hash. By default, the same as +set+, but
1444
+ # exists so it can be overridden. This is called only for new records, before
1445
+ # changed_columns is cleared.
1446
+ def initialize_set(h)
1447
+ set(h)
1448
+ end
1449
+
1356
1450
  # Default inspection output for the values hash, overwrite to change what #inspect displays.
1357
1451
  def inspect_values
1358
1452
  @values.inspect
@@ -1369,7 +1463,7 @@ module Sequel
1369
1463
  # Raise an error appropriate to the hook type. May be swallowed by
1370
1464
  # checked_save_failure depending on the raise_on_failure? setting.
1371
1465
  def raise_hook_failure(type)
1372
- raise BeforeHookFailed, "one of the before_#{type} hooks returned false"
1466
+ raise HookFailed, "one of the before_#{type} hooks returned false"
1373
1467
  end
1374
1468
 
1375
1469
  # Set the columns, filtered by the only and except arrays.
@@ -1461,6 +1555,18 @@ module Sequel
1461
1555
  # Artist.dataset.model # => Artist
1462
1556
  attr_accessor :model
1463
1557
 
1558
+ # Assume if a single integer is given that it is a lookup by primary
1559
+ # key, and call with_pk with the argument.
1560
+ #
1561
+ # Artist.dataset[1] # SELECT * FROM artists WHERE (id = 1) LIMIT 1
1562
+ def [](*args)
1563
+ if args.length == 1 && (i = args.at(0)) && i.is_a?(Integer)
1564
+ with_pk(i)
1565
+ else
1566
+ super
1567
+ end
1568
+ end
1569
+
1464
1570
  # Destroy each row in the dataset by instantiating it and then calling
1465
1571
  # destroy on the resulting model object. This isn't as fast as deleting
1466
1572
  # the dataset, which does a single SQL call, but this runs any destroy
@@ -1491,6 +1597,27 @@ module Sequel
1491
1597
  super(pk, value_column)
1492
1598
  end
1493
1599
  end
1600
+
1601
+ # Given a primary key value, return the first record in the dataset with that primary key
1602
+ # value.
1603
+ #
1604
+ # # Single primary key
1605
+ # Artist.dataset.with_pk(1) # SELECT * FROM artists WHERE (id = 1) LIMIT 1
1606
+ #
1607
+ # # Composite primary key
1608
+ # Artist.dataset.with_pk([1, 2]) # SELECT * FROM artists
1609
+ # # WHERE ((id1 = 1) AND (id2 = 2)) LIMIT 1
1610
+ def with_pk(pk)
1611
+ case primary_key = model.primary_key
1612
+ when Array
1613
+ raise(Error, "single primary key given (#{pk.inspect}) when a composite primary key is expected (#{primary_key.inspect})") unless pk.is_a?(Array)
1614
+ raise(Error, "composite primary key given (#{pk.inspect}) does not match composite primary key length (#{primary_key.inspect})") if pk.length != primary_key.length
1615
+ first(primary_key.zip(pk))
1616
+ else
1617
+ raise(Error, "composite primary key given (#{pk.inspect}) when a single primary key is expected (#{primary_key.inspect})") if pk.is_a?(Array)
1618
+ first(primary_key=>pk)
1619
+ end
1620
+ end
1494
1621
  end
1495
1622
 
1496
1623
  extend ClassMethods
@@ -1,6 +1,8 @@
1
1
  module Sequel
2
2
  # Exception class raised when +raise_on_save_failure+ is set and a before hook returns false
3
- class BeforeHookFailed < Error; end
3
+ # or an around hook doesn't call super or yield.
4
+ class HookFailed < Error; end
5
+ BeforeHookFailed = HookFailed
4
6
 
5
7
  # Exception class raised when +require_modification+ is set and an UPDATE or DELETE statement to modify the dataset doesn't
6
8
  # modify a single row.
@@ -14,7 +14,7 @@ module Sequel
14
14
  #
15
15
  # Note that it uses the singular form of the association name. Also note
16
16
  # that the setter both associates to new primary keys not in the assocation
17
- # and disassociated from primary keys not provided to the method.
17
+ # and disassociates from primary keys not provided to the method.
18
18
  #
19
19
  # This plugin makes modifications directly to the underlying tables,
20
20
  # it does not create or return any model objects, and therefore does
@@ -71,13 +71,15 @@ module Sequel
71
71
  super
72
72
  return if opts[:type] == :one_to_one
73
73
  def_association_pks_getter(opts) do
74
- send(opts.dataset_method).select_map(opts[:primary_key])
74
+ send(opts.dataset_method).select_map(opts.associated_class.primary_key)
75
75
  end
76
76
  def_association_pks_setter(opts) do |pks|
77
77
  checked_transaction do
78
78
  ds = send(opts.dataset_method)
79
- ds.unfiltered.filter(opts[:primary_key]=>pks).update(opts[:key]=>pk)
80
- ds.exclude(opts[:primary_key]=>pks).update(opts[:key]=>nil)
79
+ primary_key = opts.associated_class.primary_key
80
+ key = opts[:key]
81
+ ds.unfiltered.filter(primary_key=>pks).update(key=>pk)
82
+ ds.exclude(primary_key=>pks).update(key=>nil)
81
83
  end
82
84
  end
83
85
  end
@@ -0,0 +1,58 @@
1
+ module Sequel
2
+ module Plugins
3
+ # DefaultsSetter is a simple plugin that sets non-nil/NULL default values upon
4
+ # initialize:
5
+ #
6
+ # # column a default NULL
7
+ # # column b default 2
8
+ # album = Album.new.values # {:b => 2}
9
+ # album = Album.new(:a=>1, :b=>3).values # {:a => 1, :b => 3}
10
+ #
11
+ # Usage:
12
+ #
13
+ # # Make all model subclass instances set defaults (called before loading subclasses)
14
+ # Sequel::Model.plugin :defaults_setter
15
+ #
16
+ # # Make the Album class set defaults
17
+ # Album.plugin :defaults_setter
18
+ module DefaultsSetter
19
+ # Set the default values based on the model schema
20
+ def self.configure(model)
21
+ model.send(:set_default_values)
22
+ end
23
+
24
+ module ClassMethods
25
+ # The default values to set in initialize for this model. A hash with column symbol
26
+ # keys and default values. If the default values respond to +call+, it will be called
27
+ # to get the value, otherwise the value will be used directly. You can manually modify
28
+ # this hash to set specific default values, by default the ones will be parsed from the database.
29
+ attr_reader :default_values
30
+
31
+ # Set the default values when loading the dataset.
32
+ def set_dataset(*)
33
+ x = super
34
+ set_default_values
35
+ x
36
+ end
37
+
38
+ private
39
+
40
+ def set_default_values
41
+ h = {}
42
+ @db_schema.each{|k, v| h[k] = v[:ruby_default] if v[:ruby_default]} if @db_schema
43
+ @default_values = h
44
+ end
45
+ end
46
+
47
+ module InstanceMethods
48
+ private
49
+
50
+ # Set default values if they are not already set by the hash provided to initialize.
51
+ def initialize_set(h)
52
+ super
53
+ model.default_values.each{|k,v| self[k] = (v.respond_to?(:call) ? v.call : v) unless values.has_key?(k)}
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end