sequel 5.19.0 → 5.24.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 (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