sequel 5.19.0 → 5.24.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (91) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +102 -0
  3. data/doc/dataset_filtering.rdoc +15 -0
  4. data/doc/opening_databases.rdoc +5 -1
  5. data/doc/release_notes/5.20.0.txt +89 -0
  6. data/doc/release_notes/5.21.0.txt +87 -0
  7. data/doc/release_notes/5.22.0.txt +48 -0
  8. data/doc/release_notes/5.23.0.txt +56 -0
  9. data/doc/release_notes/5.24.0.txt +56 -0
  10. data/doc/sharding.rdoc +2 -0
  11. data/doc/testing.rdoc +1 -0
  12. data/doc/transactions.rdoc +38 -0
  13. data/lib/sequel/adapters/ado.rb +27 -19
  14. data/lib/sequel/adapters/jdbc.rb +7 -1
  15. data/lib/sequel/adapters/jdbc/mysql.rb +2 -2
  16. data/lib/sequel/adapters/jdbc/postgresql.rb +1 -13
  17. data/lib/sequel/adapters/jdbc/sqlite.rb +29 -0
  18. data/lib/sequel/adapters/mysql2.rb +2 -3
  19. data/lib/sequel/adapters/shared/mssql.rb +7 -7
  20. data/lib/sequel/adapters/shared/postgres.rb +37 -19
  21. data/lib/sequel/adapters/shared/sqlite.rb +27 -3
  22. data/lib/sequel/adapters/sqlite.rb +1 -1
  23. data/lib/sequel/adapters/tinytds.rb +12 -0
  24. data/lib/sequel/adapters/utils/mysql_mysql2.rb +2 -0
  25. data/lib/sequel/database/logging.rb +7 -1
  26. data/lib/sequel/database/query.rb +1 -1
  27. data/lib/sequel/database/schema_generator.rb +12 -3
  28. data/lib/sequel/database/schema_methods.rb +2 -0
  29. data/lib/sequel/database/transactions.rb +57 -5
  30. data/lib/sequel/dataset.rb +4 -2
  31. data/lib/sequel/dataset/actions.rb +3 -2
  32. data/lib/sequel/dataset/placeholder_literalizer.rb +4 -1
  33. data/lib/sequel/dataset/query.rb +5 -1
  34. data/lib/sequel/dataset/sql.rb +11 -7
  35. data/lib/sequel/extensions/named_timezones.rb +52 -8
  36. data/lib/sequel/extensions/pg_array.rb +4 -0
  37. data/lib/sequel/extensions/pg_json.rb +387 -123
  38. data/lib/sequel/extensions/pg_range.rb +3 -2
  39. data/lib/sequel/extensions/pg_row.rb +3 -1
  40. data/lib/sequel/extensions/schema_dumper.rb +1 -1
  41. data/lib/sequel/extensions/server_block.rb +15 -4
  42. data/lib/sequel/model/associations.rb +35 -9
  43. data/lib/sequel/model/plugins.rb +104 -0
  44. data/lib/sequel/plugins/association_dependencies.rb +3 -3
  45. data/lib/sequel/plugins/association_pks.rb +14 -4
  46. data/lib/sequel/plugins/association_proxies.rb +3 -2
  47. data/lib/sequel/plugins/class_table_inheritance.rb +11 -0
  48. data/lib/sequel/plugins/composition.rb +13 -9
  49. data/lib/sequel/plugins/finder.rb +2 -2
  50. data/lib/sequel/plugins/hook_class_methods.rb +17 -5
  51. data/lib/sequel/plugins/insert_conflict.rb +72 -0
  52. data/lib/sequel/plugins/inverted_subsets.rb +2 -2
  53. data/lib/sequel/plugins/pg_auto_constraint_validations.rb +147 -59
  54. data/lib/sequel/plugins/rcte_tree.rb +6 -0
  55. data/lib/sequel/plugins/static_cache.rb +8 -3
  56. data/lib/sequel/plugins/static_cache_cache.rb +53 -0
  57. data/lib/sequel/plugins/subset_conditions.rb +2 -2
  58. data/lib/sequel/plugins/validation_class_methods.rb +5 -3
  59. data/lib/sequel/sql.rb +15 -3
  60. data/lib/sequel/timezones.rb +50 -11
  61. data/lib/sequel/version.rb +1 -1
  62. data/spec/adapters/mssql_spec.rb +24 -0
  63. data/spec/adapters/mysql_spec.rb +0 -5
  64. data/spec/adapters/postgres_spec.rb +319 -1
  65. data/spec/bin_spec.rb +1 -1
  66. data/spec/core/database_spec.rb +123 -2
  67. data/spec/core/dataset_spec.rb +33 -1
  68. data/spec/core/expression_filters_spec.rb +25 -1
  69. data/spec/core/schema_spec.rb +24 -0
  70. data/spec/extensions/class_table_inheritance_spec.rb +30 -8
  71. data/spec/extensions/core_refinements_spec.rb +1 -1
  72. data/spec/extensions/hook_class_methods_spec.rb +22 -0
  73. data/spec/extensions/insert_conflict_spec.rb +103 -0
  74. data/spec/extensions/migration_spec.rb +13 -0
  75. data/spec/extensions/named_timezones_spec.rb +109 -2
  76. data/spec/extensions/pg_auto_constraint_validations_spec.rb +45 -0
  77. data/spec/extensions/pg_json_spec.rb +218 -29
  78. data/spec/extensions/pg_range_spec.rb +76 -9
  79. data/spec/extensions/rcte_tree_spec.rb +6 -0
  80. data/spec/extensions/s_spec.rb +1 -1
  81. data/spec/extensions/schema_dumper_spec.rb +4 -2
  82. data/spec/extensions/server_block_spec.rb +38 -0
  83. data/spec/extensions/spec_helper.rb +8 -1
  84. data/spec/extensions/static_cache_cache_spec.rb +35 -0
  85. data/spec/integration/dataset_test.rb +25 -9
  86. data/spec/integration/plugin_test.rb +42 -0
  87. data/spec/integration/schema_test.rb +7 -2
  88. data/spec/integration/transaction_test.rb +50 -0
  89. data/spec/model/associations_spec.rb +84 -4
  90. data/spec/model/plugins_spec.rb +111 -0
  91. metadata +16 -2
@@ -453,13 +453,14 @@ module Sequel
453
453
  end
454
454
 
455
455
  ENDLESS_RANGE_NOT_SUPPORTED = RUBY_VERSION < '2.6'
456
+ STARTLESS_RANGE_NOT_SUPPORTED = RUBY_VERSION < '2.7'
456
457
 
457
458
  # Return a ruby Range object for this instance, if one can be created.
458
459
  def to_range
459
460
  return @range if @range
460
461
  raise(Error, "cannot create ruby range for an empty PostgreSQL range") if empty?
461
462
  raise(Error, "cannot create ruby range when PostgreSQL range excludes beginning element") if exclude_begin?
462
- raise(Error, "cannot create ruby range when PostgreSQL range has unbounded beginning") unless self.begin
463
+ raise(Error, "cannot create ruby range when PostgreSQL range has unbounded beginning") if STARTLESS_RANGE_NOT_SUPPORTED && !self.begin
463
464
  raise(Error, "cannot create ruby range when PostgreSQL range has unbounded ending") if ENDLESS_RANGE_NOT_SUPPORTED && !self.end
464
465
  @range = Range.new(self.begin, self.end, exclude_end?)
465
466
  end
@@ -468,7 +469,7 @@ module Sequel
468
469
  # it must have a beginning and an ending (no unbounded ranges), and it cannot exclude
469
470
  # the beginning element.
470
471
  def valid_ruby_range?
471
- !(empty? || exclude_begin? || !self.begin || (ENDLESS_RANGE_NOT_SUPPORTED && !self.end))
472
+ !(empty? || exclude_begin? || (STARTLESS_RANGE_NOT_SUPPORTED && !self.begin) || (ENDLESS_RANGE_NOT_SUPPORTED && !self.end))
472
473
  end
473
474
 
474
475
  # Whether the beginning of the range is unbounded.
@@ -7,7 +7,7 @@
7
7
  # that when composite fields are retrieved, they are parsed and returned
8
8
  # as instances of Sequel::Postgres::PGRow::(HashRow|ArrayRow), or
9
9
  # optionally a custom type. HashRow and ArrayRow are DelegateClasses of
10
- # of Hash and Array, so they mostly act like a hash or array, but not
10
+ # Hash and Array, so they mostly act like a hash or array, but not
11
11
  # completely (is_a?(Hash) and is_a?(Array) are false). If you want the
12
12
  # actual hash for a HashRow, call HashRow#to_hash, and if you want the
13
13
  # actual array for an ArrayRow, call ArrayRow#to_a. This is done so
@@ -228,7 +228,9 @@ module Sequel
228
228
  if skip(/\)/)
229
229
  values << nil
230
230
  else
231
+ # :nocov:
231
232
  until eos?
233
+ # :nocov:
232
234
  if skip(/"/)
233
235
  values << scan(/(\\.|""|[^"])*/).gsub(/\\(.)|"(")/, '\1\2')
234
236
  skip(/"[,)]/)
@@ -37,7 +37,7 @@ module Sequel
37
37
  {:type =>schema[:type] == :boolean ? TrueClass : Integer}
38
38
  when /\Abigint(?:\((?:\d+)\))?(?: unsigned)?\z/
39
39
  {:type=>:Bignum}
40
- when /\A(?:real|float|double(?: precision)?|double\(\d+,\d+\)(?: unsigned)?)\z/
40
+ when /\A(?:real|float(?: unsigned)?|double(?: precision)?|double\(\d+,\d+\)(?: unsigned)?)\z/
41
41
  {:type=>Float}
42
42
  when 'boolean', 'bit', 'bool'
43
43
  {:type=>TrueClass}
@@ -52,6 +52,12 @@
52
52
  # DB[:a].server(:read_only).delete # Uses shard2
53
53
  # end
54
54
  #
55
+ # If you use an invalid server when calling with_server, it will be
56
+ # treated the same way as if you called Dataset#server with an invalid
57
+ # server. By default, the default server will be used in such cases.
58
+ # If you would like a different server to be used, or an exception to
59
+ # be raised, then use the :servers_hash Database option.
60
+ #
55
61
  # Related modules: Sequel::ServerBlock, Sequel::UnthreadedServerBlock,
56
62
  # Sequel::ThreadedServerBlock
57
63
 
@@ -110,9 +116,9 @@ module Sequel
110
116
  else
111
117
  case server
112
118
  when :default, nil
113
- @default_servers[-1][0]
119
+ @servers[@default_servers[-1][0]]
114
120
  when :read_only
115
- @default_servers[-1][1]
121
+ @servers[@default_servers[-1][1]]
116
122
  else
117
123
  super
118
124
  end
@@ -155,11 +161,16 @@ module Sequel
155
161
  if !a || a.empty?
156
162
  super
157
163
  else
164
+ # Hash handling required to work when loaded after arbitrary servers plugin.
158
165
  case server
159
166
  when :default, nil
160
- a[-1][0]
167
+ v = a[-1][0]
168
+ v = @servers[v] unless v.is_a?(Hash)
169
+ v
161
170
  when :read_only
162
- a[-1][1]
171
+ v = a[-1][1]
172
+ v = @servers[v] unless v.is_a?(Hash)
173
+ v
163
174
  else
164
175
  super
165
176
  end
@@ -1617,9 +1617,10 @@ module Sequel
1617
1617
  # is hash or array of two element arrays. Consider also specifying the :graph_block
1618
1618
  # option if the value for this option is not a hash or array of two element arrays
1619
1619
  # and you plan to use this association in eager_graph or association_join.
1620
- # :dataset :: A proc that is instance_execed to get the base dataset to use (before the other
1620
+ # :dataset :: A proc that is used to define the method to get the base dataset to use (before the other
1621
1621
  # options are applied). If the proc accepts an argument, it is passed the related
1622
- # association reflection.
1622
+ # association reflection. It is a best practice to always have the dataset accept an argument
1623
+ # and use the argument to return the appropriate dataset.
1623
1624
  # :distinct :: Use the DISTINCT clause when selecting associating object, both when
1624
1625
  # lazy loading and eager loading via .eager (but not when using .eager_graph).
1625
1626
  # :eager :: The associations to eagerly load via +eager+ when loading the associated object(s).
@@ -1909,7 +1910,7 @@ module Sequel
1909
1910
  # can be easily overridden in the class itself while allowing for
1910
1911
  # super to be called.
1911
1912
  def association_module_def(name, opts=OPTS, &block)
1912
- association_module(opts).module_eval{define_method(name, &block)}
1913
+ association_module(opts).send(:define_method, name, &block)
1913
1914
  end
1914
1915
 
1915
1916
  # Add a private method to the module included in the class.
@@ -1944,6 +1945,13 @@ module Sequel
1944
1945
  end
1945
1946
 
1946
1947
  association_module_def(opts.dataset_method, opts){_dataset(opts)}
1948
+ if opts[:block]
1949
+ opts[:block_method] = Plugins.def_sequel_method(association_module(opts), "#{opts[:name]}_block", 1, &opts[:block])
1950
+ end
1951
+ if opts[:dataset]
1952
+ opts[:dataset_opt_arity] = opts[:dataset].arity == 0 ? 0 : 1
1953
+ opts[:dataset_opt_method] = Plugins.def_sequel_method(association_module(opts), "#{opts[:name]}_dataset_opt", opts[:dataset_opt_arity], &opts[:dataset])
1954
+ end
1947
1955
  def_association_method(opts)
1948
1956
 
1949
1957
  return if opts[:read_only]
@@ -2204,12 +2212,28 @@ module Sequel
2204
2212
  if one_to_one
2205
2213
  opts[:setter] ||= proc do |o|
2206
2214
  up_ds = _apply_association_options(opts, opts.associated_dataset.where(cks.zip(cpks.map{|k| get_column_value(k)})))
2215
+
2216
+ if (froms = up_ds.opts[:from]) && (from = froms[0]) && (from.is_a?(Sequel::Dataset) || (from.is_a?(Sequel::SQL::AliasedExpression) && from.expression.is_a?(Sequel::Dataset)))
2217
+ if old = up_ds.first
2218
+ cks.each{|k| old.set_column_value(:"#{k}=", nil)}
2219
+ end
2220
+ save_old = true
2221
+ end
2222
+
2207
2223
  if o
2208
- up_ds = up_ds.exclude(o.pk_hash) unless o.new?
2224
+ if !o.new? && !save_old
2225
+ up_ds = up_ds.exclude(o.pk_hash)
2226
+ end
2209
2227
  cks.zip(cpks).each{|k, pk| o.set_column_value(:"#{k}=", get_column_value(pk))}
2210
2228
  end
2229
+
2211
2230
  checked_transaction do
2212
- up_ds.skip_limit_check.update(ck_nil_hash)
2231
+ if save_old
2232
+ old.save(save_opts) || raise(Sequel::Error, "invalid previously associated object, cannot save") if old
2233
+ else
2234
+ up_ds.skip_limit_check.update(ck_nil_hash)
2235
+ end
2236
+
2213
2237
  o.save(save_opts) || raise(Sequel::Error, "invalid associated object, cannot save") if o
2214
2238
  end
2215
2239
  end
@@ -2287,7 +2311,8 @@ module Sequel
2287
2311
  end
2288
2312
  ds = ds.clone(:model_object => self)
2289
2313
  ds = ds.eager_graph(opts[:eager_graph]) if opts[:eager_graph] && opts.eager_graph_lazy_dataset?
2290
- ds = instance_exec(ds, &opts[:block]) if opts[:block]
2314
+ # block method is private
2315
+ ds = send(opts[:block_method], ds) if opts[:block_method]
2291
2316
  ds
2292
2317
  end
2293
2318
 
@@ -2310,10 +2335,11 @@ module Sequel
2310
2335
  # Return an association dataset for the given association reflection
2311
2336
  def _dataset(opts)
2312
2337
  raise(Sequel::Error, "model object #{inspect} does not have a primary key") if opts.dataset_need_primary_key? && !pk
2313
- ds = if opts[:dataset].arity == 1
2314
- instance_exec(opts, &opts[:dataset])
2338
+ ds = if opts[:dataset_opt_arity] == 1
2339
+ # dataset_opt_method is private
2340
+ send(opts[:dataset_opt_method], opts)
2315
2341
  else
2316
- instance_exec(&opts[:dataset])
2342
+ send(opts[:dataset_opt_method])
2317
2343
  end
2318
2344
  _apply_association_options(opts, ds)
2319
2345
  end
@@ -51,5 +51,109 @@ module Sequel
51
51
  r
52
52
  end
53
53
  end
54
+
55
+ method_num = 0
56
+ method_num_mutex = Mutex.new
57
+ # Return a unique method name symbol for the given suffix.
58
+ SEQUEL_METHOD_NAME = lambda do |suffix|
59
+ :"_sequel_#{suffix}_#{method_num_mutex.synchronize{method_num += 1}}"
60
+ end
61
+
62
+ # Define a private instance method using the block with the provided name and
63
+ # expected arity. If the name is given as a Symbol, it is used directly.
64
+ # If the name is given as a String, a unique name will be generated using
65
+ # that string. The expected_arity should be either 0 (no arguments) or
66
+ # 1 (single argument).
67
+ #
68
+ # If a block with an arity that does not match the expected arity is used,
69
+ # a deprecation warning will be issued. The method defined should still
70
+ # work, though it will be slower than a method with the expected arity.
71
+ #
72
+ # Sequel only checks arity for regular blocks, not lambdas. Lambdas were
73
+ # already strict in regards to arity, so there is no need to try to fix
74
+ # arity to keep backwards compatibility for lambdas.
75
+ #
76
+ # Blocks with required keyword arguments are not supported by this method.
77
+ def self.def_sequel_method(model, meth, expected_arity, &block)
78
+ if meth.is_a?(String)
79
+ meth = SEQUEL_METHOD_NAME.call(meth)
80
+ end
81
+ call_meth = meth
82
+
83
+ unless block.lambda?
84
+ required_args, optional_args, rest, keyword = _define_sequel_method_arg_numbers(block)
85
+
86
+ if keyword == :required
87
+ raise Error, "cannot use block with required keyword arguments when calling define_sequel_method with expected arity #{expected_arity}"
88
+ end
89
+
90
+ case expected_arity
91
+ when 0
92
+ unless required_args == 0
93
+ # SEQUEL6: remove
94
+ Sequel::Deprecation.deprecate("Arity mismatch in block passed to define_sequel_method. Expected Arity 0, but arguments required for #{block.inspect}. Support for this will be removed in Sequel 6.")
95
+ b = block
96
+ block = lambda{instance_exec(&b)} # Fallback
97
+ end
98
+ when 1
99
+ if required_args == 0 && optional_args == 0 && !rest
100
+ # SEQUEL6: remove
101
+ Sequel::Deprecation.deprecate("Arity mismatch in block passed to define_sequel_method. Expected Arity 1, but no arguments accepted for #{block.inspect}. Support for this will be removed in Sequel 6.")
102
+ temp_method = SEQUEL_METHOD_NAME.call("temp")
103
+ model.class_eval("def #{temp_method}(_) #{meth =~ /\A\w+\z/ ? "#{meth}_arity" : "send(:\"#{meth}_arity\")"} end", __FILE__, __LINE__)
104
+ model.send(:alias_method, meth, temp_method)
105
+ model.send(:undef_method, temp_method)
106
+ model.send(:private, meth)
107
+ meth = :"#{meth}_arity"
108
+ elsif required_args > 1
109
+ # SEQUEL6: remove
110
+ Sequel::Deprecation.deprecate("Arity mismatch in block passed to define_sequel_method. Expected Arity 1, but more arguments required for #{block.inspect}. Support for this will be removed in Sequel 6.")
111
+ b = block
112
+ block = lambda{|r| instance_exec(r, &b)} # Fallback
113
+ end
114
+ else
115
+ raise Error, "unexpected arity passed to define_sequel_method: #{expected_arity.inspect}"
116
+ end
117
+ end
118
+
119
+ model.send(:define_method, meth, &block)
120
+ model.send(:private, meth)
121
+ call_meth
122
+ end
123
+
124
+ # Return the number of required argument, optional arguments,
125
+ # whether the callable accepts any additional arguments,
126
+ # and whether the callable accepts keyword arguments (true, false
127
+ # or :required).
128
+ def self._define_sequel_method_arg_numbers(callable)
129
+ optional_args = 0
130
+ rest = false
131
+ keyword = false
132
+ callable.parameters.map(&:first).each do |arg_type, _|
133
+ case arg_type
134
+ when :opt
135
+ optional_args += 1
136
+ when :rest
137
+ rest = true
138
+ when :keyreq
139
+ keyword = :required
140
+ when :key, :keyrest
141
+ keyword ||= true
142
+ end
143
+ end
144
+ arity = callable.arity
145
+ if arity < 0
146
+ arity = arity.abs - 1
147
+ end
148
+ required_args = arity
149
+ arity -= 1 if keyword == :required
150
+
151
+ if callable.is_a?(Proc) && !callable.lambda?
152
+ optional_args -= arity
153
+ end
154
+
155
+ [required_args, optional_args, rest, keyword]
156
+ end
157
+ private_class_method :_define_sequel_method_arg_numbers
54
158
  end
55
159
  end
@@ -60,9 +60,9 @@ module Sequel
60
60
  association_dependencies[:"#{time}_#{action}"] << if action == :nullify
61
61
  case type
62
62
  when :one_to_many , :many_to_many
63
- proc{public_send(r[:remove_all_method])}
63
+ [r[:remove_all_method]]
64
64
  when :one_to_one
65
- proc{public_send(r[:setter_method], nil)}
65
+ [r[:setter_method], nil]
66
66
  else
67
67
  raise(Error, "Can't nullify many_to_one associated objects: association: #{association}")
68
68
  end
@@ -97,7 +97,7 @@ module Sequel
97
97
  def before_destroy
98
98
  model.association_dependencies[:before_delete].each{|m| public_send(m).delete}
99
99
  model.association_dependencies[:before_destroy].each{|m| public_send(m).destroy}
100
- model.association_dependencies[:before_nullify].each{|p| instance_exec(&p)}
100
+ model.association_dependencies[:before_nullify].each{|args| public_send(*args)}
101
101
  super
102
102
  end
103
103
  end
@@ -60,8 +60,15 @@ module Sequel
60
60
 
61
61
  # Define a association_pks method using the block for the association reflection
62
62
  def def_association_pks_methods(opts)
63
+ opts[:pks_getter_method] = :"#{singularize(opts[:name])}_pks_getter"
64
+ association_module_def(opts[:pks_getter_method], &opts[:pks_getter])
63
65
  association_module_def(:"#{singularize(opts[:name])}_pks", opts){_association_pks_getter(opts)}
64
- association_module_def(:"#{singularize(opts[:name])}_pks=", opts){|pks| _association_pks_setter(opts, pks)} if opts[:pks_setter]
66
+
67
+ if opts[:pks_setter]
68
+ opts[:pks_setter_method] = :"#{singularize(opts[:name])}_pks_setter"
69
+ association_module_def(opts[:pks_setter_method], &opts[:pks_setter])
70
+ association_module_def(:"#{singularize(opts[:name])}_pks=", opts){|pks| _association_pks_setter(opts, pks)}
71
+ end
65
72
  end
66
73
 
67
74
  # Add a getter that checks the join table for matching records and
@@ -181,7 +188,8 @@ module Sequel
181
188
  def after_save
182
189
  if assoc_pks = @_association_pks
183
190
  assoc_pks.each do |name, pks|
184
- instance_exec(pks, &model.association_reflection(name)[:pks_setter])
191
+ # pks_setter_method is private
192
+ send(model.association_reflection(name)[:pks_setter_method], pks)
185
193
  end
186
194
  @_association_pks = nil
187
195
  end
@@ -206,7 +214,8 @@ module Sequel
206
214
  elsif delay && @_association_pks && (objs = @_association_pks[opts[:name]])
207
215
  objs
208
216
  else
209
- instance_exec(&opts[:pks_getter])
217
+ # pks_getter_method is private
218
+ send(opts[:pks_getter_method])
210
219
  end
211
220
  end
212
221
 
@@ -231,7 +240,8 @@ module Sequel
231
240
  modified!
232
241
  (@_association_pks ||= {})[opts[:name]] = pks
233
242
  else
234
- instance_exec(pks, &opts[:pks_setter])
243
+ # pks_setter_method is private
244
+ send(opts[:pks_setter_method], pks)
235
245
  end
236
246
  end
237
247
 
@@ -64,6 +64,8 @@ module Sequel
64
64
  array = [].freeze
65
65
 
66
66
  if RUBY_VERSION < '2.6'
67
+ # :nocov:
68
+
67
69
  # Default proc used to determine whether to send the method to the dataset.
68
70
  # If the array would respond to it, sends it to the array instead of the dataset.
69
71
  DEFAULT_PROXY_TO_DATASET = proc do |opts|
@@ -73,10 +75,9 @@ module Sequel
73
75
  end
74
76
  !array_method
75
77
  end
76
- else
77
78
  # :nocov:
79
+ else
78
80
  DEFAULT_PROXY_TO_DATASET = proc{|opts| !array.respond_to?(opts[:method])}
79
- # :nocov:
80
81
  end
81
82
 
82
83
  # Set the association reflection to use, and whether the association should be
@@ -108,6 +108,16 @@ module Sequel
108
108
  # a = Executive.where{{employees[:id]=>1}}.first # works
109
109
  # a = Executive.where{{executives[:id]=>1}}.first # doesn't work
110
110
  #
111
+ # Note that because subclass datasets select from a subquery, you cannot update,
112
+ # delete, or insert into them directly. To delete related rows, you need to go
113
+ # through the related tables and remove the related rows. Code that does this would
114
+ # be similar to:
115
+ #
116
+ # pks = Executive.where{num_staff < 10}.select_map(:id)
117
+ # Executive.cti_tables.reverse_each do |table|
118
+ # DB.from(table).where(:id=>pks).delete
119
+ # end
120
+ #
111
121
  # = Usage
112
122
  #
113
123
  # # Use the default of storing the class name in the sti_key
@@ -326,6 +336,7 @@ module Sequel
326
336
  cti_tables.reverse_each do |ct|
327
337
  db.schema(ct).each{|sk,v| db_schema[sk] = v}
328
338
  end
339
+ setup_auto_validations if respond_to?(:setup_auto_validations, true)
329
340
  end
330
341
  end
331
342
 
@@ -32,9 +32,10 @@ module Sequel
32
32
  #
33
33
  # The :mapping option is just a shortcut that works in particular
34
34
  # cases. To handle any case, you can define a custom :composer
35
- # and :decomposer procs. The :composer proc will be instance_execed
36
- # the first time the getter is called, and the :decomposer proc
37
- # will be instance_execed before saving. The above example could
35
+ # and :decomposer procs. The :composer and :decomposer procs will
36
+ # be used to define instance methods. The :composer will be called
37
+ # the first time the getter is called, and the :decomposer
38
+ # will be called before saving. The above example could
38
39
  # also be implemented as:
39
40
  #
40
41
  # Album.composition :date,
@@ -74,9 +75,9 @@ module Sequel
74
75
  #
75
76
  # Options:
76
77
  # :class :: if using the :mapping option, the class to use, as a Class, String or Symbol.
77
- # :composer :: A proc that is instance_execed when the composition getter method is called
78
+ # :composer :: A proc used to define the method that the composition getter method will call
78
79
  # to create the composition.
79
- # :decomposer :: A proc that is instance_execed before saving the model object,
80
+ # :decomposer :: A proc used to define the method called before saving the model object,
80
81
  # if the composition object exists, which sets the columns in the model object
81
82
  # based on the value of the composition object.
82
83
  # :mapping :: An array where each element is either a symbol or an array of two symbols.
@@ -129,15 +130,17 @@ module Sequel
129
130
 
130
131
  # Define getter and setter methods for the composition object.
131
132
  def define_composition_accessor(name, opts=OPTS)
132
- composer = opts[:composer]
133
+ composer_meth = opts[:composer_method] = Plugins.def_sequel_method(@composition_module, "#{name}_composer", 0, &opts[:composer])
134
+ opts[:decomposer_method] = Plugins.def_sequel_method(@composition_module, "#{name}_decomposer", 0, &opts[:decomposer])
133
135
  @composition_module.class_eval do
134
136
  define_method(name) do
135
137
  if compositions.has_key?(name)
136
138
  compositions[name]
137
139
  elsif frozen?
138
- instance_exec(&composer)
140
+ # composer_meth is private
141
+ send(composer_meth)
139
142
  else
140
- compositions[name] = instance_exec(&composer)
143
+ compositions[name] = send(composer_meth)
141
144
  end
142
145
  end
143
146
  define_method("#{name}=") do |v|
@@ -171,7 +174,8 @@ module Sequel
171
174
  # For each composition, set the columns in the model class based
172
175
  # on the composition object.
173
176
  def before_validation
174
- @compositions.keys.each{|n| instance_exec(&model.compositions[n][:decomposer])} if @compositions
177
+ # decomposer_method is private
178
+ @compositions.keys.each{|n| send(model.compositions[n][:decomposer_method])} if @compositions
175
179
  super
176
180
  end
177
181