sequel 3.10.0 → 3.11.0

Sign up to get free protection for your applications and to get access to all the features.
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