sequel 3.10.0 → 3.11.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.
- 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
|