sequel 3.11.0 → 3.12.0

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