sequel 4.14.0 → 4.15.0

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