sequel 3.23.0 → 3.24.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +64 -0
- data/doc/association_basics.rdoc +43 -5
- data/doc/model_hooks.rdoc +64 -27
- data/doc/prepared_statements.rdoc +8 -4
- data/doc/reflection.rdoc +8 -2
- data/doc/release_notes/3.23.0.txt +1 -1
- data/doc/release_notes/3.24.0.txt +420 -0
- data/lib/sequel/adapters/db2.rb +8 -1
- data/lib/sequel/adapters/firebird.rb +25 -9
- data/lib/sequel/adapters/informix.rb +4 -19
- data/lib/sequel/adapters/jdbc.rb +34 -17
- data/lib/sequel/adapters/jdbc/h2.rb +5 -0
- data/lib/sequel/adapters/jdbc/informix.rb +31 -0
- data/lib/sequel/adapters/jdbc/jtds.rb +34 -0
- data/lib/sequel/adapters/jdbc/mssql.rb +0 -32
- data/lib/sequel/adapters/jdbc/mysql.rb +9 -0
- data/lib/sequel/adapters/jdbc/sqlserver.rb +46 -0
- data/lib/sequel/adapters/postgres.rb +30 -1
- data/lib/sequel/adapters/shared/access.rb +10 -0
- data/lib/sequel/adapters/shared/informix.rb +45 -0
- data/lib/sequel/adapters/shared/mssql.rb +82 -8
- data/lib/sequel/adapters/shared/mysql.rb +25 -7
- data/lib/sequel/adapters/shared/postgres.rb +39 -6
- data/lib/sequel/adapters/shared/sqlite.rb +57 -5
- data/lib/sequel/adapters/sqlite.rb +8 -3
- data/lib/sequel/adapters/swift/mysql.rb +9 -0
- data/lib/sequel/ast_transformer.rb +190 -0
- data/lib/sequel/core.rb +1 -1
- data/lib/sequel/database/misc.rb +6 -0
- data/lib/sequel/database/query.rb +33 -3
- data/lib/sequel/database/schema_methods.rb +6 -2
- data/lib/sequel/dataset/features.rb +6 -0
- data/lib/sequel/dataset/prepared_statements.rb +17 -2
- data/lib/sequel/dataset/query.rb +17 -0
- data/lib/sequel/dataset/sql.rb +2 -53
- data/lib/sequel/exceptions.rb +4 -0
- data/lib/sequel/extensions/to_dot.rb +95 -83
- data/lib/sequel/model.rb +5 -0
- data/lib/sequel/model/associations.rb +80 -14
- data/lib/sequel/model/base.rb +182 -55
- data/lib/sequel/model/exceptions.rb +3 -1
- data/lib/sequel/plugins/association_pks.rb +6 -4
- data/lib/sequel/plugins/defaults_setter.rb +58 -0
- data/lib/sequel/plugins/many_through_many.rb +8 -3
- data/lib/sequel/plugins/prepared_statements.rb +140 -0
- data/lib/sequel/plugins/prepared_statements_associations.rb +84 -0
- data/lib/sequel/plugins/prepared_statements_safe.rb +72 -0
- data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
- data/lib/sequel/sql.rb +8 -0
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/postgres_spec.rb +43 -18
- data/spec/core/connection_pool_spec.rb +56 -77
- data/spec/core/database_spec.rb +25 -0
- data/spec/core/dataset_spec.rb +127 -16
- data/spec/core/expression_filters_spec.rb +13 -0
- data/spec/core/schema_spec.rb +6 -1
- data/spec/extensions/association_pks_spec.rb +7 -0
- data/spec/extensions/defaults_setter_spec.rb +64 -0
- data/spec/extensions/many_through_many_spec.rb +60 -4
- data/spec/extensions/nested_attributes_spec.rb +1 -0
- data/spec/extensions/prepared_statements_associations_spec.rb +126 -0
- data/spec/extensions/prepared_statements_safe_spec.rb +69 -0
- data/spec/extensions/prepared_statements_spec.rb +72 -0
- data/spec/extensions/prepared_statements_with_pk_spec.rb +38 -0
- data/spec/extensions/to_dot_spec.rb +3 -5
- data/spec/integration/associations_test.rb +155 -1
- data/spec/integration/dataset_test.rb +8 -1
- data/spec/integration/plugin_test.rb +119 -0
- data/spec/integration/prepared_statement_test.rb +72 -1
- data/spec/integration/schema_test.rb +66 -8
- data/spec/integration/transaction_test.rb +40 -0
- data/spec/model/associations_spec.rb +349 -8
- data/spec/model/base_spec.rb +59 -0
- data/spec/model/hooks_spec.rb +161 -0
- data/spec/model/record_spec.rb +24 -0
- metadata +21 -4
data/lib/sequel/model/base.rb
CHANGED
@@ -271,7 +271,8 @@ module Sequel
|
|
271
271
|
db
|
272
272
|
begin
|
273
273
|
if self == Model || !@dataset
|
274
|
-
|
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
|
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
|
-
|
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
|
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
|
1037
|
-
|
1038
|
-
|
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
|
-
|
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
|
-
#
|
1197
|
+
# Do the deletion of the object's dataset, and check that the row
|
1198
|
+
# was actually deleted.
|
1201
1199
|
def _delete
|
1202
|
-
n =
|
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
|
-
|
1217
|
-
|
1218
|
-
|
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.
|
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
|
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
|
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
|
-
|
1265
|
-
|
1266
|
-
|
1267
|
-
|
1268
|
-
|
1269
|
-
|
1270
|
-
|
1271
|
-
|
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 =
|
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
|
-
#
|
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
|
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
|
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
|
-
|
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
|
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
|
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
|
-
|
80
|
-
|
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
|