sequel 3.11.0 → 3.12.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 (136) hide show
  1. data/CHANGELOG +70 -0
  2. data/Rakefile +1 -1
  3. data/doc/active_record.rdoc +896 -0
  4. data/doc/advanced_associations.rdoc +46 -31
  5. data/doc/association_basics.rdoc +14 -9
  6. data/doc/dataset_basics.rdoc +3 -3
  7. data/doc/migration.rdoc +1011 -0
  8. data/doc/model_hooks.rdoc +198 -0
  9. data/doc/querying.rdoc +811 -86
  10. data/doc/release_notes/3.12.0.txt +304 -0
  11. data/doc/sharding.rdoc +17 -0
  12. data/doc/sql.rdoc +537 -0
  13. data/doc/validations.rdoc +501 -0
  14. data/lib/sequel/adapters/jdbc.rb +19 -27
  15. data/lib/sequel/adapters/jdbc/postgresql.rb +0 -7
  16. data/lib/sequel/adapters/mysql.rb +5 -4
  17. data/lib/sequel/adapters/odbc.rb +3 -2
  18. data/lib/sequel/adapters/shared/mssql.rb +7 -6
  19. data/lib/sequel/adapters/shared/mysql.rb +2 -7
  20. data/lib/sequel/adapters/shared/postgres.rb +2 -8
  21. data/lib/sequel/adapters/shared/sqlite.rb +2 -5
  22. data/lib/sequel/adapters/sqlite.rb +4 -4
  23. data/lib/sequel/core.rb +0 -1
  24. data/lib/sequel/database.rb +2 -1060
  25. data/lib/sequel/database/connecting.rb +227 -0
  26. data/lib/sequel/database/dataset.rb +58 -0
  27. data/lib/sequel/database/dataset_defaults.rb +127 -0
  28. data/lib/sequel/database/logging.rb +62 -0
  29. data/lib/sequel/database/misc.rb +246 -0
  30. data/lib/sequel/database/query.rb +390 -0
  31. data/lib/sequel/database/schema_generator.rb +7 -3
  32. data/lib/sequel/database/schema_methods.rb +351 -7
  33. data/lib/sequel/dataset/actions.rb +9 -2
  34. data/lib/sequel/dataset/misc.rb +6 -2
  35. data/lib/sequel/dataset/mutation.rb +3 -11
  36. data/lib/sequel/dataset/query.rb +49 -6
  37. data/lib/sequel/exceptions.rb +3 -0
  38. data/lib/sequel/extensions/migration.rb +395 -113
  39. data/lib/sequel/extensions/schema_dumper.rb +21 -13
  40. data/lib/sequel/model.rb +27 -25
  41. data/lib/sequel/model/associations.rb +72 -34
  42. data/lib/sequel/model/base.rb +74 -18
  43. data/lib/sequel/model/errors.rb +8 -1
  44. data/lib/sequel/plugins/active_model.rb +8 -0
  45. data/lib/sequel/plugins/association_pks.rb +87 -0
  46. data/lib/sequel/plugins/association_proxies.rb +8 -0
  47. data/lib/sequel/plugins/boolean_readers.rb +12 -6
  48. data/lib/sequel/plugins/caching.rb +14 -7
  49. data/lib/sequel/plugins/class_table_inheritance.rb +15 -9
  50. data/lib/sequel/plugins/composition.rb +2 -1
  51. data/lib/sequel/plugins/force_encoding.rb +10 -7
  52. data/lib/sequel/plugins/hook_class_methods.rb +12 -11
  53. data/lib/sequel/plugins/identity_map.rb +9 -0
  54. data/lib/sequel/plugins/instance_hooks.rb +23 -13
  55. data/lib/sequel/plugins/lazy_attributes.rb +4 -1
  56. data/lib/sequel/plugins/many_through_many.rb +18 -4
  57. data/lib/sequel/plugins/nested_attributes.rb +1 -0
  58. data/lib/sequel/plugins/optimistic_locking.rb +1 -1
  59. data/lib/sequel/plugins/rcte_tree.rb +9 -8
  60. data/lib/sequel/plugins/schema.rb +8 -0
  61. data/lib/sequel/plugins/serialization.rb +1 -3
  62. data/lib/sequel/plugins/sharding.rb +135 -0
  63. data/lib/sequel/plugins/single_table_inheritance.rb +117 -25
  64. data/lib/sequel/plugins/skip_create_refresh.rb +35 -0
  65. data/lib/sequel/plugins/string_stripper.rb +26 -0
  66. data/lib/sequel/plugins/tactical_eager_loading.rb +8 -0
  67. data/lib/sequel/plugins/timestamps.rb +15 -2
  68. data/lib/sequel/plugins/touch.rb +13 -0
  69. data/lib/sequel/plugins/update_primary_key.rb +48 -0
  70. data/lib/sequel/plugins/validation_class_methods.rb +8 -0
  71. data/lib/sequel/plugins/validation_helpers.rb +1 -1
  72. data/lib/sequel/sql.rb +17 -20
  73. data/lib/sequel/version.rb +1 -1
  74. data/spec/adapters/postgres_spec.rb +5 -5
  75. data/spec/core/core_sql_spec.rb +17 -1
  76. data/spec/core/database_spec.rb +17 -5
  77. data/spec/core/dataset_spec.rb +31 -8
  78. data/spec/core/schema_generator_spec.rb +8 -1
  79. data/spec/core/schema_spec.rb +13 -0
  80. data/spec/extensions/association_pks_spec.rb +85 -0
  81. data/spec/extensions/hook_class_methods_spec.rb +9 -9
  82. data/spec/extensions/migration_spec.rb +339 -219
  83. data/spec/extensions/schema_dumper_spec.rb +28 -17
  84. data/spec/extensions/sharding_spec.rb +272 -0
  85. data/spec/extensions/single_table_inheritance_spec.rb +92 -4
  86. data/spec/extensions/skip_create_refresh_spec.rb +17 -0
  87. data/spec/extensions/string_stripper_spec.rb +23 -0
  88. data/spec/extensions/update_primary_key_spec.rb +65 -0
  89. data/spec/extensions/validation_class_methods_spec.rb +5 -5
  90. data/spec/files/bad_down_migration/001_create_alt_basic.rb +4 -0
  91. data/spec/files/bad_down_migration/002_create_alt_advanced.rb +4 -0
  92. data/spec/files/bad_timestamped_migrations/1273253849_create_sessions.rb +9 -0
  93. data/spec/files/bad_timestamped_migrations/1273253851_create_nodes.rb +9 -0
  94. data/spec/files/bad_timestamped_migrations/1273253853_3_create_users.rb +3 -0
  95. data/spec/files/bad_up_migration/001_create_alt_basic.rb +4 -0
  96. data/spec/files/bad_up_migration/002_create_alt_advanced.rb +3 -0
  97. data/spec/files/convert_to_timestamp_migrations/001_create_sessions.rb +9 -0
  98. data/spec/files/convert_to_timestamp_migrations/002_create_nodes.rb +9 -0
  99. data/spec/files/convert_to_timestamp_migrations/003_3_create_users.rb +4 -0
  100. data/spec/files/convert_to_timestamp_migrations/1273253850_create_artists.rb +9 -0
  101. data/spec/files/convert_to_timestamp_migrations/1273253852_create_albums.rb +9 -0
  102. data/spec/files/duplicate_integer_migrations/001_create_alt_advanced.rb +4 -0
  103. data/spec/files/duplicate_integer_migrations/001_create_alt_basic.rb +4 -0
  104. data/spec/files/duplicate_timestamped_migrations/1273253849_create_sessions.rb +9 -0
  105. data/spec/files/duplicate_timestamped_migrations/1273253853_create_nodes.rb +9 -0
  106. data/spec/files/duplicate_timestamped_migrations/1273253853_create_users.rb +4 -0
  107. data/spec/files/integer_migrations/001_create_sessions.rb +9 -0
  108. data/spec/files/integer_migrations/002_create_nodes.rb +9 -0
  109. data/spec/files/integer_migrations/003_3_create_users.rb +4 -0
  110. data/spec/files/interleaved_timestamped_migrations/1273253849_create_sessions.rb +9 -0
  111. data/spec/files/interleaved_timestamped_migrations/1273253850_create_artists.rb +9 -0
  112. data/spec/files/interleaved_timestamped_migrations/1273253851_create_nodes.rb +9 -0
  113. data/spec/files/interleaved_timestamped_migrations/1273253852_create_albums.rb +9 -0
  114. data/spec/files/interleaved_timestamped_migrations/1273253853_3_create_users.rb +4 -0
  115. data/spec/files/missing_integer_migrations/001_create_alt_basic.rb +4 -0
  116. data/spec/files/missing_integer_migrations/003_create_alt_advanced.rb +4 -0
  117. data/spec/files/missing_timestamped_migrations/1273253849_create_sessions.rb +9 -0
  118. data/spec/files/missing_timestamped_migrations/1273253853_3_create_users.rb +4 -0
  119. data/spec/files/timestamped_migrations/1273253849_create_sessions.rb +9 -0
  120. data/spec/files/timestamped_migrations/1273253851_create_nodes.rb +9 -0
  121. data/spec/files/timestamped_migrations/1273253853_3_create_users.rb +4 -0
  122. data/spec/files/uppercase_timestamped_migrations/1273253849_CREATE_SESSIONS.RB +9 -0
  123. data/spec/files/uppercase_timestamped_migrations/1273253851_CREATE_NODES.RB +9 -0
  124. data/spec/files/uppercase_timestamped_migrations/1273253853_3_CREATE_USERS.RB +4 -0
  125. data/spec/integration/eager_loader_test.rb +20 -20
  126. data/spec/integration/migrator_test.rb +187 -0
  127. data/spec/integration/plugin_test.rb +150 -0
  128. data/spec/integration/schema_test.rb +13 -2
  129. data/spec/model/associations_spec.rb +41 -14
  130. data/spec/model/base_spec.rb +69 -0
  131. data/spec/model/eager_loading_spec.rb +7 -3
  132. data/spec/model/record_spec.rb +79 -4
  133. data/spec/model/validations_spec.rb +21 -9
  134. metadata +66 -5
  135. data/doc/schema.rdoc +0 -36
  136. data/lib/sequel/database/schema_sql.rb +0 -320
@@ -13,12 +13,12 @@ module Sequel
13
13
  def dump_indexes_migration(options={})
14
14
  ts = tables(options)
15
15
  <<END_MIG
16
- Class.new(Sequel::Migration) do
17
- def up
16
+ Sequel.migration do
17
+ up do
18
18
  #{ts.sort_by{|t| t.to_s}.map{|t| dump_table_indexes(t, :add_index, options)}.reject{|x| x == ''}.join("\n\n").gsub(/^/o, ' ')}
19
19
  end
20
20
 
21
- def down
21
+ down do
22
22
  #{ts.sort_by{|t| t.to_s}.map{|t| dump_table_indexes(t, :drop_index, options)}.reject{|x| x == ''}.join("\n\n").gsub(/^/o, ' ')}
23
23
  end
24
24
  end
@@ -37,12 +37,12 @@ END_MIG
37
37
  def dump_schema_migration(options={})
38
38
  ts = tables(options)
39
39
  <<END_MIG
40
- Class.new(Sequel::Migration) do
41
- def up
40
+ Sequel.migration do
41
+ up do
42
42
  #{ts.sort_by{|t| t.to_s}.map{|t| dump_table_schema(t, options)}.join("\n\n").gsub(/^/o, ' ')}
43
43
  end
44
44
 
45
- def down
45
+ down do
46
46
  drop_table(#{ts.sort_by{|t| t.to_s}.inspect[1...-1]})
47
47
  end
48
48
  end
@@ -52,12 +52,18 @@ END_MIG
52
52
  # Return a string with a create table block that will recreate the given
53
53
  # table's schema. Takes the same options as dump_schema_migration.
54
54
  def dump_table_schema(table, options={})
55
+ table = table.value.to_s if table.is_a?(SQL::Identifier)
56
+ raise(Error, "must provide table as a Symbol, String, or Sequel::SQL::Identifier") unless [String, Symbol].any?{|c| table.is_a?(c)}
55
57
  s = schema(table).dup
56
58
  pks = s.find_all{|x| x.last[:primary_key] == true}.map{|x| x.first}
57
59
  options = options.merge(:single_pk=>true) if pks.length == 1
58
60
  m = method(:column_schema_to_generator_opts)
59
61
  im = method(:index_to_generator_opts)
60
- indexes = indexes(table).sort_by{|k,v| k.to_s} if options[:indexes] != false and respond_to?(:indexes)
62
+ begin
63
+ indexes = indexes(table).sort_by{|k,v| k.to_s} if options[:indexes] != false
64
+ rescue Sequel::NotImplemented
65
+ nil
66
+ end
61
67
  gen = Schema::Generator.new(self) do
62
68
  s.each{|name, info| send(*m.call(name, info, options))}
63
69
  primary_key(pks) if !@primary_key && pks.length > 0
@@ -128,8 +134,8 @@ END_MIG
128
134
  {:type=>Date}
129
135
  when /\A(?:small)?datetime\z/o
130
136
  {:type=>DateTime}
131
- when /\Atimestamp(?: with(?:out)? time zone)?\z/o
132
- {:type=>DateTime}
137
+ when /\Atimestamp(?:\((\d+)\))?(?: with(?:out)? time zone)?\z/o
138
+ {:type=>DateTime, :size=>($1.to_i if $1)}
133
139
  when /\Atime(?: with(?:out)? time zone)?\z/o
134
140
  {:type=>Time, :only_time=>true}
135
141
  when /\An?char(?:acter)?(?:\((\d+)\))?\z/o
@@ -208,7 +214,7 @@ END_MIG
208
214
  # Dump this generator's constraints to a string that could be evaled inside
209
215
  # another instance to represent the same constraints
210
216
  def dump_constraints
211
- constraints.map do |c|
217
+ cs = constraints.map do |c|
212
218
  c = c.dup
213
219
  type = c.delete(:type)
214
220
  case type
@@ -224,7 +230,8 @@ END_MIG
224
230
  cols = c.delete(:columns)
225
231
  "#{type} #{cols.inspect}#{opts_inspect(c)}"
226
232
  end
227
- end.join("\n")
233
+ end
234
+ cs.join("\n")
228
235
  end
229
236
 
230
237
  # Dump this generator's indexes to a string that could be evaled inside
@@ -235,7 +242,7 @@ END_MIG
235
242
  # * :drop_index - Same as add_index, but create drop_index statements.
236
243
  # * :ignore_errors - Add the ignore_errors option to the outputted indexes
237
244
  def dump_indexes(options={})
238
- indexes.map do |c|
245
+ is = indexes.map do |c|
239
246
  c = c.dup
240
247
  cols = c.delete(:columns)
241
248
  if table = options[:add_index] || options[:drop_index]
@@ -243,7 +250,8 @@ END_MIG
243
250
  else
244
251
  "index #{cols.inspect}#{opts_inspect(c)}"
245
252
  end
246
- end.join("\n")
253
+ end
254
+ is.join("\n")
247
255
  end
248
256
 
249
257
  private
data/lib/sequel/model.rb CHANGED
@@ -44,28 +44,30 @@ module Sequel
44
44
  ANONYMOUS_MODEL_CLASSES = {}
45
45
 
46
46
  # Class methods added to model that call the method of the same name on the dataset
47
- DATASET_METHODS = %w'<< add_graph_aliases all avg count cross_join delete distinct
48
- each each_page each_server eager eager_graph empty? except exclude filter first for_update from from_self
49
- full_join full_outer_join get graph grep group group_and_count group_by having import
50
- inner_join insert insert_multiple intersect interval invert join join_table
51
- last left_join left_outer_join limit lock_style map max min multi_insert naked
52
- natural_full_join natural_join natural_left_join natural_right_join order order_by
53
- order_more paginate print qualify query range reverse reverse_order right_join right_outer_join
54
- select select_all select_append select_hash select_map select_more select_order_map
55
- server set set_defaults set_graph_aliases set_overrides
56
- single_value sum to_csv to_hash truncate unfiltered ungraphed ungrouped union unlimited unordered
57
- update where with with_recursive with_sql'.map{|x| x.to_sym}
58
-
47
+ DATASET_METHODS = (Dataset::ACTION_METHODS + Dataset::QUERY_METHODS +
48
+ [:eager, :eager_graph, :each_page, :each_server, :print]) - [:and, :or, :[], :[]=, :columns, :columns!]
49
+
59
50
  # Class instance variables to set to nil when a subclass is created, for -w compliance
60
51
  EMPTY_INSTANCE_VARIABLES = [:@overridable_methods_module, :@db]
61
52
 
53
+ # Boolean settings that can be modified at the global, class, or instance level.
54
+ BOOLEAN_SETTINGS = [:typecast_empty_string_to_nil, :typecast_on_assignment, :strict_param_setting, :raise_on_save_failure, :raise_on_typecast_failure, :require_modification, :use_transactions]
55
+
56
+ # Hooks that are called before an action. Can return false to not do the action. When
57
+ # overriding these, it is recommended to call super as the last line of your method,
58
+ # so later hooks are called before earlier hooks.
59
+ BEFORE_HOOKS = [:before_create, :before_update, :before_save, :before_destroy, :before_validation]
60
+
61
+ # Hooks that are called after an action. When overriding these, it is recommended to call
62
+ # super on the first line of your method, so later hooks are called before earlier hooks.
63
+ AFTER_HOOKS = [:after_initialize, :after_create, :after_update, :after_save, :after_destroy, :after_validation]
64
+
62
65
  # Empty instance methods to create that the user can override to get hook/callback behavior.
63
66
  # Just like any other method defined by Sequel, if you override one of these, you should
64
67
  # call super to get the default behavior (while empty by default, they can also be defined
65
- # by plugins).
66
- HOOKS = [:after_initialize, :before_create, :after_create, :before_update,
67
- :after_update, :before_save, :after_save, :before_destroy, :after_destroy,
68
- :before_validation, :after_validation]
68
+ # by plugins). See the {"Model Hooks" guide}[link:files/doc/model_hooks_rdoc.html] for
69
+ # more detail on hooks.
70
+ HOOKS = BEFORE_HOOKS + AFTER_HOOKS
69
71
 
70
72
  # Class instance variables that are inherited in subclasses. If the value is :dup, dup is called
71
73
  # on the superclass's instance variable when creating the instance variable in the subclass.
@@ -83,10 +85,6 @@ module Sequel
83
85
  # avoid problems when using eval with a string to define methods.
84
86
  NORMAL_METHOD_NAME_REGEXP = /\A[A-Za-z_][A-Za-z0-9_]*\z/
85
87
 
86
- # The setter methods (methods ending with =) that are never allowed
87
- # to be called automatically via set/update/new/etc..
88
- RESTRICTED_SETTER_METHODS = %w"== === []= taguri= typecast_empty_string_to_nil= typecast_on_assignment= strict_param_setting= raise_on_save_failure= raise_on_typecast_failure="
89
-
90
88
  # Regular expression that determines if the method is a valid setter name
91
89
  # (i.e. it ends with =).
92
90
  SETTER_METHOD_REGEXP = /=\z/
@@ -110,11 +108,15 @@ module Sequel
110
108
  @typecast_empty_string_to_nil = true
111
109
  @typecast_on_assignment = true
112
110
  @use_transactions = true
113
- end
114
111
 
115
- require %w"default_inflections inflections plugins base exceptions errors", "model"
116
- if !defined?(::SEQUEL_NO_ASSOCIATIONS) && !ENV.has_key?('SEQUEL_NO_ASSOCIATIONS')
117
- require 'associations', 'model'
118
- Model.plugin Model::Associations
112
+ Sequel.require %w"default_inflections inflections plugins base exceptions errors", "model"
113
+ if !defined?(::SEQUEL_NO_ASSOCIATIONS) && !ENV.has_key?('SEQUEL_NO_ASSOCIATIONS')
114
+ Sequel.require 'associations', 'model'
115
+ plugin Model::Associations
116
+ end
117
+
118
+ # The setter methods (methods ending with =) that are never allowed
119
+ # to be called automatically via set/update/new/etc..
120
+ RESTRICTED_SETTER_METHODS = instance_methods.map{|x| x.to_s}.grep(SETTER_METHOD_REGEXP)
119
121
  end
120
122
  end
@@ -310,6 +310,11 @@ module Sequel
310
310
  self[:join_table]
311
311
  end
312
312
 
313
+ # Alias of right_primary_keys
314
+ def associated_object_keys
315
+ right_primary_keys
316
+ end
317
+
313
318
  # many_to_many associations can only have associated objects if none of
314
319
  # the :left_primary_keys options have a nil value.
315
320
  def can_have_associated_objects?(obj)
@@ -380,7 +385,6 @@ module Sequel
380
385
  def right_primary_keys
381
386
  self[:right_primary_keys] ||= Array(right_primary_key)
382
387
  end
383
- alias associated_object_keys right_primary_keys
384
388
 
385
389
  # The columns to select when loading the association, associated_class.table_name.* by default.
386
390
  def select
@@ -441,8 +445,8 @@ module Sequel
441
445
  # => {:type => :many_to_one, :name => :portfolio, :class_name => "Portfolio"}
442
446
  #
443
447
  # For a more in depth general overview, as well as a reference guide,
444
- # see the {Association Basics page}[link:files/doc/association_basics_rdoc.html].
445
- # For examples of advanced usage, see the {Advanced Associations page}[link:files/doc/advanced_associations_rdoc.html].
448
+ # see the {Association Basics guide}[link:files/doc/association_basics_rdoc.html].
449
+ # For examples of advanced usage, see the {Advanced Associations guide}[link:files/doc/advanced_associations_rdoc.html].
446
450
  module ClassMethods
447
451
  # All association reflections defined for this model (default: none).
448
452
  attr_reader :association_reflections
@@ -521,9 +525,11 @@ module Sequel
521
525
  # Takes three arguments, a dataset, an alias to use for the table to graph for this association,
522
526
  # and the alias that was used for the current table (since you can cascade associations),
523
527
  # Should return a copy of the dataset with the association graphed into it.
524
- # - :eager_loader - A proc to use to implement eager loading, overriding the default. Takes three arguments,
525
- # a key hash (used solely to enhance performance), an array of records,
526
- # and a hash of dependent associations. The associated records should
528
+ # - :eager_loader - A proc to use to implement eager loading, overriding the default. Takes one or three arguments.
529
+ # If three arguments, the first should be a key hash (used solely to enhance performance), the second an array of records,
530
+ # and the third a hash of dependent associations. If one argument, is passed a hash with keys :key_hash,
531
+ # :rows, and :associations, corresponding to the three arguments, and an additional key :self, which is
532
+ # the dataset doing the eager loading. In the proc, the associated records should
527
533
  # be queried from the database and the associations cache for each
528
534
  # record should be populated for this to work correctly.
529
535
  # - :eager_loader_key - A symbol for the key column to use to populate the key hash
@@ -593,6 +599,8 @@ module Sequel
593
599
  # the current model and the associated model, as a symbol. Defaults to the name
594
600
  # of current model and name of associated model, pluralized,
595
601
  # underscored, sorted, and joined with '_'.
602
+ # - :join_table_block - proc that can be used to modify the dataset used in the add/remove/remove_all
603
+ # methods. Should accept a dataset argument and return a modified dataset if present.
596
604
  # - :left_key - foreign key in join table that points to current model's
597
605
  # primary key, as a symbol. Defaults to :"#{self.name.underscore}_id".
598
606
  # Can use an array of symbols for a composite key association.
@@ -610,6 +618,7 @@ module Sequel
610
618
  raise(Error, 'one_to_many association type with :one_to_one option removed, used one_to_one association type') if opts[:one_to_one] && type == :one_to_many
611
619
  raise(Error, 'invalid association type') unless assoc_class = ASSOCIATION_TYPES[type]
612
620
  raise(Error, 'Model.associate name argument must be a symbol') unless Symbol === name
621
+ raise(Error, ':eager_loader option must have an arity of 1 or 3') if opts[:eager_loader] && ![1, 3].include?(opts[:eager_loader].arity)
613
622
 
614
623
  # merge early so we don't modify opts
615
624
  orig_opts = opts.dup
@@ -649,7 +658,7 @@ module Sequel
649
658
  end
650
659
 
651
660
  # Modify and return eager loading dataset based on association options. Options:
652
- def eager_loading_dataset(opts, ds, select, associations)
661
+ def eager_loading_dataset(opts, ds, select, associations, eager_options={})
653
662
  ds = ds.select(*select) if select
654
663
  if c = opts[:conditions]
655
664
  ds = (c.is_a?(Array) && !Sequel.condition_specifier?(c)) ? ds.filter(*c) : ds.filter(c)
@@ -757,12 +766,12 @@ module Sequel
757
766
  opts[:dataset] ||= proc{opts.associated_class.inner_join(join_table, rcks.zip(opts.right_primary_keys) + lcks.zip(lcpks.map{|k| send(k)}))}
758
767
  database = db
759
768
 
760
- opts[:eager_loader] ||= proc do |key_hash, records, associations|
761
- h = key_hash[left_pk]
762
- records.each{|object| object.associations[name] = []}
769
+ opts[:eager_loader] ||= proc do |eo|
770
+ h = eo[:key_hash][left_pk]
771
+ eo[:rows].each{|object| object.associations[name] = []}
763
772
  r = uses_rcks ? rcks.zip(opts.right_primary_keys) : [[right, opts.right_primary_key]]
764
773
  l = uses_lcks ? [[lcks.map{|k| SQL::QualifiedIdentifier.new(join_table, k)}, SQL::SQLArray.new(h.keys)]] : [[left, h.keys]]
765
- model.eager_loading_dataset(opts, opts.associated_class.inner_join(join_table, r + l), Array(opts.select), associations).all do |assoc_record|
774
+ model.eager_loading_dataset(opts, opts.associated_class.inner_join(join_table, r + l), Array(opts.select), eo[:associations], eo).all do |assoc_record|
766
775
  hash_key = if uses_lcks
767
776
  left_key_alias.map{|k| assoc_record.values.delete(k)}
768
777
  else
@@ -796,13 +805,13 @@ module Sequel
796
805
  h = {}
797
806
  lcks.zip(lcpks).each{|k, pk| h[k] = send(pk)}
798
807
  rcks.zip(opts.right_primary_keys).each{|k, pk| h[k] = o.send(pk)}
799
- database.dataset.from(join_table).insert(h)
808
+ _join_table_dataset(opts).insert(h)
800
809
  end
801
810
  association_module_private_def(opts._remove_method) do |o|
802
- database.dataset.from(join_table).filter(lcks.zip(lcpks.map{|k| send(k)}) + rcks.zip(opts.right_primary_keys.map{|k| o.send(k)})).delete
811
+ _join_table_dataset(opts).filter(lcks.zip(lcpks.map{|k| send(k)}) + rcks.zip(opts.right_primary_keys.map{|k| o.send(k)})).delete
803
812
  end
804
813
  association_module_private_def(opts._remove_all_method) do
805
- _apply_association_options(opts, database.dataset.from(join_table).filter(lcks.zip(lcpks.map{|k| send(k)}))).delete
814
+ _join_table_dataset(opts).filter(lcks.zip(lcpks.map{|k| send(k)})).delete
806
815
  end
807
816
 
808
817
  def_add_method(opts)
@@ -823,16 +832,16 @@ module Sequel
823
832
  klass = opts.associated_class
824
833
  klass.filter(opts.primary_keys.map{|k| SQL::QualifiedIdentifier.new(klass.table_name, k)}.zip(cks.map{|k| send(k)}))
825
834
  end
826
- opts[:eager_loader] ||= proc do |key_hash, records, associations|
827
- h = key_hash[key]
835
+ opts[:eager_loader] ||= proc do |eo|
836
+ h = eo[:key_hash][key]
828
837
  keys = h.keys
829
838
  # Default the cached association to nil, so any object that doesn't have it
830
839
  # populated will have cached the negative lookup.
831
- records.each{|object| object.associations[name] = nil}
840
+ eo[:rows].each{|object| object.associations[name] = nil}
832
841
  # Skip eager loading if no objects have a foreign key for this association
833
842
  unless keys.empty?
834
843
  klass = opts.associated_class
835
- model.eager_loading_dataset(opts, klass.filter(uses_cks ? {opts.primary_keys.map{|k| SQL::QualifiedIdentifier.new(klass.table_name, k)}=>SQL::SQLArray.new(keys)} : {SQL::QualifiedIdentifier.new(klass.table_name, opts.primary_key)=>keys}), opts.select, associations).all do |assoc_record|
844
+ model.eager_loading_dataset(opts, klass.filter(uses_cks ? {opts.primary_keys.map{|k| SQL::QualifiedIdentifier.new(klass.table_name, k)}=>SQL::SQLArray.new(keys)} : {SQL::QualifiedIdentifier.new(klass.table_name, opts.primary_key)=>keys}), opts.select, eo[:associations], eo).all do |assoc_record|
836
845
  hash_key = uses_cks ? opts.primary_keys.map{|k| assoc_record.send(k)} : assoc_record.send(opts.primary_key)
837
846
  next unless objects = h[hash_key]
838
847
  objects.each{|object| object.associations[name] = assoc_record}
@@ -873,16 +882,16 @@ module Sequel
873
882
  klass = opts.associated_class
874
883
  klass.filter(cks.map{|k| SQL::QualifiedIdentifier.new(klass.table_name, k)}.zip(cpks.map{|k| send(k)}))
875
884
  end
876
- opts[:eager_loader] ||= proc do |key_hash, records, associations|
877
- h = key_hash[primary_key]
885
+ opts[:eager_loader] ||= proc do |eo|
886
+ h = eo[:key_hash][primary_key]
878
887
  if one_to_one
879
- records.each{|object| object.associations[name] = nil}
888
+ eo[:rows].each{|object| object.associations[name] = nil}
880
889
  else
881
- records.each{|object| object.associations[name] = []}
890
+ eo[:rows].each{|object| object.associations[name] = []}
882
891
  end
883
892
  reciprocal = opts.reciprocal
884
893
  klass = opts.associated_class
885
- model.eager_loading_dataset(opts, klass.filter(uses_cks ? {cks.map{|k| SQL::QualifiedIdentifier.new(klass.table_name, k)}=>SQL::SQLArray.new(h.keys)} : {SQL::QualifiedIdentifier.new(klass.table_name, key)=>h.keys}), opts.select, associations).all do |assoc_record|
894
+ model.eager_loading_dataset(opts, klass.filter(uses_cks ? {cks.map{|k| SQL::QualifiedIdentifier.new(klass.table_name, k)}=>SQL::SQLArray.new(h.keys)} : {SQL::QualifiedIdentifier.new(klass.table_name, key)=>h.keys}), opts.select, eo[:associations], eo).all do |assoc_record|
886
895
  hash_key = uses_cks ? cks.map{|k| assoc_record.send(k)} : assoc_record.send(key)
887
896
  next unless objects = h[hash_key]
888
897
  if one_to_one
@@ -928,11 +937,10 @@ module Sequel
928
937
  up_ds = up_ds.exclude(o.pk_hash)
929
938
  cks.zip(cpks).each{|k, pk| o.send(:"#{k}=", send(pk))}
930
939
  end
931
- update_database = lambda do
940
+ checked_transaction do
932
941
  up_ds.update(ck_nil_hash)
933
942
  o.save(:validate=>validate) || raise(Sequel::Error, "invalid associated object, cannot save") if o
934
943
  end
935
- use_transactions && o ? db.transaction(opts){update_database.call} : update_database.call
936
944
  end
937
945
  association_module_def(opts.setter_method){|o| set_one_to_one_associated_object(opts, o)}
938
946
  else
@@ -953,7 +961,11 @@ module Sequel
953
961
  end
954
962
  end
955
963
  end
956
- alias def_one_to_one def_one_to_many
964
+
965
+ # Alias of def_one_to_many
966
+ def def_one_to_one(opts)
967
+ def_one_to_many(opts)
968
+ end
957
969
 
958
970
  # Add the remove_ and remove_all instance methods
959
971
  def def_remove_methods(opts)
@@ -1005,6 +1017,12 @@ module Sequel
1005
1017
  _apply_association_options(opts, send(opts._dataset_method))
1006
1018
  end
1007
1019
 
1020
+ # Dataset for the join table of the given many to many association reflection
1021
+ def _join_table_dataset(opts)
1022
+ ds = model.db.from(opts[:join_table])
1023
+ opts[:join_table_block] ? opts[:join_table_block].call(ds) : ds
1024
+ end
1025
+
1008
1026
  # Return the associated objects from the dataset, without callbacks, reciprocals, and caching.
1009
1027
  def _load_associated_objects(opts)
1010
1028
  if opts.returns_array?
@@ -1031,10 +1049,7 @@ module Sequel
1031
1049
  raise(Sequel::Error, "associated object #{o.inspect} not of correct type #{klass}")
1032
1050
  end
1033
1051
  raise(Sequel::Error, "model object #{inspect} does not have a primary key") unless pk
1034
- if opts.need_associated_primary_key?
1035
- o.save(:validate=>opts[:validate]) if o.new?
1036
- raise(Sequel::Error, "associated object #{o.inspect} does not have a primary key") unless o.pk
1037
- end
1052
+ ensure_associated_primary_key(opts, o, *args)
1038
1053
  return if run_association_callbacks(opts, :before_add, o) == false
1039
1054
  send(opts._add_method, o, *args)
1040
1055
  if array = associations[opts[:name]] and !array.include?(o)
@@ -1063,6 +1078,16 @@ module Sequel
1063
1078
  a.uniq!
1064
1079
  end
1065
1080
 
1081
+ # Save the associated object if the associated object needs a primary key
1082
+ # and the associated object is new and does not have one. Raise an error if
1083
+ # the object still does not have a primary key
1084
+ def ensure_associated_primary_key(opts, o, *args)
1085
+ if opts.need_associated_primary_key?
1086
+ o.save(:validate=>opts[:validate]) if o.new?
1087
+ raise(Sequel::Error, "associated object #{o.inspect} does not have a primary key") unless o.pk
1088
+ end
1089
+ end
1090
+
1066
1091
  # Load the associated objects using the dataset, handling callbacks, reciprocals, and caching.
1067
1092
  def load_associated_objects(opts, reload=false)
1068
1093
  name = opts[:name]
@@ -1095,9 +1120,7 @@ module Sequel
1095
1120
  def remove_associated_object(opts, o, *args)
1096
1121
  klass = opts.associated_class
1097
1122
  if o.is_a?(Integer) || o.is_a?(String) || o.is_a?(Array)
1098
- key = o
1099
- pkh = klass.primary_key_hash(key)
1100
- raise(Sequel::Error, "no object with key(s) #{key} is currently associated to #{inspect}") unless o = (opts.remove_should_check_existing? ? send(opts.dataset_method) : klass).first(pkh)
1123
+ o = remove_check_existing_object_from_pk(opts, o, *args)
1101
1124
  elsif !o.is_a?(klass)
1102
1125
  raise(Sequel::Error, "associated object #{o.inspect} not of correct type #{klass}")
1103
1126
  elsif opts.remove_should_check_existing? && send(opts.dataset_method).filter(o.pk_hash).empty?
@@ -1113,6 +1136,16 @@ module Sequel
1113
1136
  o
1114
1137
  end
1115
1138
 
1139
+ # If necessary, check that the object from the associated table specified by the primary key
1140
+ # is currently associated to the object. If it is associated, return the object, otherwise
1141
+ # raise an error.
1142
+ def remove_check_existing_object_from_pk(opts, o, *args)
1143
+ key = o
1144
+ pkh = opts.associated_class.qualified_primary_key_hash(key)
1145
+ raise(Sequel::Error, "no object with key(s) #{key.inspect} is currently associated to #{inspect}") unless o = send(opts.dataset_method).first(pkh)
1146
+ o
1147
+ end
1148
+
1116
1149
  # Remove/unset the current object from/as the given object's reciprocal association.
1117
1150
  def remove_reciprocal_object(opts, o)
1118
1151
  return unless reciprocal = opts.reciprocal
@@ -1498,7 +1531,12 @@ module Sequel
1498
1531
  end
1499
1532
 
1500
1533
  reflections.each do |r|
1501
- r[:eager_loader].call(key_hash, a, eager_assoc[r[:name]])
1534
+ loader = r[:eager_loader]
1535
+ if loader.arity == 1
1536
+ loader.call(:key_hash=>key_hash, :rows=>a, :associations=>eager_assoc[r[:name]], :self=>self)
1537
+ else
1538
+ loader.call(key_hash, a, eager_assoc[r[:name]])
1539
+ end
1502
1540
  a.each{|object| object.send(:run_association_callbacks, r, :after_load, object.associations[r[:name]])} unless r[:after_load].empty?
1503
1541
  end
1504
1542
  end
@@ -113,6 +113,11 @@ module Sequel
113
113
  def dataset
114
114
  @dataset || raise(Error, "No dataset associated with #{self}")
115
115
  end
116
+
117
+ # Alias of set_dataset
118
+ def dataset=(ds)
119
+ set_dataset(ds)
120
+ end
116
121
 
117
122
  # Returns the database associated with the Model class.
118
123
  # If this model doesn't have a database associated with it,
@@ -236,6 +241,14 @@ module Sequel
236
241
  {key => value}
237
242
  end
238
243
  end
244
+
245
+ # Return a hash where the keys are qualified column references. Uses the given
246
+ # qualifier if provided, or the table_name otherwise.
247
+ def qualified_primary_key_hash(value, qualifier=table_name)
248
+ h = primary_key_hash(value)
249
+ h.to_a.each{|k,v| h[SQL::QualifiedIdentifier.new(qualifier, k)] = h.delete(k)}
250
+ h
251
+ end
239
252
 
240
253
  # Restrict the setting of the primary key(s) inside new/set/update. Because
241
254
  # this is the default, this only make sense to use in a subclass where the
@@ -299,7 +312,6 @@ module Sequel
299
312
  check_non_connection_error{@db_schema = (inherited ? superclass.db_schema : get_db_schema)}
300
313
  self
301
314
  end
302
- alias dataset= set_dataset
303
315
 
304
316
  # Sets the primary key for this model. You can use either a regular
305
317
  # or a composite primary key.
@@ -524,7 +536,7 @@ module Sequel
524
536
  private_class_method :class_attr_overridable, :class_attr_reader
525
537
 
526
538
  class_attr_reader :columns, :db, :primary_key, :db_schema
527
- class_attr_overridable :raise_on_save_failure, :raise_on_typecast_failure, :require_modification, :strict_param_setting, :typecast_empty_string_to_nil, :typecast_on_assignment, :use_transactions
539
+ class_attr_overridable *BOOLEAN_SETTINGS
528
540
 
529
541
  # The hash of attribute values. Keys are symbols with the names of the
530
542
  # underlying database columns.
@@ -576,11 +588,10 @@ module Sequel
576
588
  end
577
589
  end
578
590
 
579
- # Compares model instances by values.
591
+ # Alias of eql?
580
592
  def ==(obj)
581
- (obj.class == model) && (obj.values == @values)
593
+ eql?(obj)
582
594
  end
583
- alias eql? ==
584
595
 
585
596
  # If pk is not nil, true only if the objects have the same class and pk.
586
597
  # If pk is nil, false.
@@ -632,6 +643,11 @@ module Sequel
632
643
  @values.each(&block)
633
644
  end
634
645
 
646
+ # Compares model instances by values.
647
+ def eql?(obj)
648
+ (obj.class == model) && (obj.values == @values)
649
+ end
650
+
635
651
  # Returns the validation errors associated with this object.
636
652
  def errors
637
653
  @errors ||= Errors.new
@@ -772,6 +788,13 @@ module Sequel
772
788
  set_restricted(hash, false, except.flatten)
773
789
  end
774
790
 
791
+ # For each of the fields in the given array +fields+, call the setter
792
+ # method with the value of that +hash+ entry for the field. Returns self.
793
+ def set_fields(hash, fields)
794
+ fields.each{|f| send("#{f}=", hash[f])}
795
+ self
796
+ end
797
+
775
798
  # Set the values using the entries in the hash, only if the key
776
799
  # is included in only.
777
800
  def set_only(hash, *only)
@@ -800,6 +823,13 @@ module Sequel
800
823
  update_restricted(hash, false, except.flatten)
801
824
  end
802
825
 
826
+ # Update the instances values by calling +set_fields+ with the +hash+
827
+ # and +fields+, then save any changes to the record. Returns self.
828
+ def update_fields(hash, fields)
829
+ set_fields(hash, fields)
830
+ save_changes
831
+ end
832
+
803
833
  # Update the values using the entries in the hash, only if the key
804
834
  # is included in only.
805
835
  def update_only(hash, *only)
@@ -808,7 +838,8 @@ module Sequel
808
838
 
809
839
  # Validates the object. If the object is invalid, errors should be added
810
840
  # to the errors attribute. By default, does nothing, as all models
811
- # are valid by default.
841
+ # are valid by default. See the {"Model Validations" guide}[link:files/doc/validations_rdoc.html].
842
+ # for details about validation.
812
843
  def validate
813
844
  end
814
845
 
@@ -855,8 +886,10 @@ module Sequel
855
886
  delete
856
887
  end
857
888
 
889
+ # Insert the record into the database, returning the primary key if
890
+ # the record should be refreshed from the database.
858
891
  def _insert
859
- ds = model.dataset
892
+ ds = _insert_dataset
860
893
  if ds.respond_to?(:insert_select) and h = ds.insert_select(@values)
861
894
  @values = h
862
895
  nil
@@ -871,6 +904,12 @@ module Sequel
871
904
  end
872
905
  end
873
906
 
907
+ # The dataset to use when inserting a new object. The same as the model's
908
+ # dataset by default.
909
+ def _insert_dataset
910
+ model.dataset
911
+ end
912
+
874
913
  # Refresh using a particular dataset, used inside save to make sure the same server
875
914
  # is used for reading newly inserted values from the database
876
915
  def _refresh(dataset)
@@ -886,30 +925,28 @@ module Sequel
886
925
  if new?
887
926
  return save_failure(:create) if before_create == false
888
927
  pk = _insert
889
- @this = nil if pk
928
+ @this = nil
890
929
  @new = false
891
930
  @was_new = true
892
931
  after_create
893
932
  after_save
894
933
  @was_new = nil
895
- if pk
896
- ds = this
897
- ds = ds.server(:default) unless ds.opts[:server]
898
- _refresh(ds)
899
- else
900
- changed_columns.clear
901
- end
934
+ pk ? _save_refresh : changed_columns.clear
902
935
  else
903
936
  return save_failure(:update) if before_update == false
904
937
  if columns.empty?
905
- @columns_updated = opts[:changed] ? @values.reject{|k,v| !changed_columns.include?(k)} : @values.dup
938
+ @columns_updated = if opts[:changed]
939
+ @values.reject{|k,v| !changed_columns.include?(k)}
940
+ else
941
+ _save_update_all_columns_hash
942
+ end
906
943
  changed_columns.clear
907
944
  else # update only the specified columns
908
945
  @columns_updated = @values.reject{|k, v| !columns.include?(k)}
909
946
  changed_columns.reject!{|c| columns.include?(c)}
910
947
  end
911
- Array(primary_key).each{|x| @columns_updated.delete(x)}
912
948
  _update(@columns_updated) unless @columns_updated.empty?
949
+ @this = nil
913
950
  after_update
914
951
  after_save
915
952
  @columns_updated = nil
@@ -917,6 +954,25 @@ module Sequel
917
954
  @modified = false
918
955
  self
919
956
  end
957
+
958
+ # Refresh the object after saving it, used to get
959
+ # default values of all columns. Separated from _save so it
960
+ # can be overridden to avoid the refresh.
961
+ def _save_refresh
962
+ _refresh(this.opts[:server] ? this : this.server(:default))
963
+ end
964
+
965
+ # Return a hash of values used when saving all columns of an
966
+ # existing object (i.e. not passing specific columns to save
967
+ # or using update/save_changes). Defaults to all of the
968
+ # object's values except unmodified primary key columns, as some
969
+ # databases don't like you setting primary key values even
970
+ # to their existing values.
971
+ def _save_update_all_columns_hash
972
+ v = @values.dup
973
+ Array(primary_key).each{|x| v.delete(x) unless changed_columns.include?(x)}
974
+ v
975
+ end
920
976
 
921
977
  # Update this instance's dataset with the supplied column hash.
922
978
  def _update(columns)
@@ -946,7 +1002,7 @@ module Sequel
946
1002
  end
947
1003
 
948
1004
  # If transactions should be used, wrap the yield in a transaction block.
949
- def checked_transaction(opts)
1005
+ def checked_transaction(opts={})
950
1006
  use_transaction?(opts) ? db.transaction(opts){yield} : yield
951
1007
  end
952
1008