sequel 4.14.0 → 4.15.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 (56) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +32 -0
  3. data/README.rdoc +3 -3
  4. data/Rakefile +1 -1
  5. data/doc/opening_databases.rdoc +20 -2
  6. data/doc/release_notes/4.15.0.txt +56 -0
  7. data/doc/testing.rdoc +10 -4
  8. data/lib/sequel/adapters/fdbsql.rb +285 -0
  9. data/lib/sequel/adapters/informix.rb +15 -0
  10. data/lib/sequel/adapters/jdbc/fdbsql.rb +65 -0
  11. data/lib/sequel/adapters/mock.rb +1 -0
  12. data/lib/sequel/adapters/shared/fdbsql.rb +550 -0
  13. data/lib/sequel/adapters/shared/postgres.rb +23 -10
  14. data/lib/sequel/database/connecting.rb +1 -1
  15. data/lib/sequel/database/schema_methods.rb +10 -3
  16. data/lib/sequel/dataset/placeholder_literalizer.rb +7 -0
  17. data/lib/sequel/extensions/date_arithmetic.rb +5 -0
  18. data/lib/sequel/extensions/migration.rb +2 -2
  19. data/lib/sequel/extensions/pg_array.rb +15 -1
  20. data/lib/sequel/extensions/pg_json.rb +3 -0
  21. data/lib/sequel/extensions/pg_json_ops.rb +4 -4
  22. data/lib/sequel/extensions/schema_dumper.rb +9 -1
  23. data/lib/sequel/model/associations.rb +70 -21
  24. data/lib/sequel/plugins/active_model.rb +7 -2
  25. data/lib/sequel/plugins/many_through_many.rb +1 -0
  26. data/lib/sequel/plugins/pg_array_associations.rb +2 -1
  27. data/lib/sequel/plugins/split_values.rb +64 -0
  28. data/lib/sequel/version.rb +1 -1
  29. data/spec/adapters/fdbsql_spec.rb +429 -0
  30. data/spec/adapters/informix_spec.rb +6 -0
  31. data/spec/adapters/postgres_spec.rb +49 -1
  32. data/spec/adapters/spec_helper.rb +6 -1
  33. data/spec/adapters/sqlite_spec.rb +1 -1
  34. data/spec/core/placeholder_literalizer_spec.rb +10 -0
  35. data/spec/extensions/date_arithmetic_spec.rb +7 -0
  36. data/spec/extensions/many_through_many_spec.rb +14 -0
  37. data/spec/extensions/migration_spec.rb +3 -3
  38. data/spec/extensions/pg_array_associations_spec.rb +9 -0
  39. data/spec/extensions/pg_json_ops_spec.rb +4 -8
  40. data/spec/extensions/schema_dumper_spec.rb +9 -0
  41. data/spec/extensions/spec_helper.rb +3 -0
  42. data/spec/extensions/split_values_spec.rb +22 -0
  43. data/spec/integration/database_test.rb +1 -1
  44. data/spec/integration/dataset_test.rb +1 -1
  45. data/spec/integration/eager_loader_test.rb +1 -1
  46. data/spec/integration/plugin_test.rb +3 -2
  47. data/spec/integration/prepared_statement_test.rb +3 -3
  48. data/spec/integration/schema_test.rb +3 -3
  49. data/spec/integration/spec_helper.rb +6 -1
  50. data/spec/integration/timezone_test.rb +1 -1
  51. data/spec/model/association_reflection_spec.rb +29 -0
  52. data/spec/model/associations_spec.rb +36 -0
  53. data/spec/model/eager_loading_spec.rb +14 -0
  54. data/spec/model/spec_helper.rb +3 -0
  55. data/spec/rspec_helper.rb +4 -0
  56. metadata +10 -2
@@ -809,27 +809,40 @@ module Sequel
809
809
 
810
810
  # DDL statement for creating a table with the given name, columns, and options
811
811
  def create_table_prefix_sql(name, options)
812
- if on_commit = options[:on_commit]
813
- raise(Error, "can't provide :on_commit without :temp to create_table") unless options[:temp]
814
- raise(Error, "unsupported on_commit option: #{on_commit.inspect}") unless ON_COMMIT.has_key? on_commit
812
+ prefix_sql = if options[:temp]
813
+ raise(Error, "can't provide both :temp and :unlogged to create_table") if options[:unlogged]
814
+ raise(Error, "can't provide both :temp and :foreign to create_table") if options[:foreign]
815
+ temporary_table_sql
816
+ elsif options[:foreign]
817
+ raise(Error, "can't provide both :foreign and :unlogged to create_table") if options[:unlogged]
818
+ 'FOREIGN '
819
+ elsif options[:unlogged]
820
+ UNLOGGED
815
821
  end
816
- temp_or_unlogged_sql = if options[:temp]
817
- raise(Error, "can't provide both :temp and :unlogged to create_table") if options[:unlogged]
818
- temporary_table_sql
819
- elsif options[:unlogged]
820
- UNLOGGED
821
- end
822
- "CREATE #{temp_or_unlogged_sql}TABLE#{' IF NOT EXISTS' if options[:if_not_exists]} #{options[:temp] ? quote_identifier(name) : quote_schema_table(name)}"
822
+
823
+ "CREATE #{prefix_sql}TABLE#{' IF NOT EXISTS' if options[:if_not_exists]} #{options[:temp] ? quote_identifier(name) : quote_schema_table(name)}"
823
824
  end
824
825
 
825
826
  def create_table_sql(name, generator, options)
826
827
  sql = super
828
+
827
829
  if inherits = options[:inherits]
828
830
  sql << " INHERITS (#{Array(inherits).map{|t| quote_schema_table(t)}.join(', ')})"
829
831
  end
832
+
830
833
  if on_commit = options[:on_commit]
834
+ raise(Error, "can't provide :on_commit without :temp to create_table") unless options[:temp]
835
+ raise(Error, "unsupported on_commit option: #{on_commit.inspect}") unless ON_COMMIT.has_key?(on_commit)
831
836
  sql << " ON COMMIT #{ON_COMMIT[on_commit]}"
832
837
  end
838
+
839
+ if server = options[:foreign]
840
+ sql << " SERVER #{quote_identifier(server)}"
841
+ if foreign_opts = options[:options]
842
+ sql << " OPTIONS (#{foreign_opts.map{|k, v| "#{k} #{literal(v.to_s)}"}.join(', ')})"
843
+ end
844
+ end
845
+
833
846
  sql
834
847
  end
835
848
 
@@ -6,7 +6,7 @@ module Sequel
6
6
  # ---------------------
7
7
 
8
8
  # Array of supported database adapters
9
- ADAPTERS = %w'ado amalgalite cubrid db2 dbi do firebird ibmdb informix jdbc mock mysql mysql2 odbc openbase oracle postgres sqlanywhere sqlite swift tinytds'.collect{|x| x.to_sym}
9
+ ADAPTERS = %w'ado amalgalite cubrid db2 dbi do fdbsql firebird ibmdb informix jdbc mock mysql mysql2 odbc openbase oracle postgres sqlanywhere sqlite swift tinytds'.collect{|x| x.to_sym}
10
10
 
11
11
  @single_threaded = false
12
12
 
@@ -171,10 +171,17 @@ module Sequel
171
171
  # :engine :: The table engine to use for the table.
172
172
  #
173
173
  # PostgreSQL specific options:
174
- # :on_commit :: Either :preserve_rows (default), :drop or :delete_rows.
175
- # :unlogged :: Create the table as an unlogged table.
176
- # :inherits :: Inherit from a different tables. An array can be
174
+ # :on_commit :: Either :preserve_rows (default), :drop or :delete_rows. Should
175
+ # only be specified when creating a temporary table.
176
+ # :foreign :: Create a foreign table. The value should be the name of the
177
+ # foreign server that was specified in CREATE SERVER.
178
+ # :inherits :: Inherit from a different table. An array can be
177
179
  # specified to inherit from multiple tables.
180
+ # :unlogged :: Create the table as an unlogged table.
181
+ # :options :: The OPTIONS clause to use for foreign tables. Should be a hash
182
+ # where keys are option names and values are option values. Note
183
+ # that option names are unquoted, so you should not use untrusted
184
+ # keys.
178
185
  #
179
186
  # See <tt>Schema::Generator</tt> and the {"Schema Modification" guide}[rdoc-ref:doc/schema_modification.rdoc].
180
187
  def create_table(name, options=OPTS, &block)
@@ -125,6 +125,13 @@ module Sequel
125
125
  @arity = arity
126
126
  end
127
127
 
128
+ # Return a new PlaceholderLiteralizer with a modified dataset. This yields the
129
+ # receiver's dataset to the block, and the block should return the new dataset
130
+ # to use.
131
+ def with_dataset
132
+ dup.instance_exec{@dataset = yield @dataset; self}
133
+ end
134
+
128
135
  # Return an array of all objects by running the SQL query for the given arguments.
129
136
  # If a block is given, yields all objects to the block after loading them.
130
137
  def all(*args, &block)
@@ -62,6 +62,7 @@ module Sequel
62
62
  DERBY_DURATION_UNITS = DURATION_UNITS.zip(DURATION_UNITS.map{|s| Sequel.lit("SQL_TSI_#{s.to_s.upcase[0...-1]}").freeze}).freeze
63
63
  ACCESS_DURATION_UNITS = DURATION_UNITS.zip(%w'yyyy m d h n s'.map{|s| s.freeze}).freeze
64
64
  DB2_DURATION_UNITS = DURATION_UNITS.zip(DURATION_UNITS.map{|s| Sequel.lit(s.to_s).freeze}).freeze
65
+ FDBSQL_DURATION_UNITS = DURATION_UNITS.zip(DURATION_UNITS.map{|s| Sequel.lit(s.to_s.chop).freeze}).freeze
65
66
 
66
67
  # Append the SQL fragment for the DateAdd expression to the SQL query.
67
68
  def date_add_sql_append(sql, da)
@@ -122,6 +123,10 @@ module Sequel
122
123
  expr = Sequel.+(expr, Sequel.lit(["", " "], value, sql_unit))
123
124
  end
124
125
  false
126
+ when :fdbsql
127
+ each_valid_interval_unit(h, FDBSQL_DURATION_UNITS) do |value, sql_unit|
128
+ expr = Sequel.+(expr, Sequel.lit(["INTERVAL ", " "], value, sql_unit))
129
+ end
125
130
  else
126
131
  raise Error, "date arithmetic is not implemented on #{db.database_type}"
127
132
  end
@@ -207,8 +207,8 @@ module Sequel
207
207
  @actions << [:drop_table, args.first]
208
208
  end
209
209
 
210
- def create_view(*args)
211
- @actions << [:drop_view, args.first]
210
+ def create_view(name, _, options={})
211
+ @actions << [:drop_view, name, options]
212
212
  end
213
213
 
214
214
  def rename_column(table, name, new_name)
@@ -203,7 +203,7 @@ module Sequel
203
203
  procs = conversion_procs
204
204
  procs[1115] = Creator.new("timestamp without time zone", procs[1114])
205
205
  procs[1185] = Creator.new("timestamp with time zone", procs[1184])
206
- copy_conversion_procs([1009, 1007, 1016, 1231, 1022, 1000, 1001, 1182, 1183, 1270, 1005, 1028, 1021, 1014, 1015])
206
+ copy_conversion_procs([143, 791, 1000, 1001, 1003, 1005, 1006, 1007, 1009, 1010, 1011, 1012, 1013, 1014, 1015, 1016, 1021, 1022, 1028, 1182, 1183, 1231, 1270, 1561, 1563, 2951])
207
207
  [:string_array, :integer_array, :decimal_array, :float_array, :boolean_array, :blob_array, :date_array, :time_array, :datetime_array].each do |v|
208
208
  @schema_type_classes[v] = PGArray
209
209
  end
@@ -542,6 +542,20 @@ module Sequel
542
542
  register('real', :oid=>1021, :scalar_oid=>700, :scalar_typecast=>:float)
543
543
  register('character', :oid=>1014, :array_type=>:text, :scalar_typecast=>:string)
544
544
  register('character varying', :oid=>1015, :scalar_typecast=>:string, :type_symbol=>:varchar)
545
+
546
+ register('xml', :oid=>143, :scalar_oid=>142)
547
+ register('money', :oid=>791, :scalar_oid=>790)
548
+ register('bit', :oid=>1561, :scalar_oid=>1560)
549
+ register('bit varying', :oid=>1563, :scalar_oid=>1562, :type_symbol=>:varbit)
550
+ register('uuid', :oid=>2951, :scalar_oid=>2950)
551
+
552
+ register('xid', :oid=>1011, :scalar_oid=>28)
553
+ register('cid', :oid=>1012, :scalar_oid=>29)
554
+
555
+ register('name', :oid=>1003, :scalar_oid=>19)
556
+ register('tid', :oid=>1010, :scalar_oid=>27)
557
+ register('int2vector', :oid=>1006, :scalar_oid=>22)
558
+ register('oidvector', :oid=>1013, :scalar_oid=>30)
545
559
  end
546
560
  end
547
561
 
@@ -56,6 +56,9 @@
56
56
  #
57
57
  # DB.extension :pg_array, :pg_json
58
58
  #
59
+ # Note that when accessing json hashes, you should always use strings for keys.
60
+ # Attempting to use other values (such as symbols) will not work correctly.
61
+ #
59
62
  # This extension requires both the json and delegate libraries.
60
63
 
61
64
  require 'delegate'
@@ -188,16 +188,16 @@ module Sequel
188
188
  # structure of the record using #as on the resulting object:
189
189
  #
190
190
  # json_op.to_record.as(:x, [Sequel.lit('a integer'), Sequel.lit('b text')]) # json_to_record(json) AS x(a integer, b text)
191
- def to_record(nested_as_text=false)
192
- function(:to_record, nested_as_text)
191
+ def to_record
192
+ function(:to_record)
193
193
  end
194
194
 
195
195
  # Builds arbitrary set of records from json array of objects. You need to define the
196
196
  # structure of the records using #as on the resulting object:
197
197
  #
198
198
  # json_op.to_recordset.as(:x, [Sequel.lit('a integer'), Sequel.lit('b text')]) # json_to_recordset(json) AS x(a integer, b text)
199
- def to_recordset(nested_as_text=false)
200
- function(:to_recordset, nested_as_text)
199
+ def to_recordset
200
+ function(:to_recordset)
201
201
  end
202
202
 
203
203
  # Returns the type of the outermost json value as text.
@@ -171,7 +171,15 @@ END_MIG
171
171
  gen.primary_key(name, type_hash)
172
172
  end
173
173
  else
174
- col_opts = options[:same_db] ? {:type=>schema[:db_type]} : column_schema_to_ruby_type(schema)
174
+ col_opts = if options[:same_db]
175
+ h = {:type=>schema[:db_type]}
176
+ if database_type == :mysql && h[:type] =~ /\Atimestamp/
177
+ h[:null] = true
178
+ end
179
+ h
180
+ else
181
+ column_schema_to_ruby_type(schema)
182
+ end
175
183
  type = col_opts.delete(:type)
176
184
  col_opts.delete(:size) if col_opts[:size].nil?
177
185
  col_opts[:default] = if schema[:ruby_default].nil?
@@ -8,8 +8,11 @@ module Sequel
8
8
 
9
9
  # Set an empty association reflection hash in the model
10
10
  def self.apply(model)
11
- model.instance_variable_set(:@association_reflections, {})
12
- model.instance_variable_set(:@autoreloading_associations, {})
11
+ model.instance_eval do
12
+ @association_reflections = {}
13
+ @autoreloading_associations = {}
14
+ @cache_associations = true
15
+ end
13
16
  end
14
17
 
15
18
  # AssociationReflection is a Hash subclass that keeps information on Sequel::Model associations. It
@@ -244,7 +247,6 @@ module Sequel
244
247
  if eo[:eager_block] || eo[:loader] == false
245
248
  strategy = true_eager_graph_limit_strategy if strategy == :union
246
249
  objects = apply_eager_limit_strategy(eager_loading_dataset(eo), strategy).all
247
- cascade = nil
248
250
  elsif strategy == :union
249
251
  objects = []
250
252
  ds = associated_dataset
@@ -253,12 +255,12 @@ module Sequel
253
255
  eo[:id_map].keys.each_slice(subqueries_per_union).each do |slice|
254
256
  objects.concat(ds.with_sql(slice.map{|k| loader.sql(*k)}.join(joiner)).to_a)
255
257
  end
258
+ ds = ds.eager(cascade) if cascade
259
+ ds.send(:post_load, objects)
256
260
  else
257
- objects = placeholder_eager_loader.all(eo[:id_map].keys)
258
- end
259
-
260
- if cascade && !(cascade = associated_dataset.send(:eager_options_for_associations, [cascade])).empty?
261
- associated_eager_dataset.send(:eager_load, objects, cascade)
261
+ loader = placeholder_eager_loader
262
+ loader = loader.with_dataset{|ds| ds.eager(cascade)} if cascade
263
+ objects = loader.all(eo[:id_map].keys)
262
264
  end
263
265
 
264
266
  objects.each(&block)
@@ -397,7 +399,7 @@ module Sequel
397
399
  end
398
400
 
399
401
  if possible_recips.length == 1
400
- cached_set(:reciprocal_type, possible_recips.first[:type]) if reciprocal_type.is_a?(Array)
402
+ cached_set(:reciprocal_type, possible_recips.first[:type]) if ambiguous_reciprocal_type?
401
403
  possible_recips.first[:name]
402
404
  end
403
405
  end
@@ -468,7 +470,7 @@ module Sequel
468
470
  # in a special sub-hash that always uses this method to synchronize access.
469
471
  def cached_fetch(key)
470
472
  fetch(key) do
471
- h = self[:cache]
473
+ return yield unless h = self[:cache]
472
474
  Sequel.synchronize{return h[key] if h.has_key?(key)}
473
475
  value = yield
474
476
  Sequel.synchronize{h[key] = value}
@@ -477,7 +479,7 @@ module Sequel
477
479
 
478
480
  # Cache the value at the given key, synchronizing access.
479
481
  def cached_set(key, value)
480
- h = self[:cache]
482
+ return unless h = self[:cache]
481
483
  Sequel.synchronize{h[key] = value}
482
484
  end
483
485
  # :nocov:
@@ -485,7 +487,7 @@ module Sequel
485
487
  # On MRI, use a plain fetch, since the GVL will synchronize access.
486
488
  def cached_fetch(key)
487
489
  fetch(key) do
488
- h = self[:cache]
490
+ return yield unless h = self[:cache]
489
491
  h.fetch(key){h[key] = yield}
490
492
  end
491
493
  end
@@ -493,7 +495,8 @@ module Sequel
493
495
  # On MRI, just set the value at the key in the cache, since the GVL
494
496
  # will synchronize access.
495
497
  def cached_set(key, value)
496
- self[:cache][key] = value
498
+ return unless h = self[:cache]
499
+ h[key] = value
497
500
  end
498
501
  end
499
502
 
@@ -503,6 +506,12 @@ module Sequel
503
506
  associated_class.dataset.clone
504
507
  end
505
508
 
509
+ # Whether for the reciprocal type for the given association can not be
510
+ # known in advantage, false by default.
511
+ def ambiguous_reciprocal_type?
512
+ false
513
+ end
514
+
506
515
  # Apply a limit strategy to the given dataset so that filter by
507
516
  # associations works with a limited dataset.
508
517
  def apply_filter_by_associations_limit_strategy(ds)
@@ -645,11 +654,17 @@ module Sequel
645
654
  end
646
655
  end
647
656
 
657
+ # The reciprocal type as an array, should be overridden in reflection subclasses that
658
+ # have ambiguous reciprocal types.
659
+ def possible_reciprocal_types
660
+ [reciprocal_type]
661
+ end
662
+
648
663
  # Whether the given association reflection is possible reciprocal
649
664
  # association for the current association reflection.
650
665
  def reciprocal_association?(assoc_reflect)
651
- Array(reciprocal_type).include?(assoc_reflect[:type]) &&
652
- assoc_reflect.associated_class == self[:model] &&
666
+ possible_reciprocal_types.include?(assoc_reflect[:type]) &&
667
+ (begin; assoc_reflect.associated_class; rescue NameError; end) == self[:model] &&
653
668
  assoc_reflect[:conditions].nil? &&
654
669
  assoc_reflect[:block].nil?
655
670
  end
@@ -759,7 +774,7 @@ module Sequel
759
774
 
760
775
  # The column(s) in the associated table that the key in the current table references (either a symbol or an array).
761
776
  def primary_key
762
- cached_fetch(:primary_key){associated_class.primary_key}
777
+ cached_fetch(:primary_key){associated_class.primary_key || raise(Error, "no primary key specified for #{associated_class.inspect}")}
763
778
  end
764
779
 
765
780
  # The columns in the associated table that the key in the current table references (always an array).
@@ -804,6 +819,12 @@ module Sequel
804
819
 
805
820
  private
806
821
 
822
+ # Reciprocals of many_to_one associations could be either one_to_many or one_to_one,
823
+ # and which is not known in advance.
824
+ def ambiguous_reciprocal_type?
825
+ true
826
+ end
827
+
807
828
  def filter_by_associations_conditions_associated_keys
808
829
  qualify(associated_class.table_name, primary_keys)
809
830
  end
@@ -822,14 +843,36 @@ module Sequel
822
843
  self[:keys]
823
844
  end
824
845
 
846
+ # The reciprocal type of a many_to_one association is either
847
+ # a one_to_many or a one_to_one association.
848
+ def possible_reciprocal_types
849
+ [:one_to_many, :one_to_one]
850
+ end
851
+
852
+ # Whether the given association reflection is possible reciprocal
825
853
  def reciprocal_association?(assoc_reflect)
826
854
  super && self[:keys] == assoc_reflect[:keys] && primary_key == assoc_reflect.primary_key
827
855
  end
828
856
 
829
857
  # The reciprocal type of a many_to_one association is either
830
- # a one_to_many or a one_to_one association.
858
+ # a one_to_many or a one_to_one association, look in the associated class
859
+ # to try to figure out which.
831
860
  def reciprocal_type
832
- cached_fetch(:reciprocal_type){[:one_to_many, :one_to_one]}
861
+ cached_fetch(:reciprocal_type) do
862
+ possible_recips = []
863
+
864
+ associated_class.all_association_reflections.each do |assoc_reflect|
865
+ if reciprocal_association?(assoc_reflect)
866
+ possible_recips << assoc_reflect
867
+ end
868
+ end
869
+
870
+ if possible_recips.length == 1
871
+ possible_recips.first[:type]
872
+ else
873
+ possible_reciprocal_types
874
+ end
875
+ end
833
876
  end
834
877
  end
835
878
 
@@ -1184,7 +1227,7 @@ module Sequel
1184
1227
 
1185
1228
  # The primary key column(s) to use in the associated table (can be symbol or array).
1186
1229
  def right_primary_key
1187
- cached_fetch(:right_primary_key){associated_class.primary_key}
1230
+ cached_fetch(:right_primary_key){associated_class.primary_key || raise(Error, "no primary key specified for #{associated_class.inspect}")}
1188
1231
  end
1189
1232
 
1190
1233
  # The primary key columns to use in the associated table (always array).
@@ -1367,6 +1410,12 @@ module Sequel
1367
1410
  # value changes.
1368
1411
  attr_reader :autoreloading_associations
1369
1412
 
1413
+ # Whether association metadata should be cached in the association reflection. If not cached, it will be computed
1414
+ # on demand. In general you only want to set this to default when using code reloading. When using code reloading,
1415
+ # setting this will make sure that if an associated class is removed or modified, this class will not hang on to
1416
+ # the previous class.
1417
+ attr_accessor :cache_associations
1418
+
1370
1419
  # The default :eager_limit_strategy option to use for limited or offset associations (default: true, causing Sequel
1371
1420
  # to use what it considers the most appropriate strategy).
1372
1421
  attr_accessor :default_eager_limit_strategy
@@ -1600,7 +1649,7 @@ module Sequel
1600
1649
  orig_opts = cloned_assoc[:orig_opts].merge(orig_opts)
1601
1650
  end
1602
1651
 
1603
- opts = orig_opts.merge(:type => type, :name => name, :cache=>{}, :model => self)
1652
+ opts = orig_opts.merge(:type => type, :name => name, :cache=>({} if cache_associations), :model => self)
1604
1653
  opts[:block] = block if block
1605
1654
  if block || orig_opts[:block] || orig_opts[:dataset]
1606
1655
  # It's possible the association is instance specific, in that it depends on
@@ -1683,7 +1732,7 @@ module Sequel
1683
1732
  associate(:one_to_one, name, opts, &block)
1684
1733
  end
1685
1734
 
1686
- Plugins.inherited_instance_variables(self, :@association_reflections=>:dup, :@autoreloading_associations=>:hash_dup, :@default_eager_limit_strategy=>nil)
1735
+ Plugins.inherited_instance_variables(self, :@association_reflections=>:dup, :@autoreloading_associations=>:hash_dup, :@cache_associations=>nil, :@default_eager_limit_strategy=>nil)
1687
1736
  Plugins.def_dataset_methods(self, [:eager, :eager_graph, :eager_graph_with_options, :association_join, :association_full_join, :association_inner_join, :association_left_join, :association_right_join])
1688
1737
 
1689
1738
  private
@@ -4,7 +4,7 @@ module Sequel
4
4
  # The ActiveModel plugin makes Sequel::Model objects
5
5
  # pass the ActiveModel::Lint tests, which should
6
6
  # hopefully mean full ActiveModel compliance. This should
7
- # allow the full support of Sequel::Model objects in Rails 3.
7
+ # allow the full support of Sequel::Model objects in Rails 3+.
8
8
  # This plugin requires active_model in order to use
9
9
  # ActiveModel::Naming.
10
10
  #
@@ -43,7 +43,12 @@ module Sequel
43
43
  super
44
44
  @destroyed = true
45
45
  end
46
-
46
+
47
+ # Return ::ActiveModel::Name instance for the class.
48
+ def model_name
49
+ model.model_name
50
+ end
51
+
47
52
  # False if the object is new? or has been destroyed, true otherwise.
48
53
  def persisted?
49
54
  !new? && @destroyed != true
@@ -229,6 +229,7 @@ module Sequel
229
229
  opts[:left_keys] = Array(left_key)
230
230
  opts[:uses_left_composite_keys] = left_key.is_a?(Array)
231
231
  left_pk = (opts[:left_primary_key] ||= self.primary_key)
232
+ raise(Error, "no primary key specified for #{inspect}") unless left_pk
232
233
  opts[:eager_loader_key] = left_pk unless opts.has_key?(:eager_loader_key)
233
234
  opts[:left_primary_keys] = Array(left_pk)
234
235
  lpkc = opts[:left_primary_key_column] ||= left_pk