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.
Files changed (87) hide show
  1. data/CHANGELOG +68 -0
  2. data/COPYING +1 -1
  3. data/README.rdoc +87 -27
  4. data/bin/sequel +2 -4
  5. data/doc/association_basics.rdoc +1383 -0
  6. data/doc/dataset_basics.rdoc +106 -0
  7. data/doc/opening_databases.rdoc +45 -16
  8. data/doc/querying.rdoc +210 -0
  9. data/doc/release_notes/3.11.0.txt +254 -0
  10. data/doc/virtual_rows.rdoc +217 -31
  11. data/lib/sequel/adapters/ado.rb +28 -12
  12. data/lib/sequel/adapters/ado/mssql.rb +33 -1
  13. data/lib/sequel/adapters/amalgalite.rb +13 -8
  14. data/lib/sequel/adapters/db2.rb +1 -2
  15. data/lib/sequel/adapters/dbi.rb +7 -4
  16. data/lib/sequel/adapters/do.rb +14 -15
  17. data/lib/sequel/adapters/do/postgres.rb +4 -5
  18. data/lib/sequel/adapters/do/sqlite.rb +9 -0
  19. data/lib/sequel/adapters/firebird.rb +5 -10
  20. data/lib/sequel/adapters/informix.rb +2 -4
  21. data/lib/sequel/adapters/jdbc.rb +111 -49
  22. data/lib/sequel/adapters/jdbc/mssql.rb +1 -2
  23. data/lib/sequel/adapters/jdbc/mysql.rb +11 -0
  24. data/lib/sequel/adapters/jdbc/oracle.rb +4 -7
  25. data/lib/sequel/adapters/jdbc/postgresql.rb +8 -1
  26. data/lib/sequel/adapters/jdbc/sqlite.rb +12 -0
  27. data/lib/sequel/adapters/mysql.rb +14 -5
  28. data/lib/sequel/adapters/odbc.rb +2 -4
  29. data/lib/sequel/adapters/odbc/mssql.rb +2 -4
  30. data/lib/sequel/adapters/openbase.rb +1 -2
  31. data/lib/sequel/adapters/oracle.rb +4 -8
  32. data/lib/sequel/adapters/postgres.rb +4 -11
  33. data/lib/sequel/adapters/shared/mssql.rb +22 -9
  34. data/lib/sequel/adapters/shared/mysql.rb +33 -30
  35. data/lib/sequel/adapters/shared/oracle.rb +0 -5
  36. data/lib/sequel/adapters/shared/postgres.rb +13 -11
  37. data/lib/sequel/adapters/shared/sqlite.rb +56 -10
  38. data/lib/sequel/adapters/sqlite.rb +16 -9
  39. data/lib/sequel/connection_pool.rb +6 -1
  40. data/lib/sequel/connection_pool/single.rb +1 -0
  41. data/lib/sequel/core.rb +6 -1
  42. data/lib/sequel/database.rb +52 -23
  43. data/lib/sequel/database/schema_generator.rb +6 -0
  44. data/lib/sequel/database/schema_methods.rb +5 -5
  45. data/lib/sequel/database/schema_sql.rb +1 -1
  46. data/lib/sequel/dataset.rb +4 -190
  47. data/lib/sequel/dataset/actions.rb +323 -1
  48. data/lib/sequel/dataset/features.rb +18 -2
  49. data/lib/sequel/dataset/graph.rb +7 -0
  50. data/lib/sequel/dataset/misc.rb +119 -0
  51. data/lib/sequel/dataset/mutation.rb +64 -0
  52. data/lib/sequel/dataset/prepared_statements.rb +6 -0
  53. data/lib/sequel/dataset/query.rb +272 -6
  54. data/lib/sequel/dataset/sql.rb +186 -394
  55. data/lib/sequel/model.rb +4 -2
  56. data/lib/sequel/model/associations.rb +31 -14
  57. data/lib/sequel/model/base.rb +32 -13
  58. data/lib/sequel/model/exceptions.rb +8 -4
  59. data/lib/sequel/model/plugins.rb +3 -13
  60. data/lib/sequel/plugins/active_model.rb +26 -7
  61. data/lib/sequel/plugins/instance_filters.rb +98 -0
  62. data/lib/sequel/plugins/many_through_many.rb +1 -1
  63. data/lib/sequel/plugins/optimistic_locking.rb +25 -9
  64. data/lib/sequel/version.rb +1 -1
  65. data/spec/adapters/mssql_spec.rb +26 -0
  66. data/spec/adapters/mysql_spec.rb +33 -4
  67. data/spec/adapters/postgres_spec.rb +24 -1
  68. data/spec/adapters/spec_helper.rb +6 -0
  69. data/spec/adapters/sqlite_spec.rb +28 -0
  70. data/spec/core/connection_pool_spec.rb +17 -5
  71. data/spec/core/database_spec.rb +101 -1
  72. data/spec/core/dataset_spec.rb +42 -4
  73. data/spec/core/schema_spec.rb +13 -0
  74. data/spec/extensions/active_model_spec.rb +34 -11
  75. data/spec/extensions/caching_spec.rb +2 -0
  76. data/spec/extensions/instance_filters_spec.rb +55 -0
  77. data/spec/extensions/spec_helper.rb +2 -0
  78. data/spec/integration/dataset_test.rb +12 -1
  79. data/spec/integration/model_test.rb +12 -0
  80. data/spec/integration/plugin_test.rb +61 -1
  81. data/spec/integration/schema_test.rb +14 -3
  82. data/spec/model/base_spec.rb +27 -0
  83. data/spec/model/plugins_spec.rb +0 -22
  84. data/spec/model/record_spec.rb +32 -1
  85. data/spec/model/spec_helper.rb +2 -0
  86. metadata +14 -3
  87. 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, :@restricted_columns=>:dup, :@restrict_primary_key=>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
- # * 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.
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[:graph_conditions] ? opts[:graph_conditions].to_a : []
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[:graph_join_table_conditions] ? opts[:graph_join_table_conditions].to_a : []
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)
@@ -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
- this.delete
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
- this.update(columns)
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.include?(:transaction) ? opts[:transaction] : use_transactions
1036
+ opts.fetch(:transaction, use_transactions)
1018
1037
  end
1019
1038
  end
1020
1039
 
@@ -1,5 +1,12 @@
1
1
  module Sequel
2
- # This exception will be raised when raise_on_save_failure is set and validation fails
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
@@ -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
- define_method(:"#{plugin}_opts", &block)
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
- # Whether the object was destroyed by destroy. Not true
22
- # for objects that were deleted.
23
- def destroyed?
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 alias for new?
28
- def new_record?
29
- new?
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.include?(:only_conditions) ? t[: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])
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 does not work with the class_table_inheritance 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
- class Error < Sequel::Error
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
- # Only delete the object when destroying if it has the same lock version. If the row
46
- # doesn't have the same lock version, raise an error.
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
- raise(Error, "Attempt to destroy a stale object") if this.filter(lc=>send(lc)).delete != 1
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. If the row doesn't have the same lock version, raise
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
- raise(Error, "Attempt to update a stale object") if this.filter(lc=>lcv).update(columns) != 1
75
+ super
60
76
  send("#{lc}=", lcv + 1)
61
77
  end
62
78
  end