sequel 4.19.0 → 4.20.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +36 -0
  3. data/doc/association_basics.rdoc +27 -2
  4. data/doc/release_notes/4.20.0.txt +79 -0
  5. data/doc/schema_modification.rdoc +1 -1
  6. data/doc/sharding.rdoc +10 -0
  7. data/lib/sequel/adapters/mysql2.rb +2 -1
  8. data/lib/sequel/adapters/shared/mysql.rb +3 -1
  9. data/lib/sequel/adapters/shared/sqlite.rb +3 -9
  10. data/lib/sequel/adapters/swift.rb +9 -1
  11. data/lib/sequel/database/transactions.rb +13 -5
  12. data/lib/sequel/dataset/actions.rb +4 -1
  13. data/lib/sequel/dataset/query.rb +1 -1
  14. data/lib/sequel/extensions/constraint_validations.rb +1 -1
  15. data/lib/sequel/extensions/pg_enum.rb +19 -0
  16. data/lib/sequel/extensions/schema_dumper.rb +1 -1
  17. data/lib/sequel/model/associations.rb +26 -13
  18. data/lib/sequel/model/base.rb +4 -3
  19. data/lib/sequel/plugins/json_serializer.rb +23 -17
  20. data/lib/sequel/plugins/prepared_statements_associations.rb +1 -0
  21. data/lib/sequel/plugins/rcte_tree.rb +11 -2
  22. data/lib/sequel/plugins/static_cache.rb +8 -2
  23. data/lib/sequel/version.rb +1 -1
  24. data/spec/adapters/mysql_spec.rb +7 -0
  25. data/spec/core/database_spec.rb +13 -0
  26. data/spec/core/dataset_spec.rb +5 -0
  27. data/spec/extensions/json_serializer_spec.rb +11 -0
  28. data/spec/extensions/pg_enum_spec.rb +29 -1
  29. data/spec/extensions/prepared_statements_associations_spec.rb +8 -0
  30. data/spec/extensions/rcte_tree_spec.rb +6 -1
  31. data/spec/extensions/schema_dumper_spec.rb +2 -1
  32. data/spec/extensions/static_cache_spec.rb +7 -1
  33. data/spec/integration/timezone_test.rb +3 -3
  34. data/spec/model/associations_spec.rb +17 -0
  35. data/spec/model/eager_loading_spec.rb +39 -0
  36. data/spec/model/record_spec.rb +9 -0
  37. metadata +4 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ce1762f276f7bfdac15fe395f0eef9696c185397
4
- data.tar.gz: f1164599a6a4bccd41e20d7ff36f443e6a46551f
3
+ metadata.gz: 6daa0c1ef4f76e1d9bfdeedbf9208bf32646ab20
4
+ data.tar.gz: f2cf0e139a84f465fe1146de98b767fad029fca9
5
5
  SHA512:
6
- metadata.gz: 31ecc2aef0b4d68eb9424f60cd2b1b7256b8916ca57d5fe6b74c5d3681e0b72a73014fa7225c4a87c87ee23656699ea85d027d919ac30613aa99269007676f22
7
- data.tar.gz: fe8a6d58fab4a544a5cc5168b6721dc7d72c93567b8d17d04a940efd0ac4fb5dcf5bbe6968ed8fe9e6d52c30dbde418d571567b7f49103ec20f9f86cf9a82a7c
6
+ metadata.gz: 28f1f4b25bd2c5db83ce6630ebd9310524dafb59a60ce9e72e3c8e6d12f0a9ea9eff9e1e2ec492fa3f7f655b76ee51b6f5a8e233a36b80370d51922d3024f504
7
+ data.tar.gz: 7309df3435d53878e8aa1945fc728fedad302727d503af24c399285fbf511b70571ca7cbef4f9c1b5c615aac689d9971f8072b0c9e2fb77f89491410384be665
data/CHANGELOG CHANGED
@@ -1,3 +1,39 @@
1
+ === 4.20.0 (2015-03-03)
2
+
3
+ * Restore the use of AUTOINCREMENT on SQLite (jeremyevans) (#965)
4
+
5
+ * Duplicate the associations hash when duplicating a model object (jeremyevans)
6
+
7
+ * Correctly apply association limit when eager loading with an eager block using default limit strategy on some databases (jeremyevans)
8
+
9
+ * Fix eager loading when using the :window_function limit strategy with an eager block and cascaded associations (jeremyevans)
10
+
11
+ * Add support for set_column_type :auto_increment=>true to add AUTO_INCREMENT to existing column on MySQL (jeremyevans) (#959)
12
+
13
+ * Add support for overridding the :instance_specific association option (jeremyevans)
14
+
15
+ * Recognize MSSQL bit type as boolean in the schema_dumper (jeremyevans)
16
+
17
+ * Skip eager loading queries if there are no matching keys (jeremyevans) (#952)
18
+
19
+ * Dataset#paged_each now returns an enumerator if not passed a block (jeremyevans)
20
+
21
+ * Use to_json :root option with string value as the JSON object key in the json_serializer plugin (jeremyevans)
22
+
23
+ * Allow create_enum in the pg_enum extension be reversible in migrations (celsworth) (#951)
24
+
25
+ * Have swift adapter respect database and application timezone settings (asppsa, jeremyevans) (#946)
26
+
27
+ * Don't have the static cache plugin attempt to validate objects (jeremyevans)
28
+
29
+ * Make freeze not validate objects if their errors are already frozen (jeremyevans)
30
+
31
+ * Only use prepared statements for associations if caching association metadata (jeremyevans)
32
+
33
+ * Set parent association when loading descendants in the rcte_tree plugin (jeremyevans)
34
+
35
+ * Add Database#transaction :before_retry option, specifying a proc to call before retrying (uhoh-itsmaciek) (#941)
36
+
1
37
  === 4.19.0 (2015-02-01)
2
38
 
3
39
  * Make jdbc/sqlanywhere correctly set :auto_increment entry in schema hashes (jeremyevans)
@@ -268,9 +268,9 @@ an example of a directed graph:
268
268
  # :name \----- :predecessor_id
269
269
 
270
270
  class Node
271
- many_to_many :direct_successors, :left_key=>:successor_id,
271
+ many_to_many :direct_predecessors, :left_key=>:successor_id,
272
272
  :right_key=>:predecessor_id, :join_table=>:edges, :class=>self
273
- many_to_many :direct_predecessors, :right_key=>:successor_id,
273
+ many_to_many :direct_successors, :right_key=>:successor_id,
274
274
  :left_key=>:predecessor_id, :join_table=>:edges, :class=>self
275
275
  end
276
276
 
@@ -886,6 +886,22 @@ that returns all albums of an artist that went gold (sold at least
886
886
  ds.where{copies_sold > 500000}
887
887
  end
888
888
 
889
+ The result of the block is cached as an optimization. One of the side
890
+ effects of that is that if your block depends on external state, it won't
891
+ work correctly unless you setup a delayed evaluation. For example:
892
+
893
+ Artist.one_to_many :gold_albums, :class=>:Album do |ds|
894
+ ds.where{copies_sold > $gold_limit}
895
+ end
896
+
897
+ In this case if you change <tt>$gold_limit</tt> later, the changes won't
898
+ effect the association. If you want to pick up changes to <tt>$gold_limit</tt>,
899
+ you need to setup a delayed evaluation:
900
+
901
+ Artist.one_to_many :gold_albums, :class=>:Album do |ds|
902
+ ds.where{copies_sold > Sequel.delay{$gold_limit}}
903
+ end
904
+
889
905
  ==== :class
890
906
 
891
907
  This is the class of the associated objects that will be used. It's
@@ -1719,6 +1735,15 @@ This is usually used if the association dataset depends on specific values in
1719
1735
  model instance that would not be valid when eager loading for multiple
1720
1736
  instances.
1721
1737
 
1738
+ ==== :instance_specific
1739
+
1740
+ This allows you to override the setting of whether the dataset contains instance
1741
+ specific code. For example, if you are passing a block to the association,
1742
+ Sequel sets this to true by default, which disables some optimizations that
1743
+ would be invalid if the association is instance specific. If you know that the
1744
+ block does not contain instance specific code, you can set this to false to
1745
+ reenable the optimizations.
1746
+
1722
1747
  ==== :cartesian_product_number
1723
1748
 
1724
1749
  The number of joins completed by this association that could cause more
@@ -0,0 +1,79 @@
1
+ = New Features
2
+
3
+ * A :before_retry option has been added to Database#transaction, which
4
+ specifies a proc to call when retrying if the :retry_on option
5
+ is used. This can be used to implement additional logging, sleeping
6
+ between retries, or other things.
7
+
8
+ * The to_json method :root option in the json_serializer plugin can now
9
+ be a string value to specify the name for the object key, instead of
10
+ using the underscored model name.
11
+
12
+ * Dataset#paged_each now returns an enumerator if not passed a block.
13
+
14
+ * You can now set the :instance_specific association option to false.
15
+ Previously, it was automatically set to true in some cases. If you
16
+ know the association does not depend on anything instance-specific
17
+ other than the foreign/primary key, setting this option can allow
18
+ Sequel to perform some additional optimizations.
19
+
20
+ = Other Improvements
21
+
22
+ * Eager loading queries are now skipped if there are no matching keys.
23
+ There was code to check this previously, but it was accidently
24
+ removed in an earlier refactoring.
25
+
26
+ * Eager loading an association with a limit and an eager block and
27
+ cascaded associations now works correctly when the window_function
28
+ limit strategy is used (the default on databases that support
29
+ window functions).
30
+
31
+ * Eager loading an association with a limit with an eager block now
32
+ works correctly on databases do not support window functions but do
33
+ support correlated subqueries.
34
+
35
+ * The parent association is now set on associated objects when loading
36
+ descendants in the rcte_tree plugin. This allows the parent method
37
+ on any of the descendants to work without issuing a database query.
38
+
39
+ * The prepared_statements_associations plugin now only uses prepared
40
+ statements if association metadata is being cached. Previously, it
41
+ would use prepared statements even if association metadata was not
42
+ cached, which could leak the prepared statements.
43
+
44
+ * Model#dup now duplicates the associations hash for the object.
45
+
46
+ * Model#freeze no longer validates an object if the the errors for the
47
+ object are already frozen. The static_cache plugin now freezes the
48
+ errors before freezing the object, so that it doesn't validate the
49
+ object. This can skip many database queries when the
50
+ auto_validations plugin is used and there is a unique constraint or
51
+ index on the related table.
52
+
53
+ * AUTOINCREMENT is now used again on SQLite by default for primary
54
+ keys. It was removed when :auto_increment was added to the schema
55
+ hashes, but the removal changed SQLite's behavior. This restores
56
+ the previous behavior.
57
+
58
+ * Microsoft SQL Server's bit type is now recognized as a boolean type
59
+ by the schema dumper.
60
+
61
+ * The pg_enum extension's create_enum method can now be used in
62
+ reversible migrations.
63
+
64
+ * set_column_type with the :auto_increment=>true option once again
65
+ works on MySQL. It had been broken since Sequel started adding
66
+ :auto_increment to the schema hashes.
67
+
68
+ * The mysql2 adapter now recognizes the :charset option as a synonym
69
+ for :encoding.
70
+
71
+ * The swift adapter now respects database and application timezone
72
+ settings.
73
+
74
+ = Backwards Compatibility
75
+
76
+ * AssociationReflection#apply_ruby_eager_limit_strategy no longer
77
+ checks that the strategy is :ruby, callers are now expected to
78
+ check the value themselves. This should only matter if you are
79
+ using custom association types.
@@ -142,7 +142,7 @@ as it's third argument. A simple example is:
142
142
 
143
143
  +foreign_key+ accepts the same options as +column+. For example, to have a unique foreign key with varchar(16) type:
144
144
 
145
- foreign_key :column_name, :unique=>true, :type=>'varchar(16)'
145
+ foreign_key :column_name, :table, :unique=>true, :type=>'varchar(16)'
146
146
 
147
147
  +foreign_key+ also accepts some specific options:
148
148
 
data/doc/sharding.rdoc CHANGED
@@ -41,6 +41,16 @@ is the simplest configuration:
41
41
  This will use the slave_server for SELECT queries and master_server for
42
42
  other queries.
43
43
 
44
+ If you want to ensure your queries are going to a specific database, you
45
+ can force this for a given query by using the .server method and passing
46
+ the symbol name defined in the connect options. For example:
47
+
48
+ # Force the SELECT to run on the master
49
+ DB[:users].server(:default).all
50
+
51
+ # Force the SELECT to run on the read-only slave
52
+ DB[:users].server(:read_only).all
53
+
44
54
  === Multiple Read-Only Slaves, Single Master
45
55
 
46
56
  Let's say you have 4 slave database servers with names slave_server0,
@@ -32,6 +32,7 @@ module Sequel
32
32
  opts[:username] ||= opts.delete(:user)
33
33
  opts[:flags] ||= 0
34
34
  opts[:flags] |= ::Mysql2::Client::FOUND_ROWS if ::Mysql2::Client.const_defined?(:FOUND_ROWS)
35
+ opts[:encoding] ||= opts[:charset]
35
36
  conn = ::Mysql2::Client.new(opts)
36
37
  conn.query_options.merge!(:symbolize_keys=>true, :cache_rows=>false)
37
38
 
@@ -41,7 +42,7 @@ module Sequel
41
42
  # in case the READ_DEFAULT_GROUP overrode the provided encoding.
42
43
  # Doesn't work across implicit reconnects, but Sequel doesn't turn on
43
44
  # that feature.
44
- if encoding = opts[:encoding] || opts[:charset]
45
+ if encoding = opts[:encoding]
45
46
  sqls.unshift("SET NAMES #{conn.escape(encoding.to_s)}")
46
47
  end
47
48
 
@@ -215,7 +215,9 @@ module Sequel
215
215
  raise Error, "cannot determine database type to use for CHANGE COLUMN operation"
216
216
  end
217
217
  opts = op.merge(opts)
218
- opts.delete(:auto_increment) if op[:auto_increment] == false
218
+ if op.has_key?(:auto_increment)
219
+ opts[:auto_increment] = op[:auto_increment]
220
+ end
219
221
  "CHANGE COLUMN #{quote_identifier(op[:name])} #{column_definition_sql(opts)}"
220
222
  when :drop_constraint
221
223
  case op[:type]
@@ -160,10 +160,8 @@ module Sequel
160
160
  end
161
161
 
162
162
  # Override the default setting for whether to use timezones in timestamps.
163
- # For backwards compatibility, it is set to +true+ by default.
164
- # Anyone wanting to use SQLite's datetime functions should set it to +false+
165
- # using this method. It's possible that the default will change in a future version,
166
- # so anyone relying on timezones in timestamps should set this to +true+.
163
+ # It is set to +false+ by default, as SQLite's date/time methods do not
164
+ # support timezones in timestamps.
167
165
  attr_writer :use_timestamp_timezones
168
166
 
169
167
  # SQLite supports timezones in timestamps, since it just stores them as strings,
@@ -282,11 +280,6 @@ module Sequel
282
280
  end
283
281
  end
284
282
 
285
- # SQLite does not need to specify AUTOINCREMENT, integer primary keys are automatically auto incrementing.
286
- def auto_increment_sql
287
- ''
288
- end
289
-
290
283
  def begin_new_transaction(conn, opts)
291
284
  mode = opts[:mode] || @transaction_mode
292
285
  sql = TRANSACTION_MODE[mode] or raise Error, "transaction :mode must be one of: :deferred, :immediate, :exclusive, nil"
@@ -353,6 +346,7 @@ module Sequel
353
346
  cols.each do |c|
354
347
  c[:default] = LiteralString.new(c[:default]) if c[:default]
355
348
  c[:type] = c[:db_type]
349
+ c.delete(:auto_increment)
356
350
  end
357
351
  cols
358
352
  end
@@ -135,10 +135,18 @@ module Sequel
135
135
  @columns = res.fields.map do |c|
136
136
  col_map[c] = output_identifier(c)
137
137
  end
138
+ tz = db.timezone if Sequel.application_timezone
138
139
  res.each do |r|
139
140
  h = {}
140
141
  r.each do |k, v|
141
- h[col_map[k]] = v.is_a?(StringIO) ? SQL::Blob.new(v.read) : v
142
+ h[col_map[k]] = case v
143
+ when StringIO
144
+ SQL::Blob.new(v.read)
145
+ when DateTime
146
+ tz ? Sequel.database_to_application_timestamp(Sequel.send(:convert_input_datetime_no_offset, v, tz)) : v
147
+ else
148
+ v
149
+ end
142
150
  end
143
151
  yield h
144
152
  end
@@ -45,6 +45,9 @@ module Sequel
45
45
  # :num_retries :: The number of times to retry if the :retry_on option is used.
46
46
  # The default is 5 times. Can be set to nil to retry indefinitely,
47
47
  # but that is not recommended.
48
+ # :before_retry :: Proc to execute before rertrying if the :retry_on option is used.
49
+ # Called with two arguments: the number of retry attempts (counting
50
+ # the current one) and the error the last attempt failed with.
48
51
  # :prepare :: A string to use as the transaction identifier for a
49
52
  # prepared transaction (two-phase commit), if the database/adapter
50
53
  # supports prepared transactions.
@@ -57,7 +60,8 @@ module Sequel
57
60
  # :rollback :: Can the set to :reraise to reraise any Sequel::Rollback exceptions
58
61
  # raised, or :always to always rollback even if no exceptions occur
59
62
  # (useful for testing).
60
- # :server :: The server to use for the transaction.
63
+ # :server :: The server to use for the transaction. Set to :default, :read_only, or
64
+ # whatever symbol you used in the connect string when naming your servers.
61
65
  # :savepoint :: Whether to create a new savepoint for this transaction,
62
66
  # only respected if the database/adapter supports savepoints. By
63
67
  # default Sequel will reuse an existing transaction, so if you want to
@@ -73,13 +77,17 @@ module Sequel
73
77
  # and :remote_write (9.2+).
74
78
  def transaction(opts=OPTS, &block)
75
79
  if retry_on = opts[:retry_on]
76
- num_retries = opts.fetch(:num_retries, 5)
80
+ tot_retries = opts.fetch(:num_retries, 5)
81
+ num_retries = 0 unless tot_retries.nil?
77
82
  begin
78
83
  transaction(opts.merge(:retry_on=>nil, :retrying=>true), &block)
79
- rescue *retry_on
84
+ rescue *retry_on => e
80
85
  if num_retries
81
- num_retries -= 1
82
- retry if num_retries >= 0
86
+ num_retries += 1
87
+ if num_retries <= tot_retries
88
+ opts[:before_retry].call(num_retries, e) if opts[:before_retry]
89
+ retry
90
+ end
83
91
  else
84
92
  retry
85
93
  end
@@ -449,7 +449,7 @@ module Sequel
449
449
  # for using an approach with a limit and offset for every page. This can
450
450
  # be set to :filter, which uses a limit and a filter that excludes
451
451
  # rows from previous pages. In order for this strategy to work, you must be
452
- # selecting the columns you are ordering by, and non of the columns can contain
452
+ # selecting the columns you are ordering by, and none of the columns can contain
453
453
  # NULLs. Note that some Sequel adapters have optimized implementations that will
454
454
  # use cursors or streaming regardless of the :strategy option used.
455
455
  # :filter_values :: If the :strategy=>:filter option is used, this option should be a proc
@@ -486,6 +486,9 @@ module Sequel
486
486
  unless @opts[:order]
487
487
  raise Sequel::Error, "Dataset#paged_each requires the dataset be ordered"
488
488
  end
489
+ unless block_given?
490
+ return enum_for(:paged_each, opts)
491
+ end
489
492
 
490
493
  total_limit = @opts[:limit]
491
494
  offset = @opts[:offset]
@@ -16,7 +16,7 @@ module Sequel
16
16
 
17
17
  # Which options don't affect the SQL generation. Used by simple_select_all?
18
18
  # to determine if this is a simple SELECT * FROM table.
19
- NON_SQL_OPTIONS = [:server, :defaults, :overrides, :graph, :eager_graph, :graph_aliases]
19
+ NON_SQL_OPTIONS = [:server, :defaults, :overrides, :graph, :eager, :eager_graph, :graph_aliases]
20
20
 
21
21
  # These symbols have _join methods created (e.g. inner_join) that
22
22
  # call join_table with the symbol, passing along the arguments and
@@ -264,7 +264,7 @@ module Sequel
264
264
 
265
265
  private
266
266
 
267
- # Modify the default create_table generator to include
267
+ # Modify the default alter_table generator to include
268
268
  # the constraint validation methods.
269
269
  def alter_table_generator(&block)
270
270
  super do
@@ -37,6 +37,15 @@
37
37
  # DB[:table_name].get(:column_name)
38
38
  # # ['value1', 'value2']
39
39
  #
40
+ # If the migration extension is loaded before this one (the order is important),
41
+ # you can use create_enum in a reversible migration:
42
+ #
43
+ # Sequel.migration do
44
+ # change do
45
+ # create_enum(:type_name, %w'value1 value2 value3')
46
+ # end
47
+ # end
48
+ #
40
49
  # Finally, typecasting for enums is setup to cast to strings, which
41
50
  # allows you to use symbols in your model code. Similar, you can provide
42
51
  # the enum values as symbols when creating enums using create_enum or
@@ -132,5 +141,15 @@ module Sequel
132
141
  end
133
142
  end
134
143
 
144
+ # support reversible create_enum statements if the migration extension is loaded
145
+ if defined?(MigrationReverser)
146
+ class MigrationReverser
147
+ private
148
+ def create_enum(name, _)
149
+ @actions << [:drop_enum, name]
150
+ end
151
+ end
152
+ end
153
+
135
154
  Database.register_extension(:pg_enum, Postgres::EnumDatabaseMethods)
136
155
  end
@@ -31,7 +31,7 @@ module Sequel
31
31
  {:type=>Bignum}
32
32
  when /\A(?:real|float|double(?: precision)?|double\(\d+,\d+\)(?: unsigned)?)\z/o
33
33
  {:type=>Float}
34
- when 'boolean'
34
+ when 'boolean', 'bit'
35
35
  {:type=>TrueClass}
36
36
  when /\A(?:(?:tiny|medium|long|n)?text|clob)\z/o
37
37
  {:type=>String, :text=>true}
@@ -144,15 +144,13 @@ module Sequel
144
144
  # If the ruby eager limit strategy is being used, slice the array using the slice
145
145
  # range to return the object(s) at the correct offset/limit.
146
146
  def apply_ruby_eager_limit_strategy(rows)
147
- if eager_limit_strategy == :ruby
148
- name = self[:name]
149
- if returns_array?
150
- range = slice_range
151
- rows.each{|o| o.associations[name] = o.associations[name][range] || []}
152
- elsif slice_range
153
- offset = slice_range.begin
154
- rows.each{|o| o.associations[name] = o.associations[name][offset]}
155
- end
147
+ name = self[:name]
148
+ if returns_array?
149
+ range = slice_range
150
+ rows.each{|o| o.associations[name] = o.associations[name][range] || []}
151
+ elsif slice_range
152
+ offset = slice_range.begin
153
+ rows.each{|o| o.associations[name] = o.associations[name][offset]}
156
154
  end
157
155
  end
158
156
 
@@ -241,18 +239,24 @@ module Sequel
241
239
  def eager_load_results(eo, &block)
242
240
  rows = eo[:rows]
243
241
  initialize_association_cache(rows) unless eo[:initialize_rows] == false
242
+ if eo[:id_map]
243
+ ids = eo[:id_map].keys
244
+ return ids if ids.empty?
245
+ end
244
246
  strategy = eager_limit_strategy
245
247
  cascade = eo[:associations]
246
248
 
247
249
  if eo[:eager_block] || eo[:loader] == false
248
250
  strategy = true_eager_graph_limit_strategy if strategy == :union
251
+ # Correlated subqueries are not supported for regular eager loading
252
+ strategy = :ruby if strategy == :correlated_subquery
249
253
  objects = apply_eager_limit_strategy(eager_loading_dataset(eo), strategy).all
250
254
  elsif strategy == :union
251
255
  objects = []
252
256
  ds = associated_dataset
253
257
  loader = union_eager_loader
254
258
  joiner = " UNION ALL "
255
- eo[:id_map].keys.each_slice(subqueries_per_union).each do |slice|
259
+ ids.each_slice(subqueries_per_union).each do |slice|
256
260
  objects.concat(ds.with_sql(slice.map{|k| loader.sql(*k)}.join(joiner)).to_a)
257
261
  end
258
262
  ds = ds.eager(cascade) if cascade
@@ -260,11 +264,13 @@ module Sequel
260
264
  else
261
265
  loader = placeholder_eager_loader
262
266
  loader = loader.with_dataset{|dataset| dataset.eager(cascade)} if cascade
263
- objects = loader.all(eo[:id_map].keys)
267
+ objects = loader.all(ids)
264
268
  end
265
269
 
266
270
  objects.each(&block)
267
- apply_ruby_eager_limit_strategy(rows)
271
+ if strategy == :ruby
272
+ apply_ruby_eager_limit_strategy(rows)
273
+ end
268
274
  end
269
275
 
270
276
  # The key to use for the key hash when eager loading
@@ -1651,7 +1657,7 @@ module Sequel
1651
1657
 
1652
1658
  opts = orig_opts.merge(:type => type, :name => name, :cache=>({} if cache_associations), :model => self)
1653
1659
  opts[:block] = block if block
1654
- if block || orig_opts[:block] || orig_opts[:dataset]
1660
+ if !opts.has_key?(:instance_specific) && (block || orig_opts[:block] || orig_opts[:dataset])
1655
1661
  # It's possible the association is instance specific, in that it depends on
1656
1662
  # values other than the foreign key value. This needs to be checked for
1657
1663
  # in certain places to disable optimizations.
@@ -2210,6 +2216,13 @@ module Sequel
2210
2216
  end
2211
2217
  end
2212
2218
 
2219
+ # Duplicate the associations hash when duplicating the object.
2220
+ def initialize_copy(other)
2221
+ super
2222
+ @associations = @associations.dup if @associations
2223
+ self
2224
+ end
2225
+
2213
2226
  # Load the associated objects using the dataset, handling callbacks, reciprocals, and caching.
2214
2227
  def load_associated_objects(opts, dynamic_opts=nil)
2215
2228
  if dynamic_opts == true or dynamic_opts == false or dynamic_opts == nil
@@ -1329,9 +1329,10 @@ module Sequel
1329
1329
  def freeze
1330
1330
  values.freeze
1331
1331
  changed_columns.freeze
1332
- errors
1333
- validate
1334
- errors.freeze
1332
+ unless errors.frozen?
1333
+ validate
1334
+ errors.freeze
1335
+ end
1335
1336
  this.freeze if !new? && model.primary_key
1336
1337
  super
1337
1338
  end
@@ -253,7 +253,9 @@ module Sequel
253
253
  # :only :: Symbol or Array of Symbols of columns to only
254
254
  # include in the JSON output, ignoring all other
255
255
  # columns.
256
- # :root :: Qualify the JSON with the name of the object.
256
+ # :root :: Qualify the JSON with the name of the object. If a
257
+ # string is given, use the string as the key, otherwise
258
+ # use an underscored version of the model's name.
257
259
  def to_json(*a)
258
260
  if opts = a.first.is_a?(Hash)
259
261
  opts = model.json_serializer_opts.merge(a.first)
@@ -286,7 +288,12 @@ module Sequel
286
288
  Array(inc).each{|c| h[c.to_s] = send(c)}
287
289
  end
288
290
  end
289
- h = {model.send(:underscore, model.to_s) => h} if opts[:root]
291
+
292
+ if root = opts[:root]
293
+ root = model.send(:underscore, model.to_s) unless root.is_a?(String)
294
+ h = {root => h}
295
+ end
296
+
290
297
  Sequel.object_to_json(h, *a)
291
298
  end
292
299
  end
@@ -299,16 +306,13 @@ module Sequel
299
306
  #
300
307
  # :array :: An array of instances. If this is not provided,
301
308
  # calls #all on the receiver to get the array.
302
- # :root :: If set to :collection, only wraps the collection
303
- # in a root object. If set to :instance, only wraps
309
+ # :root :: If set to :collection, wraps the collection
310
+ # in a root object using the pluralized, underscored model
311
+ # name as the key. If set to :instance, only wraps
304
312
  # the instances in a root object. If set to :both,
305
313
  # wraps both the collection and instances in a root
306
- # object. Unfortunately, for backwards compatibility,
307
- # if this option is true and doesn't match one of those
308
- # symbols, it defaults to both. That may change in a
309
- # future version, so for forwards compatibility, you
310
- # should pick a specific symbol for your desired
311
- # behavior.
314
+ # object. If set to a string, wraps the collection in
315
+ # a root object using the string as the key.
312
316
  def to_json(*a)
313
317
  if opts = a.first.is_a?(Hash)
314
318
  opts = model.json_serializer_opts.merge(a.first)
@@ -317,15 +321,17 @@ module Sequel
317
321
  opts = model.json_serializer_opts
318
322
  end
319
323
 
320
- collection_root = case opts[:root]
324
+ case collection_root = opts[:root]
321
325
  when nil, false, :instance
322
- false
323
- when :both
324
- true
326
+ collection_root = false
325
327
  else
326
328
  opts = opts.dup
327
- opts.delete(:root)
328
- true
329
+ unless collection_root == :both
330
+ opts.delete(:root)
331
+ end
332
+ unless collection_root.is_a?(String)
333
+ collection_root = model.send(:pluralize, model.send(:underscore, model.to_s))
334
+ end
329
335
  end
330
336
 
331
337
  res = if row_proc
@@ -341,7 +347,7 @@ module Sequel
341
347
  end
342
348
 
343
349
  if collection_root
344
- Sequel.object_to_json({model.send(:pluralize, model.send(:underscore, model.to_s)) => res}, *a)
350
+ Sequel.object_to_json({collection_root => res}, *a)
345
351
  else
346
352
  Sequel.object_to_json(res, *a)
347
353
  end
@@ -50,6 +50,7 @@ module Sequel
50
50
  # that, given appropriate bound variables, the prepared statement will work correctly for any
51
51
  # instance. Return false if such a prepared statement cannot be created.
52
52
  def association_prepared_statement(opts, assoc_bv)
53
+ return unless model.cache_associations
53
54
  opts.send(:cached_fetch, :prepared_statement) do
54
55
  unless opts[:instance_specific]
55
56
  ds, bv = _associated_dataset(opts, {}).unbind
@@ -272,7 +272,11 @@ module Sequel
272
272
  end
273
273
  end
274
274
  children_map.each do |parent_id, objs|
275
- parent_map[parent_id].associations[childrena] = objs
275
+ parent_obj = parent_map[parent_id]
276
+ parent_obj.associations[childrena] = objs
277
+ objs.each do |obj|
278
+ obj.associations[parent] = parent_obj
279
+ end
276
280
  end
277
281
  end
278
282
  end
@@ -333,7 +337,12 @@ module Sequel
333
337
  (children_map[key_conv[obj]] ||= []) << obj
334
338
  end
335
339
  children_map.each do |parent_id, objs|
336
- parent_map[parent_id].associations[childrena] = objs.uniq
340
+ objs = objs.uniq
341
+ parent_obj = parent_map[parent_id]
342
+ parent_obj.associations[childrena] = objs
343
+ objs.each do |obj|
344
+ obj.associations[parent] = parent_obj
345
+ end
337
346
  end
338
347
  end
339
348
  model.one_to_many descendants, d
@@ -12,7 +12,10 @@ module Sequel
12
12
  # instances. This is slower as it requires creating new objects, but it allows
13
13
  # you to make changes to the object and save them. If you set the option to false,
14
14
  # you are responsible for updating the cache manually (the pg_static_cache_updater
15
- # extension can handle this automatically).
15
+ # extension can handle this automatically). Note that it is not safe to use the
16
+ # :frozen=>false option if you are mutating column values directly. If you are
17
+ # mutating column values, you should also override Model.call to dup each mutable
18
+ # column value to ensure it is not shared by other instances.
16
19
  #
17
20
  # The caches this plugin creates are used for the following things:
18
21
  #
@@ -180,7 +183,10 @@ module Sequel
180
183
  def load_cache
181
184
  a = dataset.all
182
185
  h = {}
183
- a.each{|o| h[o.pk.freeze] = o.freeze}
186
+ a.each do |o|
187
+ o.errors.freeze
188
+ h[o.pk.freeze] = o.freeze
189
+ end
184
190
  @all = a.freeze
185
191
  @cache = h.freeze
186
192
  end
@@ -3,7 +3,7 @@ module Sequel
3
3
  MAJOR = 4
4
4
  # The minor version of Sequel. Bumped for every non-patch level
5
5
  # release, generally around once a month.
6
- MINOR = 19
6
+ MINOR = 20
7
7
  # The tiny version of Sequel. Usually 0, only bumped for bugfix
8
8
  # releases that fix regressions from previous versions.
9
9
  TINY = 0
@@ -90,6 +90,13 @@ describe "MySQL", '#create_table' do
90
90
  @db.create_table!(:dolls){column :t, "enum('a', 'b', 'c', 'd')", :default=>'b'}
91
91
  @db.schema(:dolls).first.last[:ruby_default].should == 'b'
92
92
  end
93
+
94
+ specify "should allow setting auto_increment for existing column" do
95
+ @db.create_table(:dolls){Integer :a, :primary_key=>true}
96
+ @db.schema(:dolls).first.last[:auto_increment].should == false
97
+ @db.set_column_type :dolls, :a, Integer, :auto_increment=>true
98
+ @db.schema(:dolls).first.last[:auto_increment].should == true
99
+ end
93
100
  end
94
101
 
95
102
  if [:mysql, :mysql2].include?(DB.adapter_scheme)
@@ -682,6 +682,19 @@ shared_examples_for "Database#transaction" do
682
682
  a.should == [1] * 100
683
683
  end
684
684
 
685
+ specify "should support :before_retry option for invoking callback before retrying" do
686
+ a, errs, calls = [], [], []
687
+ retryer = proc{|n, err| calls << n; errs << err }
688
+ @db.transaction(:retry_on=>Sequel::DatabaseDisconnectError, :before_retry => retryer) do
689
+ a << 1; raise Sequel::DatabaseDisconnectError if a.length < 3
690
+ end
691
+ @db.sqls.should == ['BEGIN', 'ROLLBACK', 'BEGIN', 'ROLLBACK', 'BEGIN', 'COMMIT']
692
+ a.should == [1, 1, 1]
693
+ errs.count.should == 2
694
+ errs.each { |e| e.class.should == Sequel::DatabaseDisconnectError }
695
+ calls.should == [1, 2]
696
+ end
697
+
685
698
  specify "should raise an error if attempting to use :retry_on inside another transaction" do
686
699
  proc{@db.transaction{@db.transaction(:retry_on=>Sequel::ConstraintViolation){}}}.should raise_error(Sequel::Error)
687
700
  @db.sqls.should == ['BEGIN', 'ROLLBACK']
@@ -4641,6 +4641,11 @@ describe "Dataset#paged_each" do
4641
4641
  @rows.should == @db
4642
4642
  end
4643
4643
 
4644
+ it "should return enumerator when called without block" do
4645
+ @ds.paged_each.each(&@proc)
4646
+ @rows.should == @db
4647
+ end
4648
+
4644
4649
  it "should respect the row_proc" do
4645
4650
  @ds.row_proc = lambda{|row| {:x=>row[:x]*2}}
4646
4651
  @ds.paged_each(&@proc)
@@ -207,6 +207,11 @@ describe "Sequel::Plugins::JsonSerializer" do
207
207
  @album.to_json(:root=>true, :only => :name).to_s.should == '{"album":{"name":"RF"}}'
208
208
  end
209
209
 
210
+ it "should handle the :root option with a string to qualify single records using the string as the key" do
211
+ @album.to_json(:root=>"foo", :except => [:name, :artist_id]).to_s.should == '{"foo":{"id":1}}'
212
+ @album.to_json(:root=>"bar", :only => :name).to_s.should == '{"bar":{"name":"RF"}}'
213
+ end
214
+
210
215
  it "should handle the :root=>:both option to qualify a dataset of records" do
211
216
  Album.dataset._fetch = [{:id=>1, :name=>'RF'}, {:id=>1, :name=>'RF'}]
212
217
  Album.dataset.to_json(:root=>:both, :only => :id).to_s.should == '{"albums":[{"album":{"id":1}},{"album":{"id":1}}]}'
@@ -223,6 +228,12 @@ describe "Sequel::Plugins::JsonSerializer" do
223
228
  Album.dataset.to_json(:root=>:instance, :only => :id).to_s.should == '[{"album":{"id":1}},{"album":{"id":1}}]'
224
229
  end
225
230
 
231
+ it "should handle the :root=>string option to qualify just the collection using the string as the key" do
232
+ Album.dataset._fetch = [{:id=>1, :name=>'RF'}, {:id=>1, :name=>'RF'}]
233
+ Album.dataset.to_json(:root=>"foos", :only => :id).to_s.should == '{"foos":[{"id":1},{"id":1}]}'
234
+ Album.dataset.to_json(:root=>"bars", :only => :id).to_s.should == '{"bars":[{"id":1},{"id":1}]}'
235
+ end
236
+
226
237
  it "should store the default options in json_serializer_opts" do
227
238
  Album.json_serializer_opts.should == {:naked=>true}
228
239
  c = Class.new(Album)
@@ -1,6 +1,8 @@
1
1
  require File.join(File.dirname(File.expand_path(__FILE__)), "spec_helper")
2
2
 
3
- describe "pg_inet extension" do
3
+ Sequel.extension :migration
4
+
5
+ describe "pg_enum extension" do
4
6
  before do
5
7
  @db = Sequel.connect('mock://postgres', :quote_identifiers=>false)
6
8
  @db.extend(Module.new do
@@ -61,4 +63,30 @@ describe "pg_inet extension" do
61
63
  @db.add_enum_value(:foo, :a, :if_not_exists=>true)
62
64
  @db.sqls.first.should == "ALTER TYPE foo ADD VALUE IF NOT EXISTS 'a'"
63
65
  end
66
+
67
+ it "should reverse a create_enum directive in a migration" do
68
+ c = Class.new do
69
+ attr_reader :actions
70
+ def initialize(&block)
71
+ @actions = []
72
+ end
73
+ def method_missing(*args)
74
+ @actions << args
75
+ end
76
+ end
77
+
78
+ db = c.new
79
+
80
+ p = Proc.new do
81
+ create_enum(:type_name, %w'value1 value2 value3')
82
+ end
83
+
84
+ Sequel.migration{change(&p)}.apply(db, :up)
85
+ Sequel.migration{change(&p)}.apply(db, :down)
86
+
87
+ db.actions.should == [
88
+ [:create_enum, :type_name, ["value1", "value2", "value3"] ],
89
+ [:drop_enum, :type_name]
90
+ ]
91
+ end
64
92
  end
@@ -90,6 +90,14 @@ describe "Sequel::Plugins::PreparedStatementsAssociations" do
90
90
  @db.sqls.should == []
91
91
  end
92
92
 
93
+ specify "should run a regular query if not caching association metadata" do
94
+ @Artist.cache_associations = false
95
+ @Artist.load(:id=>1).albums
96
+ @db.sqls.should == ["SELECT * FROM albums WHERE (albums.artist_id = 1)"]
97
+ @Artist.load(:id=>1).album
98
+ @db.sqls.should == ["SELECT * FROM albums WHERE (albums.artist_id = 1) LIMIT 1"]
99
+ end
100
+
93
101
  specify "should run a regular query if there is a callback" do
94
102
  @Artist.load(:id=>1).albums(proc{|ds| ds})
95
103
  @db.sqls.should == ["SELECT * FROM albums WHERE (albums.artist_id = 1)"]
@@ -91,14 +91,17 @@ describe Sequel::Model, "rcte_tree" do
91
91
  @o.associations[:p].associations[:p].associations[:p].associations.fetch(:p, 1).should == nil
92
92
  end
93
93
 
94
- it "should add all children associations when lazily loading descendants" do
94
+ it "should add all parent and children associations when lazily loading descendants" do
95
95
  @c.plugin :rcte_tree
96
96
  @ds._fetch = [[{:id=>3, :name=>'??', :parent_id=>1}, {:id=>1, :name=>'A', :parent_id=>2}, {:id=>4, :name=>'B', :parent_id=>2}, {:id=>5, :name=>'?', :parent_id=>3}]]
97
97
  @o.descendants.should == [@c.load(:id=>3, :name=>'??', :parent_id=>1), @c.load(:id=>1, :name=>'A', :parent_id=>2), @c.load(:id=>4, :name=>'B', :parent_id=>2), @c.load(:id=>5, :name=>'?', :parent_id=>3)]
98
98
  @o.associations[:children].should == [@c.load(:id=>1, :name=>'A', :parent_id=>2), @c.load(:id=>4, :name=>'B', :parent_id=>2)]
99
99
  @o.associations[:children].map{|c1| c1.associations[:children]}.should == [[@c.load(:id=>3, :name=>'??', :parent_id=>1)], []]
100
+ @o.associations[:children].map{|c1| c1.associations[:parent]}.should == [@o, @o]
100
101
  @o.associations[:children].map{|c1| c1.associations[:children].map{|c2| c2.associations[:children]}}.should == [[[@c.load(:id=>5, :name=>'?', :parent_id=>3)]], []]
102
+ @o.associations[:children].map{|c1| c1.associations[:children].map{|c2| c2.associations[:parent]}}.should == [[@o.children.first], []]
101
103
  @o.associations[:children].map{|c1| c1.associations[:children].map{|c2| c2.associations[:children].map{|c3| c3.associations[:children]}}}.should == [[[[]]], []]
104
+ @o.associations[:children].map{|c1| c1.associations[:children].map{|c2| c2.associations[:children].map{|c3| c3.associations[:parent]}}}.should == [[[@o.children.first.children.first]], []]
102
105
  end
103
106
 
104
107
  it "should add all children associations when lazily loading descendants and giving options" do
@@ -182,7 +185,9 @@ describe Sequel::Model, "rcte_tree" do
182
185
  [@c.load(:id=>4, :name=>'?', :parent_id=>7), @c.load(:id=>5, :name=>'?', :parent_id=>4)]]
183
186
  os.map{|o| o.children}.should == [[@c.load(:id=>6, :parent_id=>2, :name=>'C'), @c.load(:id=>9, :parent_id=>2, :name=>'E')], [@c.load(:id=>3, :name=>'00', :parent_id=>6)], [@c.load(:id=>4, :name=>'?', :parent_id=>7)]]
184
187
  os.map{|o1| o1.children.map{|o2| o2.children}}.should == [[[@c.load(:id=>3, :name=>'00', :parent_id=>6)], []], [[]], [[@c.load(:id=>5, :name=>'?', :parent_id=>4)]]]
188
+ os.map{|o1| o1.children.map{|o2| o2.parent}}.should == [[os[0], os[0]], [os[1]], [os[2]]]
185
189
  os.map{|o1| o1.children.map{|o2| o2.children.map{|o3| o3.children}}}.should == [[[[]], []], [[]], [[[]]]]
190
+ os.map{|o1| o1.children.map{|o2| o2.children.map{|o3| o3.parent}}}.should == [[[os[0].children[0]], []], [[]], [[os[2].children[0]]]]
186
191
  @db.sqls.should == []
187
192
  end
188
193
 
@@ -647,7 +647,7 @@ END_MIG
647
647
  %w"nvarchar ntext smalldatetime smallmoney binary varbinary nchar" +
648
648
  ["timestamp(6) without time zone", "timestamp(6) with time zone", 'mediumint(10) unsigned', 'int(9) unsigned',
649
649
  'int(10) unsigned', "int(12) unsigned", 'bigint unsigned', 'tinyint(3) unsigned', 'identity', 'int identity'] +
650
- %w"integer(10)"
650
+ %w"integer(10) bit"
651
651
  @d.meta_def(:schema) do |t, *o|
652
652
  i = 0
653
653
  types.map{|x| [:"c#{i+=1}", {:db_type=>x, :allow_null=>true}]}
@@ -726,6 +726,7 @@ create_table(:x) do
726
726
  Integer :c70
727
727
  Integer :c71
728
728
  Integer :c72
729
+ TrueClass :c73
729
730
 
730
731
  check Sequel::SQL::BooleanExpression.new(:>=, Sequel::SQL::Identifier.new(:c64), 0)
731
732
  check Sequel::SQL::BooleanExpression.new(:>=, Sequel::SQL::Identifier.new(:c65), 0)
@@ -1,6 +1,6 @@
1
1
  require File.join(File.dirname(File.expand_path(__FILE__)), "spec_helper")
2
2
 
3
- describe "Sequel::Plugins::StaticCache with :frozen=>false option" do
3
+ describe "Sequel::Plugins::StaticCache" do
4
4
  before do
5
5
  @db = Sequel.mock
6
6
  @db.fetch = [{:id=>1}, {:id=>2}]
@@ -9,6 +9,12 @@ describe "Sequel::Plugins::StaticCache with :frozen=>false option" do
9
9
  @c.columns :id, :name
10
10
  end
11
11
 
12
+ it "should not attempt to validate objects" do
13
+ @c.send(:define_method, :validate){errors.add(:name, 'bad')}
14
+ proc{@c.plugin(:static_cache)}.should_not raise_error
15
+ @c.map{|o| o.valid?}.should == [true, true]
16
+ end
17
+
12
18
  shared_examples_for "Sequel::Plugins::StaticCache" do
13
19
  it "should use a ruby hash as a cache of all model instances" do
14
20
  @c.cache.should == {1=>@c.load(:id=>1), 2=>@c.load(:id=>2)}
@@ -39,7 +39,7 @@ describe "Sequel timezone support" do
39
39
  @db.drop_table(:t)
40
40
  end
41
41
 
42
- cspecify "should support using UTC for database storage and local time for the application", [:swift], [:tinytds], [:do, :mysql], [:do, :postgres], [:oracle] do
42
+ cspecify "should support using UTC for database storage and local time for the application", [:tinytds], [:do, :mysql], [:do, :postgres], [:oracle] do
43
43
  Sequel.database_timezone = :utc
44
44
  Sequel.application_timezone = :local
45
45
  test_timezone
@@ -48,7 +48,7 @@ describe "Sequel timezone support" do
48
48
  test_timezone
49
49
  end
50
50
 
51
- cspecify "should support using local time for database storage and UTC for the application", [:swift], [:tinytds], [:do, :mysql], [:do, :postgres], [:oracle] do
51
+ cspecify "should support using local time for database storage and UTC for the application", [:tinytds], [:do, :mysql], [:do, :postgres], [:oracle] do
52
52
  Sequel.database_timezone = :local
53
53
  Sequel.application_timezone = :utc
54
54
  test_timezone
@@ -65,7 +65,7 @@ describe "Sequel timezone support" do
65
65
  test_timezone
66
66
  end
67
67
 
68
- cspecify "should support using local time for both database storage and for application", [:do, :postgres], [:oracle], [:swift, :postgres], [:swift, :mysql] do
68
+ cspecify "should support using local time for both database storage and for application", [:do, :postgres], [:oracle] do
69
69
  Sequel.default_timezone = :local
70
70
  test_timezone
71
71
  Sequel.database_timezone = :utc
@@ -136,6 +136,14 @@ describe Sequel::Model, "associate" do
136
136
  proc{c.one_to_many :cs, :clone=>:c}.should raise_error(Sequel::Error)
137
137
  end
138
138
 
139
+ it "should allow overriding the :instance_specific option" do
140
+ c = Class.new(Sequel::Model(:c))
141
+ c.many_to_one :c, :instance_specific=>true
142
+ c.association_reflection(:c)[:instance_specific].should == true
143
+ c.many_to_one :c, :instance_specific=>false do |ds| ds end
144
+ c.association_reflection(:c)[:instance_specific].should == false
145
+ end
146
+
139
147
  it "should allow cloning of one_to_many to one_to_one associations and vice-versa" do
140
148
  c = Class.new(Sequel::Model(:c))
141
149
  c.one_to_one :c
@@ -4207,12 +4215,21 @@ describe "Model#freeze" do
4207
4215
  Album::B.dataset._fetch = {:album_id=>1, :id=>2}
4208
4216
  @o.b.should == Album::B.load(:id=>2, :album_id=>1)
4209
4217
  @o.associations[:b].should be_nil
4218
+
4219
+ @o = @o.dup
4220
+ @o.b.should == Album::B.load(:id=>2, :album_id=>1)
4221
+ @o.associations[:b].should == Album::B.load(:id=>2, :album_id=>1)
4210
4222
  end
4211
4223
 
4212
4224
  it "should not break reciprocal associations" do
4213
4225
  b = Album::B.load(:id=>2, :album_id=>nil)
4214
4226
  b.album = @o
4215
4227
  @o.associations[:b].should be_nil
4228
+
4229
+ @o = @o.dup
4230
+ b = Album::B.load(:id=>2, :album_id=>nil)
4231
+ b.album = @o
4232
+ @o.associations[:b].should == Album::B.load(:id=>2, :album_id=>1)
4216
4233
  end
4217
4234
  end
4218
4235
 
@@ -131,6 +131,16 @@ describe Sequel::Model, "#eager" do
131
131
  DB.sqls.should == []
132
132
  end
133
133
 
134
+ it "should skip eager loading for a many_to_one association with no matching keys" do
135
+ EagerAlbum.dataset._fetch = [{:id=>1, :band_id=>nil}]
136
+ a = EagerAlbum.eager(:band).all
137
+ DB.sqls.should == ['SELECT * FROM albums']
138
+ a.should == [EagerAlbum.load(:id => 1, :band_id => nil)]
139
+ a.first.associations.fetch(:band).should == nil
140
+ a.first.band.should == nil
141
+ DB.sqls.should == []
142
+ end
143
+
134
144
  it "should eagerly load a single many_to_one association with the same name as the column" do
135
145
  EagerAlbum.def_column_alias(:band_id_id, :band_id)
136
146
  EagerAlbum.many_to_one :band_id, :key_column=>:band_id, :class=>EagerBand
@@ -1055,6 +1065,35 @@ describe Sequel::Model, "#eager" do
1055
1065
  DB.sqls.should == []
1056
1066
  end
1057
1067
 
1068
+ it "should allow cascading of eager loading with custom callback with symbol value when association has a limit" do
1069
+ EagerAlbum.dataset._fetch = (1..11).map{|i| {:band_id=>2, :id=>i}}
1070
+ EagerTrack.dataset._fetch = [{:id=>3, :album_id=>1}]
1071
+ a = EagerBand.eager(:top_10_albums=>{proc{|ds| ds.select(:id, :name)}=>:tracks}).all
1072
+ a.should == [EagerBand.load(:id => 2)]
1073
+ sqls = DB.sqls
1074
+ sqls.pop.should =~ /SELECT \* FROM tracks WHERE \(tracks.album_id IN \((\d+, ){10}\d+\)\)/
1075
+ sqls.should == ['SELECT * FROM bands', 'SELECT id, name FROM albums WHERE (albums.band_id IN (2))']
1076
+ a = a.first
1077
+ a.top_10_albums.should == (1..10).map{|i| EagerAlbum.load(:band_id=>2, :id=>i)}
1078
+ a.top_10_albums.map{|x| x.tracks}.should == [[EagerTrack.load(:id => 3, :album_id=>1)]] + ([[]] * 9)
1079
+ DB.sqls.should == []
1080
+ end
1081
+
1082
+ it "should allow cascading of eager loading with custom callback with symbol value when association has a limit when using window function eager limit strategy" do
1083
+ def (EagerAlbum.dataset).supports_window_functions?() true end
1084
+ EagerAlbum.dataset._fetch = {:band_id=>2, :id=>1}
1085
+ EagerTrack.dataset._fetch = [{:id=>3, :album_id=>1}]
1086
+ a = EagerBand.eager(:top_10_albums=>{proc{|ds| ds.select(:id, :name)}=>:tracks}).all
1087
+ a.should == [EagerBand.load(:id => 2)]
1088
+ DB.sqls.should == ['SELECT * FROM bands',
1089
+ 'SELECT * FROM (SELECT id, name, row_number() OVER (PARTITION BY albums.band_id) AS x_sequel_row_number_x FROM albums WHERE (albums.band_id IN (2))) AS t1 WHERE (x_sequel_row_number_x <= 10)',
1090
+ 'SELECT * FROM tracks WHERE (tracks.album_id IN (1))']
1091
+ a = a.first
1092
+ a.top_10_albums.should == [EagerAlbum.load(:band_id=>2, :id=>1)]
1093
+ a.top_10_albums.first.tracks.should == [EagerTrack.load(:id => 3, :album_id=>1)]
1094
+ DB.sqls.should == []
1095
+ end
1096
+
1058
1097
  it "should allow cascading of eager loading with custom callback with array value" do
1059
1098
  a = EagerTrack.eager(:album=>{proc{|ds| ds.select(:id, :band_id)}=>[:band, :band_name]}).all
1060
1099
  a.should == [EagerTrack.load(:id => 3, :album_id => 1)]
@@ -446,6 +446,15 @@ describe "Model#freeze" do
446
446
  o.valid?.should == false
447
447
  end
448
448
 
449
+ it "should not call validate if errors is already frozen" do
450
+ @o.valid?.should == true
451
+ o = Album.new
452
+ o.errors.freeze
453
+ def o.validate() errors.add(:foo, '') end
454
+ proc{o.freeze}.should_not raise_error
455
+ o.valid?.should == true
456
+ end
457
+
449
458
  it "should raise an Error if trying to save/destroy/delete/refresh" do
450
459
  proc{@o.save}.should raise_error(Sequel::Error)
451
460
  proc{@o.destroy}.should raise_error(Sequel::Error)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sequel
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.19.0
4
+ version: 4.20.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Evans
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-02-01 00:00:00.000000000 Z
11
+ date: 2015-03-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -192,6 +192,7 @@ extra_rdoc_files:
192
192
  - doc/release_notes/4.17.0.txt
193
193
  - doc/release_notes/4.18.0.txt
194
194
  - doc/release_notes/4.19.0.txt
195
+ - doc/release_notes/4.20.0.txt
195
196
  files:
196
197
  - CHANGELOG
197
198
  - MIT-LICENSE
@@ -299,6 +300,7 @@ files:
299
300
  - doc/release_notes/4.18.0.txt
300
301
  - doc/release_notes/4.19.0.txt
301
302
  - doc/release_notes/4.2.0.txt
303
+ - doc/release_notes/4.20.0.txt
302
304
  - doc/release_notes/4.3.0.txt
303
305
  - doc/release_notes/4.4.0.txt
304
306
  - doc/release_notes/4.5.0.txt