sequel 3.10.0 → 3.11.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +68 -0
- data/COPYING +1 -1
- data/README.rdoc +87 -27
- data/bin/sequel +2 -4
- data/doc/association_basics.rdoc +1383 -0
- data/doc/dataset_basics.rdoc +106 -0
- data/doc/opening_databases.rdoc +45 -16
- data/doc/querying.rdoc +210 -0
- data/doc/release_notes/3.11.0.txt +254 -0
- data/doc/virtual_rows.rdoc +217 -31
- data/lib/sequel/adapters/ado.rb +28 -12
- data/lib/sequel/adapters/ado/mssql.rb +33 -1
- data/lib/sequel/adapters/amalgalite.rb +13 -8
- data/lib/sequel/adapters/db2.rb +1 -2
- data/lib/sequel/adapters/dbi.rb +7 -4
- data/lib/sequel/adapters/do.rb +14 -15
- data/lib/sequel/adapters/do/postgres.rb +4 -5
- data/lib/sequel/adapters/do/sqlite.rb +9 -0
- data/lib/sequel/adapters/firebird.rb +5 -10
- data/lib/sequel/adapters/informix.rb +2 -4
- data/lib/sequel/adapters/jdbc.rb +111 -49
- data/lib/sequel/adapters/jdbc/mssql.rb +1 -2
- data/lib/sequel/adapters/jdbc/mysql.rb +11 -0
- data/lib/sequel/adapters/jdbc/oracle.rb +4 -7
- data/lib/sequel/adapters/jdbc/postgresql.rb +8 -1
- data/lib/sequel/adapters/jdbc/sqlite.rb +12 -0
- data/lib/sequel/adapters/mysql.rb +14 -5
- data/lib/sequel/adapters/odbc.rb +2 -4
- data/lib/sequel/adapters/odbc/mssql.rb +2 -4
- data/lib/sequel/adapters/openbase.rb +1 -2
- data/lib/sequel/adapters/oracle.rb +4 -8
- data/lib/sequel/adapters/postgres.rb +4 -11
- data/lib/sequel/adapters/shared/mssql.rb +22 -9
- data/lib/sequel/adapters/shared/mysql.rb +33 -30
- data/lib/sequel/adapters/shared/oracle.rb +0 -5
- data/lib/sequel/adapters/shared/postgres.rb +13 -11
- data/lib/sequel/adapters/shared/sqlite.rb +56 -10
- data/lib/sequel/adapters/sqlite.rb +16 -9
- data/lib/sequel/connection_pool.rb +6 -1
- data/lib/sequel/connection_pool/single.rb +1 -0
- data/lib/sequel/core.rb +6 -1
- data/lib/sequel/database.rb +52 -23
- data/lib/sequel/database/schema_generator.rb +6 -0
- data/lib/sequel/database/schema_methods.rb +5 -5
- data/lib/sequel/database/schema_sql.rb +1 -1
- data/lib/sequel/dataset.rb +4 -190
- data/lib/sequel/dataset/actions.rb +323 -1
- data/lib/sequel/dataset/features.rb +18 -2
- data/lib/sequel/dataset/graph.rb +7 -0
- data/lib/sequel/dataset/misc.rb +119 -0
- data/lib/sequel/dataset/mutation.rb +64 -0
- data/lib/sequel/dataset/prepared_statements.rb +6 -0
- data/lib/sequel/dataset/query.rb +272 -6
- data/lib/sequel/dataset/sql.rb +186 -394
- data/lib/sequel/model.rb +4 -2
- data/lib/sequel/model/associations.rb +31 -14
- data/lib/sequel/model/base.rb +32 -13
- data/lib/sequel/model/exceptions.rb +8 -4
- data/lib/sequel/model/plugins.rb +3 -13
- data/lib/sequel/plugins/active_model.rb +26 -7
- data/lib/sequel/plugins/instance_filters.rb +98 -0
- data/lib/sequel/plugins/many_through_many.rb +1 -1
- data/lib/sequel/plugins/optimistic_locking.rb +25 -9
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/mssql_spec.rb +26 -0
- data/spec/adapters/mysql_spec.rb +33 -4
- data/spec/adapters/postgres_spec.rb +24 -1
- data/spec/adapters/spec_helper.rb +6 -0
- data/spec/adapters/sqlite_spec.rb +28 -0
- data/spec/core/connection_pool_spec.rb +17 -5
- data/spec/core/database_spec.rb +101 -1
- data/spec/core/dataset_spec.rb +42 -4
- data/spec/core/schema_spec.rb +13 -0
- data/spec/extensions/active_model_spec.rb +34 -11
- data/spec/extensions/caching_spec.rb +2 -0
- data/spec/extensions/instance_filters_spec.rb +55 -0
- data/spec/extensions/spec_helper.rb +2 -0
- data/spec/integration/dataset_test.rb +12 -1
- data/spec/integration/model_test.rb +12 -0
- data/spec/integration/plugin_test.rb +61 -1
- data/spec/integration/schema_test.rb +14 -3
- data/spec/model/base_spec.rb +27 -0
- data/spec/model/plugins_spec.rb +0 -22
- data/spec/model/record_spec.rb +32 -1
- data/spec/model/spec_helper.rb +2 -0
- metadata +14 -3
- data/lib/sequel/dataset/convenience.rb +0 -326
data/lib/sequel/model.rb
CHANGED
@@ -51,7 +51,7 @@ module Sequel
|
|
51
51
|
last left_join left_outer_join limit lock_style map max min multi_insert naked
|
52
52
|
natural_full_join natural_join natural_left_join natural_right_join order order_by
|
53
53
|
order_more paginate print qualify query range reverse reverse_order right_join right_outer_join
|
54
|
-
select select_all select_hash select_map select_more select_order_map
|
54
|
+
select select_all select_append select_hash select_map select_more select_order_map
|
55
55
|
server set set_defaults set_graph_aliases set_overrides
|
56
56
|
single_value sum to_csv to_hash truncate unfiltered ungraphed ungrouped union unlimited unordered
|
57
57
|
update where with with_recursive with_sql'.map{|x| x.to_sym}
|
@@ -72,7 +72,8 @@ module Sequel
|
|
72
72
|
# If the value is nil, the superclass's instance variable is used directly in the subclass.
|
73
73
|
INHERITED_INSTANCE_VARIABLES = {:@allowed_columns=>:dup, :@dataset_methods=>:dup,
|
74
74
|
:@dataset_method_modules=>:dup, :@primary_key=>nil, :@use_transactions=>nil,
|
75
|
-
:@raise_on_save_failure=>nil, :@
|
75
|
+
:@raise_on_save_failure=>nil, :@require_modification=>nil,
|
76
|
+
:@restricted_columns=>:dup, :@restrict_primary_key=>nil,
|
76
77
|
:@simple_pk=>nil, :@simple_table=>nil, :@strict_param_setting=>nil,
|
77
78
|
:@typecast_empty_string_to_nil=>nil, :@typecast_on_assignment=>nil,
|
78
79
|
:@raise_on_typecast_failure=>nil, :@plugins=>:dup}
|
@@ -100,6 +101,7 @@ module Sequel
|
|
100
101
|
@primary_key = :id
|
101
102
|
@raise_on_save_failure = true
|
102
103
|
@raise_on_typecast_failure = true
|
104
|
+
@require_modification = nil
|
103
105
|
@restrict_primary_key = true
|
104
106
|
@restricted_columns = nil
|
105
107
|
@simple_pk = nil
|
@@ -410,17 +410,17 @@ module Sequel
|
|
410
410
|
# end
|
411
411
|
#
|
412
412
|
# The project class now has the following instance methods:
|
413
|
-
#
|
414
|
-
#
|
415
|
-
#
|
416
|
-
#
|
417
|
-
#
|
418
|
-
#
|
419
|
-
#
|
420
|
-
#
|
421
|
-
#
|
422
|
-
#
|
423
|
-
#
|
413
|
+
# portfolio :: Returns the associated portfolio.
|
414
|
+
# portfolio=(obj) :: Sets the associated portfolio to the object,
|
415
|
+
# but the change is not persisted until you save the record (for many_to_one associations).
|
416
|
+
# portfolio_dataset :: Returns a dataset that would return the associated
|
417
|
+
# portfolio, only useful in fairly specific circumstances.
|
418
|
+
# milestones :: Returns an array of associated milestones
|
419
|
+
# add_milestone(obj) :: Associates the passed milestone with this object.
|
420
|
+
# remove_milestone(obj) :: Removes the association with the passed milestone.
|
421
|
+
# remove_all_milestones :: Removes associations with all associated milestones.
|
422
|
+
# milestones_dataset :: Returns a dataset that would return the associated
|
423
|
+
# milestones, allowing for further filtering/limiting/etc.
|
424
424
|
#
|
425
425
|
# If you want to override the behavior of the add_/remove_/remove_all_/ methods
|
426
426
|
# or the association setter method, there are private instance methods created that are prepended
|
@@ -439,6 +439,10 @@ module Sequel
|
|
439
439
|
# => [:portfolio, :milestones]
|
440
440
|
# Project.association_reflection(:portfolio)
|
441
441
|
# => {:type => :many_to_one, :name => :portfolio, :class_name => "Portfolio"}
|
442
|
+
#
|
443
|
+
# For a more in depth general overview, as well as a reference guide,
|
444
|
+
# see the {Association Basics page}[link:files/doc/association_basics_rdoc.html].
|
445
|
+
# For examples of advanced usage, see the {Advanced Associations page}[link:files/doc/advanced_associations_rdoc.html].
|
442
446
|
module ClassMethods
|
443
447
|
# All association reflections defined for this model (default: none).
|
444
448
|
attr_reader :association_reflections
|
@@ -564,7 +568,7 @@ module Sequel
|
|
564
568
|
# - :primary_key - column in the associated table that :key option references, as a symbol.
|
565
569
|
# Defaults to the primary key of the associated table. Can use an
|
566
570
|
# array of symbols for a composite key association.
|
567
|
-
# * :one_to_many:
|
571
|
+
# * :one_to_many and :one_to_one:
|
568
572
|
# - :key - foreign key in associated model's table that references
|
569
573
|
# current model's primary key, as a symbol. Defaults to
|
570
574
|
# :"#{self.name.underscore}_id". Can use an
|
@@ -618,7 +622,7 @@ module Sequel
|
|
618
622
|
opts[:order_eager_graph] = true unless opts.include?(:order_eager_graph)
|
619
623
|
conds = opts[:conditions]
|
620
624
|
opts[:graph_conditions] = conds if !opts.include?(:graph_conditions) and Sequel.condition_specifier?(conds)
|
621
|
-
opts[:graph_conditions] = opts
|
625
|
+
opts[:graph_conditions] = opts.fetch(:graph_conditions, []).to_a
|
622
626
|
opts[:graph_select] = Array(opts[:graph_select]) if opts[:graph_select]
|
623
627
|
[:before_add, :before_remove, :after_add, :after_remove, :after_load, :before_set, :after_set, :extend].each do |cb_type|
|
624
628
|
opts[cb_type] = Array(opts[cb_type])
|
@@ -747,7 +751,7 @@ module Sequel
|
|
747
751
|
opts[:cartesian_product_number] ||= 1
|
748
752
|
join_table = (opts[:join_table] ||= opts.default_join_table)
|
749
753
|
left_key_alias = opts[:left_key_alias] ||= opts.default_associated_key_alias
|
750
|
-
graph_jt_conds = opts[:graph_join_table_conditions] = opts
|
754
|
+
graph_jt_conds = opts[:graph_join_table_conditions] = opts.fetch(:graph_join_table_conditions, []).to_a
|
751
755
|
opts[:graph_join_table_join_type] ||= opts[:graph_join_type]
|
752
756
|
opts[:after_load].unshift(:array_uniq!) if opts[:uniq]
|
753
757
|
opts[:dataset] ||= proc{opts.associated_class.inner_join(join_table, rcks.zip(opts.right_primary_keys) + lcks.zip(lcpks.map{|k| send(k)}))}
|
@@ -960,6 +964,13 @@ module Sequel
|
|
960
964
|
|
961
965
|
# Private instance methods used to implement the associations support.
|
962
966
|
module InstanceMethods
|
967
|
+
# The currently cached associations. A hash with the keys being the
|
968
|
+
# association name symbols and the values being the associated object
|
969
|
+
# or nil (many_to_one), or the array of associated objects (*_to_many).
|
970
|
+
def associations
|
971
|
+
@associations ||= {}
|
972
|
+
end
|
973
|
+
|
963
974
|
# Used internally by the associations code, like pk but doesn't raise
|
964
975
|
# an Error if the model has no primary key.
|
965
976
|
def pk_or_nil
|
@@ -1004,6 +1015,12 @@ module Sequel
|
|
1004
1015
|
end
|
1005
1016
|
end
|
1006
1017
|
end
|
1018
|
+
|
1019
|
+
# Clear the associations cache when refreshing
|
1020
|
+
def _refresh(dataset)
|
1021
|
+
associations.clear
|
1022
|
+
super
|
1023
|
+
end
|
1007
1024
|
|
1008
1025
|
# Add the given associated object to the given association
|
1009
1026
|
def add_associated_object(opts, o, *args)
|
data/lib/sequel/model/base.rb
CHANGED
@@ -42,6 +42,11 @@ module Sequel
|
|
42
42
|
# plugins) in connection with option to check for typecast failures for
|
43
43
|
# columns that aren't blobs or strings.
|
44
44
|
attr_accessor :raise_on_typecast_failure
|
45
|
+
|
46
|
+
# Whether to raise an error if an UPDATE or DELETE query related to
|
47
|
+
# a model instance does not modify exactly 1 row. If set to false,
|
48
|
+
# Sequel will not check the number of rows modified (default: true).
|
49
|
+
attr_accessor :require_modification
|
45
50
|
|
46
51
|
# Which columns are specifically restricted in a call to set/update/new/etc.
|
47
52
|
# (default: not set). Some columns are restricted regardless of
|
@@ -282,6 +287,7 @@ module Sequel
|
|
282
287
|
raise(Error, "Model.set_dataset takes a Symbol or a Sequel::Dataset")
|
283
288
|
end
|
284
289
|
@dataset.row_proc = Proc.new{|r| load(r)}
|
290
|
+
@require_modification = Sequel::Model.require_modification.nil? ? @dataset.provides_accurate_rows_matched? : Sequel::Model.require_modification
|
285
291
|
if inherited
|
286
292
|
@simple_table = superclass.simple_table
|
287
293
|
@columns = @dataset.columns rescue nil
|
@@ -493,7 +499,7 @@ module Sequel
|
|
493
499
|
# * The following instance_methods all call the class method of the same
|
494
500
|
# name: columns, dataset, db, primary_key, db_schema.
|
495
501
|
# * The following instance methods allow boolean flags to be set on a per-object
|
496
|
-
# basis: raise_on_save_failure, raise_on_typecast_failure, strict_param_setting,
|
502
|
+
# basis: raise_on_save_failure, raise_on_typecast_failure, require_modification, strict_param_setting,
|
497
503
|
# typecast_empty_string_to_nil, typecast_on_assignment, use_transactions.
|
498
504
|
# If they are not used, the object will default to whatever the model setting is.
|
499
505
|
module InstanceMethods
|
@@ -518,7 +524,7 @@ module Sequel
|
|
518
524
|
private_class_method :class_attr_overridable, :class_attr_reader
|
519
525
|
|
520
526
|
class_attr_reader :columns, :db, :primary_key, :db_schema
|
521
|
-
class_attr_overridable :raise_on_save_failure, :raise_on_typecast_failure, :strict_param_setting, :typecast_empty_string_to_nil, :typecast_on_assignment, :use_transactions
|
527
|
+
class_attr_overridable :raise_on_save_failure, :raise_on_typecast_failure, :require_modification, :strict_param_setting, :typecast_empty_string_to_nil, :typecast_on_assignment, :use_transactions
|
522
528
|
|
523
529
|
# The hash of attribute values. Keys are symbols with the names of the
|
524
530
|
# underlying database columns.
|
@@ -587,13 +593,6 @@ module Sequel
|
|
587
593
|
# this alias makes it so you can use model instead of
|
588
594
|
# self.class.
|
589
595
|
alias_method :model, :class
|
590
|
-
|
591
|
-
# The currently cached associations. A hash with the keys being the
|
592
|
-
# association name symbols and the values being the associated object
|
593
|
-
# or nil (many_to_one), or the array of associated objects (*_to_many).
|
594
|
-
def associations
|
595
|
-
@associations ||= {}
|
596
|
-
end
|
597
596
|
|
598
597
|
# The autoincrementing primary key for this model object. Should be
|
599
598
|
# overridden if you have a composite primary key with one part of it
|
@@ -611,7 +610,7 @@ module Sequel
|
|
611
610
|
# Deletes and returns self. Does not run destroy hooks.
|
612
611
|
# Look into using destroy instead.
|
613
612
|
def delete
|
614
|
-
|
613
|
+
_delete
|
615
614
|
self
|
616
615
|
end
|
617
616
|
|
@@ -826,6 +825,19 @@ module Sequel
|
|
826
825
|
end
|
827
826
|
|
828
827
|
private
|
828
|
+
|
829
|
+
# Actually do the deletion of the object's dataset.
|
830
|
+
def _delete
|
831
|
+
n = _delete_dataset.delete
|
832
|
+
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
|
833
|
+
n
|
834
|
+
end
|
835
|
+
|
836
|
+
# The dataset to use when deleting the object. The same as the object's
|
837
|
+
# dataset by default.
|
838
|
+
def _delete_dataset
|
839
|
+
this
|
840
|
+
end
|
829
841
|
|
830
842
|
# Internal destroy method, separted from destroy to
|
831
843
|
# allow running inside a transaction
|
@@ -864,7 +876,6 @@ module Sequel
|
|
864
876
|
def _refresh(dataset)
|
865
877
|
set_values(dataset.first || raise(Error, "Record not found"))
|
866
878
|
changed_columns.clear
|
867
|
-
associations.clear
|
868
879
|
self
|
869
880
|
end
|
870
881
|
|
@@ -909,7 +920,15 @@ module Sequel
|
|
909
920
|
|
910
921
|
# Update this instance's dataset with the supplied column hash.
|
911
922
|
def _update(columns)
|
912
|
-
|
923
|
+
n = _update_dataset.update(columns)
|
924
|
+
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
|
925
|
+
n
|
926
|
+
end
|
927
|
+
|
928
|
+
# The dataset to use when updating an object. The same as the object's
|
929
|
+
# dataset by default.
|
930
|
+
def _update_dataset
|
931
|
+
this
|
913
932
|
end
|
914
933
|
|
915
934
|
# If raise_on_save_failure is false, check for BeforeHookFailed
|
@@ -1014,7 +1033,7 @@ module Sequel
|
|
1014
1033
|
# option is present in the hash, use that, otherwise, fallback to the
|
1015
1034
|
# object's default (if set), or class's default (if not).
|
1016
1035
|
def use_transaction?(opts = {})
|
1017
|
-
opts.
|
1036
|
+
opts.fetch(:transaction, use_transactions)
|
1018
1037
|
end
|
1019
1038
|
end
|
1020
1039
|
|
@@ -1,5 +1,12 @@
|
|
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
|
4
|
+
|
5
|
+
# Exception class raised when require_modification is set and an UPDATE or DELETE statement to modify the dataset doesn't
|
6
|
+
# modify a single row.
|
7
|
+
class NoExistingObject < Error; end
|
8
|
+
|
9
|
+
# Exception class raised when raise_on_save_failure is set and validation fails
|
3
10
|
class ValidationFailed < Error
|
4
11
|
def initialize(errors)
|
5
12
|
if errors.respond_to?(:full_messages)
|
@@ -11,7 +18,4 @@ module Sequel
|
|
11
18
|
end
|
12
19
|
attr_reader :errors
|
13
20
|
end
|
14
|
-
|
15
|
-
# This exception will be raised when raise_on_save_failure is set and a before hook returns false
|
16
|
-
class BeforeHookFailed < Error; end
|
17
21
|
end
|
data/lib/sequel/model/plugins.rb
CHANGED
@@ -28,24 +28,14 @@ module Sequel
|
|
28
28
|
# the camelized plugin name under Sequel::Plugins.
|
29
29
|
def self.plugin(plugin, *args, &blk)
|
30
30
|
arg = args.first
|
31
|
-
block = args.length > 1 ? lambda{args} : lambda{arg}
|
32
31
|
m = plugin.is_a?(Module) ? plugin : plugin_module(plugin)
|
33
32
|
unless @plugins.include?(m)
|
34
33
|
@plugins << m
|
35
34
|
m.apply(self, *args, &blk) if m.respond_to?(:apply)
|
36
|
-
if m.const_defined?("InstanceMethods")
|
37
|
-
|
38
|
-
include(m::InstanceMethods)
|
39
|
-
end
|
40
|
-
if m.const_defined?("ClassMethods")
|
41
|
-
meta_def(:"#{plugin}_opts", &block)
|
42
|
-
extend(m::ClassMethods)
|
43
|
-
end
|
35
|
+
include(m::InstanceMethods) if m.const_defined?("InstanceMethods")
|
36
|
+
extend(m::ClassMethods)if m.const_defined?("ClassMethods")
|
44
37
|
if m.const_defined?("DatasetMethods")
|
45
|
-
if @dataset
|
46
|
-
dataset.meta_def(:"#{plugin}_opts", &block)
|
47
|
-
dataset.extend(m::DatasetMethods)
|
48
|
-
end
|
38
|
+
dataset.extend(m::DatasetMethods) if @dataset
|
49
39
|
dataset_method_modules << m::DatasetMethods
|
50
40
|
meths = m::DatasetMethods.public_instance_methods.reject{|x| NORMAL_METHOD_NAME_REGEXP !~ x.to_s}
|
51
41
|
def_dataset_method(*meths) unless meths.empty?
|
@@ -11,6 +11,9 @@ module Sequel
|
|
11
11
|
ClassMethods = ::ActiveModel::Naming
|
12
12
|
|
13
13
|
module InstanceMethods
|
14
|
+
# The default string to join composite primary keys with in to_param.
|
15
|
+
DEFAULT_TO_PARAM_JOINER = '-'.freeze
|
16
|
+
|
14
17
|
# Record that an object was destroyed, for later use by
|
15
18
|
# destroyed?
|
16
19
|
def after_destroy
|
@@ -18,15 +21,16 @@ module Sequel
|
|
18
21
|
@destroyed = true
|
19
22
|
end
|
20
23
|
|
21
|
-
#
|
22
|
-
|
23
|
-
|
24
|
-
@destroyed == true
|
24
|
+
# False if the object is new? or has been destroyed, true otherwise.
|
25
|
+
def persisted?
|
26
|
+
!new? && @destroyed != true
|
25
27
|
end
|
26
28
|
|
27
|
-
# An
|
28
|
-
def
|
29
|
-
|
29
|
+
# An array of primary key values, or nil if the object is not persisted.
|
30
|
+
def to_key
|
31
|
+
if persisted?
|
32
|
+
primary_key.is_a?(Symbol) ? [pk] : pk
|
33
|
+
end
|
30
34
|
end
|
31
35
|
|
32
36
|
# With the ActiveModel plugin, Sequel model objects are already
|
@@ -34,6 +38,21 @@ module Sequel
|
|
34
38
|
def to_model
|
35
39
|
self
|
36
40
|
end
|
41
|
+
|
42
|
+
# An string representing the object's primary key. For composite
|
43
|
+
# primary keys, joins them with to_param_joiner.
|
44
|
+
def to_param
|
45
|
+
if k = to_key
|
46
|
+
k.join(to_param_joiner)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
# The string to use to join composite primary key param strings.
|
53
|
+
def to_param_joiner
|
54
|
+
DEFAULT_TO_PARAM_JOINER
|
55
|
+
end
|
37
56
|
end
|
38
57
|
end
|
39
58
|
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
module Sequel
|
2
|
+
module Plugins
|
3
|
+
# This plugin allows you to add filters on a per object basis that
|
4
|
+
# restrict updating or deleting the object. It's designed for cases
|
5
|
+
# where you would normally have to drop down to the dataset level
|
6
|
+
# to get the necessary control, because you only want to delete or
|
7
|
+
# update the rows in certain cases based on the current status of
|
8
|
+
# the row in the database.
|
9
|
+
#
|
10
|
+
# class Item < Sequel::Model
|
11
|
+
# plugin :instance_filters
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# # These are two separate objects that represent the same
|
15
|
+
# # database row.
|
16
|
+
# i1 = Item.first(:id=>1, :delete_allowed=>false)
|
17
|
+
# i2 = Item.first(:id=>1, :delete_allowed=>false)
|
18
|
+
#
|
19
|
+
# # Add an instance filter to the object. This filter is in effect
|
20
|
+
# # until the object is successfully updated or deleted.
|
21
|
+
# i1.instance_filter(:delete_allowed=>true)
|
22
|
+
#
|
23
|
+
# # Attempting to delete the object where the filter doesn't
|
24
|
+
# # match any rows raises an error.
|
25
|
+
# i1.delete # raises Sequel::Error
|
26
|
+
#
|
27
|
+
# # The other object that represents the same row has no
|
28
|
+
# # instance filters, and can be updated normally.
|
29
|
+
# i2.update(:delete_allowed=>true)
|
30
|
+
#
|
31
|
+
# # Even though the filter is now still in effect, since the
|
32
|
+
# # database row has been updated to allow deleting,
|
33
|
+
# # delete now works.
|
34
|
+
# i1.delete
|
35
|
+
#
|
36
|
+
# This plugin sets the require_modification flag on the model,
|
37
|
+
# so if the model's dataset doesn't provide an accurate number
|
38
|
+
# of matched rows, this could result in invalid exceptions being raised.
|
39
|
+
module InstanceFilters
|
40
|
+
# Exception class raised when updating or deleting an object does
|
41
|
+
# not affect exactly one row.
|
42
|
+
Error = Sequel::NoExistingObject
|
43
|
+
|
44
|
+
# Set the require_modification flag to true for the model.
|
45
|
+
def self.configure(model)
|
46
|
+
model.require_modification = true
|
47
|
+
end
|
48
|
+
|
49
|
+
module InstanceMethods
|
50
|
+
# Clear the instance filters after successfully destroying the object.
|
51
|
+
def after_destroy
|
52
|
+
super
|
53
|
+
clear_instance_filters
|
54
|
+
end
|
55
|
+
|
56
|
+
# Clear the instance filters after successfully updating the object.
|
57
|
+
def after_update
|
58
|
+
super
|
59
|
+
clear_instance_filters
|
60
|
+
end
|
61
|
+
|
62
|
+
# Add an instance filter to the array of instance filters
|
63
|
+
# Both the arguments given and the block are passed to the
|
64
|
+
# dataset's filter method.
|
65
|
+
def instance_filter(*args, &block)
|
66
|
+
instance_filters << [args, block]
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
# Lazily initialize the instance filter array.
|
72
|
+
def instance_filters
|
73
|
+
@instance_filters ||= []
|
74
|
+
end
|
75
|
+
|
76
|
+
# Apply the instance filters to the given dataset
|
77
|
+
def apply_instance_filters(ds)
|
78
|
+
instance_filters.inject(ds){|ds, i| ds.filter(*i[0], &i[1])}
|
79
|
+
end
|
80
|
+
|
81
|
+
# Clear the instance filters.
|
82
|
+
def clear_instance_filters
|
83
|
+
instance_filters.clear
|
84
|
+
end
|
85
|
+
|
86
|
+
# Apply the instance filters to the dataset returned by super.
|
87
|
+
def _delete_dataset
|
88
|
+
apply_instance_filters(super)
|
89
|
+
end
|
90
|
+
|
91
|
+
# Apply the instance filters to the dataset returned by super.
|
92
|
+
def _update_dataset
|
93
|
+
apply_instance_filters(super)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -193,7 +193,7 @@ module Sequel
|
|
193
193
|
opts[:eager_grapher] ||= proc do |ds, assoc_alias, table_alias|
|
194
194
|
iq = table_alias
|
195
195
|
opts.edges.each do |t|
|
196
|
-
ds = ds.graph(t[:table], t.
|
196
|
+
ds = ds.graph(t[:table], t.fetch(:only_conditions, (Array(t[:right]).zip(Array(t[:left])) + t[:conditions])), :select=>false, :table_alias=>ds.unused_table_alias(t[:table]), :join_type=>t[:join_type], :implicit_qualifier=>iq, &t[:block])
|
197
197
|
iq = nil
|
198
198
|
end
|
199
199
|
fe = opts[:final_edge]
|
@@ -1,4 +1,6 @@
|
|
1
1
|
module Sequel
|
2
|
+
require 'plugins/instance_filters'
|
3
|
+
|
2
4
|
module Plugins
|
3
5
|
# This plugin implements a simple database-independent locking mechanism
|
4
6
|
# to ensure that concurrent updates do not override changes. This is
|
@@ -16,10 +18,14 @@ module Sequel
|
|
16
18
|
# table has a lock_version column (or other column you name via the lock_column
|
17
19
|
# class level accessor) that defaults to 0.
|
18
20
|
#
|
19
|
-
# This plugin
|
21
|
+
# This plugin relies on the instance_filters plugin.
|
20
22
|
module OptimisticLocking
|
21
23
|
# Exception class raised when trying to update or destroy a stale object.
|
22
|
-
|
24
|
+
Error = InstanceFilters::Error
|
25
|
+
|
26
|
+
# Load the instance_filters plugin into the model.
|
27
|
+
def self.apply(model, opts={})
|
28
|
+
model.plugin :instance_filters
|
23
29
|
end
|
24
30
|
|
25
31
|
# Set the lock_column to the :lock_column option, or :lock_version if
|
@@ -40,23 +46,33 @@ module Sequel
|
|
40
46
|
end
|
41
47
|
|
42
48
|
module InstanceMethods
|
49
|
+
# Add the lock column instance filter to the object before destroying it.
|
50
|
+
def before_destroy
|
51
|
+
lock_column_instance_filter
|
52
|
+
super
|
53
|
+
end
|
54
|
+
|
55
|
+
# Add the lock column instance filter to the object before updating it.
|
56
|
+
def before_update
|
57
|
+
lock_column_instance_filter
|
58
|
+
super
|
59
|
+
end
|
60
|
+
|
43
61
|
private
|
44
62
|
|
45
|
-
#
|
46
|
-
|
47
|
-
def _destroy_delete
|
63
|
+
# Add the lock column instance filter to the object.
|
64
|
+
def lock_column_instance_filter
|
48
65
|
lc = model.lock_column
|
49
|
-
|
66
|
+
instance_filter(lc=>send(lc))
|
50
67
|
end
|
51
68
|
|
52
69
|
# Only update the row if it has the same lock version, and increment the
|
53
|
-
# lock version.
|
54
|
-
# an Error.
|
70
|
+
# lock version.
|
55
71
|
def _update(columns)
|
56
72
|
lc = model.lock_column
|
57
73
|
lcv = send(lc)
|
58
74
|
columns[lc] = lcv + 1
|
59
|
-
|
75
|
+
super
|
60
76
|
send("#{lc}=", lcv + 1)
|
61
77
|
end
|
62
78
|
end
|