sequel 3.2.0 → 3.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. data/CHANGELOG +40 -0
  2. data/Rakefile +1 -1
  3. data/doc/opening_databases.rdoc +7 -0
  4. data/doc/release_notes/3.3.0.txt +192 -0
  5. data/lib/sequel/adapters/ado.rb +34 -39
  6. data/lib/sequel/adapters/ado/mssql.rb +30 -0
  7. data/lib/sequel/adapters/jdbc.rb +27 -4
  8. data/lib/sequel/adapters/jdbc/h2.rb +14 -3
  9. data/lib/sequel/adapters/jdbc/mssql.rb +51 -0
  10. data/lib/sequel/adapters/mysql.rb +28 -12
  11. data/lib/sequel/adapters/odbc.rb +36 -30
  12. data/lib/sequel/adapters/odbc/mssql.rb +44 -0
  13. data/lib/sequel/adapters/shared/mssql.rb +185 -10
  14. data/lib/sequel/adapters/shared/mysql.rb +9 -9
  15. data/lib/sequel/adapters/shared/sqlite.rb +45 -47
  16. data/lib/sequel/connection_pool.rb +8 -5
  17. data/lib/sequel/core.rb +2 -8
  18. data/lib/sequel/database.rb +9 -10
  19. data/lib/sequel/database/schema_sql.rb +3 -2
  20. data/lib/sequel/dataset.rb +1 -0
  21. data/lib/sequel/dataset/sql.rb +15 -6
  22. data/lib/sequel/extensions/schema_dumper.rb +7 -7
  23. data/lib/sequel/model/associations.rb +16 -14
  24. data/lib/sequel/model/base.rb +25 -7
  25. data/lib/sequel/plugins/association_proxies.rb +41 -0
  26. data/lib/sequel/plugins/many_through_many.rb +0 -1
  27. data/lib/sequel/sql.rb +8 -11
  28. data/lib/sequel/version.rb +1 -1
  29. data/spec/adapters/mysql_spec.rb +42 -38
  30. data/spec/adapters/sqlite_spec.rb +0 -4
  31. data/spec/core/database_spec.rb +22 -1
  32. data/spec/core/dataset_spec.rb +37 -12
  33. data/spec/core/expression_filters_spec.rb +5 -0
  34. data/spec/core/schema_spec.rb +15 -8
  35. data/spec/extensions/association_proxies_spec.rb +47 -0
  36. data/spec/extensions/caching_spec.rb +2 -2
  37. data/spec/extensions/hook_class_methods_spec.rb +6 -6
  38. data/spec/extensions/many_through_many_spec.rb +13 -0
  39. data/spec/extensions/schema_dumper_spec.rb +12 -4
  40. data/spec/extensions/validation_class_methods_spec.rb +3 -3
  41. data/spec/integration/dataset_test.rb +47 -17
  42. data/spec/integration/prepared_statement_test.rb +5 -5
  43. data/spec/integration/schema_test.rb +111 -34
  44. data/spec/model/associations_spec.rb +128 -11
  45. data/spec/model/hooks_spec.rb +7 -6
  46. data/spec/model/model_spec.rb +54 -4
  47. data/spec/model/record_spec.rb +2 -3
  48. data/spec/model/validations_spec.rb +4 -4
  49. metadata +109 -101
  50. data/spec/adapters/ado_spec.rb +0 -93
@@ -91,12 +91,13 @@ module Sequel
91
91
  when :rename_column, :set_column_type, :set_column_null, :set_column_default
92
92
  o = op[:op]
93
93
  opts = schema(table).find{|x| x.first == op[:name]}
94
- old_opts = opts ? opts.last : {}
95
- name = o == :rename_column ? op[:new_name] : op[:name]
96
- type = o == :set_column_type ? op[:type] : old_opts[:db_type]
97
- null = o == :set_column_null ? op[:null] : old_opts[:allow_null]
98
- default = o == :set_column_default ? op[:default] : old_opts[:ruby_default]
99
- "ALTER TABLE #{quote_schema_table(table)} CHANGE COLUMN #{quote_identifier(op[:name])} #{column_definition_sql(op.merge(:name=>name, :type=>type, :null=>null, :default=>default))}"
94
+ opts = opts ? opts.last.dup : {}
95
+ opts[:name] = o == :rename_column ? op[:new_name] : op[:name]
96
+ opts[:type] = o == :set_column_type ? op[:type] : opts[:db_type]
97
+ opts[:null] = o == :set_column_null ? op[:null] : opts[:allow_null]
98
+ opts[:default] = o == :set_column_default ? op[:default] : opts[:ruby_default]
99
+ opts.delete(:default) if opts[:default] == nil
100
+ "ALTER TABLE #{quote_schema_table(table)} CHANGE COLUMN #{quote_identifier(op[:name])} #{column_definition_sql(opts)}"
100
101
  when :drop_index
101
102
  "#{drop_index_sql(table, op)} ON #{quote_schema_table(table)}"
102
103
  else
@@ -187,10 +188,9 @@ module Sequel
187
188
  column[:only_time] ? :time : :datetime
188
189
  end
189
190
 
190
- # MySQL doesn't have a true boolean class, so it uses tinyint
191
- # MySQL doesn't have a true boolean class, so it uses tinyint
191
+ # MySQL doesn't have a true boolean class, so it uses tinyint(1)
192
192
  def type_literal_generic_trueclass(column)
193
- :tinyint
193
+ :'tinyint(1)'
194
194
  end
195
195
  end
196
196
 
@@ -107,48 +107,26 @@ module Sequel
107
107
  # the column inside of a transaction.
108
108
  def alter_table_sql(table, op)
109
109
  case op[:op]
110
- when :add_column, :add_index, :drop_index
110
+ when :add_index, :drop_index
111
111
  super
112
+ when :add_column
113
+ if op[:unique] || op[:primary_key]
114
+ duplicate_table(table){|columns| columns.push(op)}
115
+ else
116
+ super
117
+ end
112
118
  when :drop_column
113
- qt = quote_schema_table(table)
114
- bt = quote_identifier(backup_table_name(qt.gsub('`', '')))
115
- columns_str = dataset.send(:identifier_list, columns_for(table, :except => op[:name]))
116
- defined_columns_str = column_list_sql(defined_columns_for(table, :except => op[:name]))
117
- ["CREATE TEMPORARY TABLE #{bt}(#{defined_columns_str})",
118
- "INSERT INTO #{bt} SELECT #{columns_str} FROM #{qt}",
119
- "DROP TABLE #{qt}",
120
- "CREATE TABLE #{qt}(#{defined_columns_str})",
121
- "INSERT INTO #{qt} SELECT #{columns_str} FROM #{bt}",
122
- "DROP TABLE #{bt}"]
119
+ ocp = lambda{|oc| oc.delete_if{|c| c.to_s == op[:name].to_s}}
120
+ duplicate_table(table, :old_columns_proc=>ocp){|columns| columns.delete_if{|s| s[:name].to_s == op[:name].to_s}}
123
121
  when :rename_column
124
- qt = quote_schema_table(table)
125
- bt = quote_identifier(backup_table_name(qt.gsub('`', '')))
126
- old_columns = dataset.send(:identifier_list, columns_for(table))
127
- new_columns_arr = columns_for(table)
128
-
129
- # Replace the old column in place. This is extremely important.
130
- new_columns_arr[new_columns_arr.index(op[:name])] = op[:new_name]
131
-
132
- new_columns = dataset.send(:identifier_list, new_columns_arr)
133
-
134
- def_old_columns = column_list_sql(defined_columns_for(table))
135
-
136
- def_new_columns_arr = defined_columns_for(table).map do |c|
137
- c[:name] = op[:new_name].to_s if c[:name] == op[:name].to_s
138
- c
139
- end
140
-
141
- def_new_columns = column_list_sql(def_new_columns_arr)
142
-
143
- [
144
- "CREATE TEMPORARY TABLE #{bt}(#{def_old_columns})",
145
- "INSERT INTO #{bt}(#{old_columns}) SELECT #{old_columns} FROM #{qt}",
146
- "DROP TABLE #{qt}",
147
- "CREATE TABLE #{qt}(#{def_new_columns})",
148
- "INSERT INTO #{qt}(#{new_columns}) SELECT #{old_columns} FROM #{bt}",
149
- "DROP TABLE #{bt}"
150
- ]
151
-
122
+ ncp = lambda{|nc| nc.map!{|c| c.to_s == op[:name].to_s ? op[:new_name] : c}}
123
+ duplicate_table(table, :new_columns_proc=>ncp){|columns| columns.each{|s| s[:name] = op[:new_name] if s[:name].to_s == op[:name].to_s}}
124
+ when :set_column_default
125
+ duplicate_table(table){|columns| columns.each{|s| s[:default] = op[:default] if s[:name].to_s == op[:name].to_s}}
126
+ when :set_column_null
127
+ duplicate_table(table){|columns| columns.each{|s| s[:null] = op[:null] if s[:name].to_s == op[:name].to_s}}
128
+ when :set_column_type
129
+ duplicate_table(table){|columns| columns.each{|s| s[:type] = op[:type] if s[:name].to_s == op[:name].to_s}}
152
130
  else
153
131
  raise Error, "Unsupported ALTER TABLE operation"
154
132
  end
@@ -156,19 +134,13 @@ module Sequel
156
134
 
157
135
  # The array of column symbols in the table, except for ones given in opts[:except]
158
136
  def backup_table_name(table, opts={})
137
+ table = table.gsub('`', '')
159
138
  (opts[:times]||1000).times do |i|
160
139
  table_name = "#{table}_backup#{i}"
161
140
  return table_name unless table_exists?(table_name)
162
141
  end
163
142
  end
164
143
 
165
- # The array of column symbols in the table, except for ones given in opts[:except]
166
- def columns_for(table, opts={})
167
- cols = schema_parse_table(table, {}).map{|c| c[0]}
168
- cols = cols - Array(opts[:except])
169
- cols
170
- end
171
-
172
144
  # Allow use without a generator, needed for the alter table hackery that Sequel allows.
173
145
  def column_list_sql(generator)
174
146
  generator.is_a?(Schema::Generator) ? super : generator.map{|c| column_definition_sql(c)}.join(', ')
@@ -177,7 +149,10 @@ module Sequel
177
149
  # The array of column schema hashes, except for the ones given in opts[:except]
178
150
  def defined_columns_for(table, opts={})
179
151
  cols = parse_pragma(table, {})
180
- cols.each{|c| c[:default] = LiteralString.new(c[:default]) if c[:default]}
152
+ cols.each do |c|
153
+ c[:default] = LiteralString.new(c[:default]) if c[:default]
154
+ c[:type] = c[:db_type]
155
+ end
181
156
  if opts[:except]
182
157
  nono= Array(opts[:except]).compact.map{|n| n.to_s}
183
158
  cols.reject!{|c| nono.include? c[:name] }
@@ -185,6 +160,29 @@ module Sequel
185
160
  cols
186
161
  end
187
162
 
163
+ # Duplicate an existing table by creating a new table, copying all records
164
+ # from the existing table into the new table, deleting the existing table
165
+ # and renaming the new table to the existing table's name.
166
+ def duplicate_table(table, opts={})
167
+ def_columns = defined_columns_for(table)
168
+ old_columns = def_columns.map{|c| c[:name]}
169
+ opts[:old_columns_proc].call(old_columns) if opts[:old_columns_proc]
170
+
171
+ yield def_columns if block_given?
172
+ def_columns_str = column_list_sql(def_columns)
173
+ new_columns = old_columns.dup
174
+ opts[:new_columns_proc].call(new_columns) if opts[:new_columns_proc]
175
+
176
+ qt = quote_schema_table(table)
177
+ bt = quote_identifier(backup_table_name(qt))
178
+ [
179
+ "CREATE TABLE #{bt}(#{def_columns_str})",
180
+ "INSERT INTO #{bt}(#{dataset.send(:identifier_list, new_columns)}) SELECT #{dataset.send(:identifier_list, old_columns)} FROM #{qt}",
181
+ "DROP TABLE #{qt}",
182
+ "ALTER TABLE #{bt} RENAME TO #{qt}"
183
+ ]
184
+ end
185
+
188
186
  # SQLite folds unquoted identifiers to lowercase, so it shouldn't need to upcase identifiers on input.
189
187
  def identifier_input_method_default
190
188
  nil
@@ -95,16 +95,19 @@ class Sequel::ConnectionPool
95
95
  def hold(server=:default)
96
96
  begin
97
97
  t = Thread.current
98
- time = Time.new
99
- timeout = time + @timeout
100
- sleep_time = @sleep_time
101
98
  if conn = owned_connection(t, server)
102
99
  return yield(conn)
103
100
  end
104
101
  begin
105
- until conn = acquire(t, server)
106
- raise(::Sequel::PoolTimeout) if Time.new > timeout
102
+ unless conn = acquire(t, server)
103
+ time = Time.new
104
+ timeout = time + @timeout
105
+ sleep_time = @sleep_time
107
106
  sleep sleep_time
107
+ until conn = acquire(t, server)
108
+ raise(::Sequel::PoolTimeout) if Time.new > timeout
109
+ sleep sleep_time
110
+ end
108
111
  end
109
112
  yield conn
110
113
  rescue Sequel::DatabaseDisconnectError => dde
data/lib/sequel/core.rb CHANGED
@@ -15,12 +15,7 @@
15
15
  # object, which is closed (disconnected) when the block exits, just
16
16
  # like a block passed to connect. For example:
17
17
  #
18
- # Sequel.sqlite('blog.db'){|db| puts db[:users].count}
19
- #
20
- # Sequel converts the column type tinyint to a boolean by default,
21
- # you can override the conversion to use tinyint as an integer:
22
- #
23
- # Sequel.convert_tinyint_to_bool = false
18
+ # Sequel.sqlite('blog.db'){|db| puts db[:users].count}
24
19
  #
25
20
  # Sequel converts two digit years in Dates and DateTimes by default,
26
21
  # so 01/02/03 is interpreted at January 2nd, 2003, and 12/13/99 is interpreted
@@ -37,13 +32,12 @@
37
32
  # You can set the SEQUEL_NO_CORE_EXTENSIONS constant or environment variable to have
38
33
  # Sequel not extend the core classes.
39
34
  module Sequel
40
- @convert_tinyint_to_bool = true
41
35
  @convert_two_digit_years = true
42
36
  @datetime_class = Time
43
37
  @virtual_row_instance_eval = true
44
38
 
45
39
  class << self
46
- attr_accessor :convert_tinyint_to_bool, :convert_two_digit_years, :datetime_class, :virtual_row_instance_eval
40
+ attr_accessor :convert_two_digit_years, :datetime_class, :virtual_row_instance_eval
47
41
  end
48
42
 
49
43
  # Returns true if the passed object could be a specifier of conditions, false otherwise.
@@ -137,9 +137,10 @@ module Sequel
137
137
  scheme = uri.scheme
138
138
  scheme = :dbi if scheme =~ /\Adbi-/
139
139
  c = adapter_class(scheme)
140
- uri_options = {}
140
+ uri_options = c.send(:uri_to_options, uri)
141
141
  uri.query.split('&').collect{|s| s.split('=')}.each{|k,v| uri_options[k.to_sym] = v} unless uri.query.to_s.strip.empty?
142
- opts = c.send(:uri_to_options, uri).merge(uri_options).merge(opts)
142
+ uri_options.entries.each{|k,v| uri_options[k] = URI.unescape(v) if v.is_a?(String)}
143
+ opts = uri_options.merge(opts)
143
144
  end
144
145
  when Hash
145
146
  opts = conn_string.merge(opts)
@@ -846,27 +847,25 @@ module Sequel
846
847
  # integer, string, date, datetime, boolean, and float.
847
848
  def schema_column_type(db_type)
848
849
  case db_type
849
- when /\Atinyint/io
850
- Sequel.convert_tinyint_to_bool ? :boolean : :integer
851
850
  when /\Ainterval\z/io
852
851
  :interval
853
- when /\A(character( varying)?|(var)?char|text)/io
852
+ when /\A(character( varying)?|n?(var)?char|n?text)/io
854
853
  :string
855
- when /\A(int(eger)?|bigint|smallint)/io
854
+ when /\A(int(eger)?|(big|small|tiny)int)/io
856
855
  :integer
857
856
  when /\Adate\z/io
858
857
  :date
859
- when /\A(datetime|timestamp( with(out)? time zone)?)\z/io
858
+ when /\A((small)?datetime|timestamp( with(out)? time zone)?)\z/io
860
859
  :datetime
861
860
  when /\Atime( with(out)? time zone)?\z/io
862
861
  :time
863
- when /\Aboolean\z/io
862
+ when /\A(boolean|bit)\z/io
864
863
  :boolean
865
864
  when /\A(real|float|double( precision)?)\z/io
866
865
  :float
867
- when /\A(((numeric|decimal)(\(\d+,\d+\))?)|money)\z/io
866
+ when /\A(((numeric|decimal)(\(\d+,\d+\))?)|(small)?money)\z/io
868
867
  :decimal
869
- when /bytea|blob/io
868
+ when /bytea|blob|image|(var)?binary/io
870
869
  :blob
871
870
  end
872
871
  end
@@ -69,8 +69,9 @@ module Sequel
69
69
  def column_definition_sql(column)
70
70
  sql = "#{quote_identifier(column[:name])} #{type_literal(column)}"
71
71
  sql << UNIQUE if column[:unique]
72
- sql << NOT_NULL if column[:null] == false
73
- sql << NULL if column[:null] == true
72
+ null = column.include?(:null) ? column[:null] : column[:allow_null]
73
+ sql << NOT_NULL if null == false
74
+ sql << NULL if null == true
74
75
  sql << " DEFAULT #{literal(column[:default])}" if column.include?(:default)
75
76
  sql << PRIMARY_KEY if column[:primary_key]
76
77
  sql << " #{auto_increment_sql}" if column[:auto_increment]
@@ -342,6 +342,7 @@ module Sequel
342
342
  # Modify the identifier returned from the database based on the
343
343
  # identifier_output_method.
344
344
  def output_identifier(v)
345
+ v = 'untitled' if v == ''
345
346
  (i = identifier_output_method) ? v.to_s.send(i).to_sym : v.to_sym
346
347
  end
347
348
 
@@ -272,14 +272,16 @@ module Sequel
272
272
  end
273
273
 
274
274
  # Returns a dataset selecting from the current dataset.
275
+ # Supplying the :alias option controls the name of the result.
275
276
  #
276
- # ds = DB[:items].order(:name)
277
- # ds.sql #=> "SELECT * FROM items ORDER BY name"
278
- # ds.from_self.sql #=> "SELECT * FROM (SELECT * FROM items ORDER BY name)"
279
- def from_self
277
+ # ds = DB[:items].order(:name).select(:id, :name)
278
+ # ds.sql #=> "SELECT id,name FROM items ORDER BY name"
279
+ # ds.from_self.sql #=> "SELECT * FROM (SELECT id, name FROM items ORDER BY name) AS 't1'"
280
+ # ds.from_self(:alias=>:foo).sql #=> "SELECT * FROM (SELECT id, name FROM items ORDER BY name) AS 'foo'"
281
+ def from_self(opts={})
280
282
  fs = {}
281
283
  @opts.keys.each{|k| fs[k] = nil}
282
- clone(fs).from(self)
284
+ clone(fs).from(opts[:alias] ? as(opts[:alias]) : self)
283
285
  end
284
286
 
285
287
  # SQL fragment specifying an SQL function call
@@ -453,7 +455,7 @@ module Sequel
453
455
  # * options - a hash of options, with any of the following keys:
454
456
  # * :table_alias - the name of the table's alias when joining, necessary for joining
455
457
  # to the same table more than once. No alias is used by default.
456
- # * :implicit_qualifer - The name to use for qualifying implicit conditions. By default,
458
+ # * :implicit_qualifier - The name to use for qualifying implicit conditions. By default,
457
459
  # the last joined or primary table is used.
458
460
  # * block - The block argument should only be given if a JOIN with an ON clause is used,
459
461
  # in which case it yields the table alias/name for the table currently being joined,
@@ -797,6 +799,13 @@ module Sequel
797
799
  opts = {:all=>opts} unless opts.is_a?(Hash)
798
800
  compound_clone(:union, dataset, opts)
799
801
  end
802
+
803
+ # Returns a copy of the dataset with no limit or offset.
804
+ #
805
+ # dataset.limit(10, 20).unlimited # SELECT * FROM items
806
+ def unlimited
807
+ clone(:limit=>nil, :offset=>nil)
808
+ end
800
809
 
801
810
  # Returns a copy of the dataset with no order.
802
811
  #
@@ -109,30 +109,30 @@ END_MIG
109
109
  case t = schema[:db_type].downcase
110
110
  when /\A(?:medium|small)?int(?:eger)?(?:\((?:\d+)\))?\z/o
111
111
  {:type=>Integer}
112
- when /\Atinyint(?:\((?:\d+)\))?\z/o
113
- {:type=>(Sequel.convert_tinyint_to_bool ? TrueClass : Integer)}
112
+ when /\Atinyint(?:\((\d+)\))?\z/o
113
+ {:type=>(self.class.adapter_scheme == :mysql && $1 == '1' && Sequel::MySQL.convert_tinyint_to_bool ? TrueClass : Integer)}
114
114
  when /\Abigint(?:\((?:\d+)\))?\z/o
115
115
  {:type=>Bignum}
116
116
  when /\A(?:real|float|double(?: precision)?)\z/o
117
117
  {:type=>Float}
118
118
  when 'boolean'
119
119
  {:type=>TrueClass}
120
- when /\A(?:(?:tiny|medium|long)?text|clob)\z/o
120
+ when /\A(?:(?:tiny|medium|long|n)?text|clob)\z/o
121
121
  {:type=>String, :text=>true}
122
122
  when 'date'
123
123
  {:type=>Date}
124
- when 'datetime'
124
+ when /\A(?:small)?datetime\z/o
125
125
  {:type=>DateTime}
126
126
  when /\Atimestamp(?: with(?:out)? time zone)?\z/o
127
127
  {:type=>DateTime}
128
128
  when /\Atime(?: with(?:out)? time zone)?\z/o
129
129
  {:type=>Time, :only_time=>true}
130
- when /\Achar(?:acter)?(?:\((\d+)\))?\z/o
130
+ when /\An?char(?:acter)?(?:\((\d+)\))?\z/o
131
131
  {:type=>String, :size=>($1.to_i if $1), :fixed=>true}
132
- when /\A(?:varchar|character varying|bpchar|string)(?:\((\d+)\))?\z/o
132
+ when /\A(?:n?varchar|character varying|bpchar|string)(?:\((\d+)\))?\z/o
133
133
  s = ($1.to_i if $1)
134
134
  {:type=>String, :size=>(s == 255 ? nil : s)}
135
- when 'money'
135
+ when /\A(?:small)?money\z/o
136
136
  {:type=>BigDecimal, :size=>[19,2]}
137
137
  when /\A(?:decimal|numeric|number)(?:\((\d+)(?:,\s*(\d+))?\))?\z/o
138
138
  s = [($1.to_i if $1), ($2.to_i if $2)].compact
@@ -548,6 +548,7 @@ module Sequel
548
548
  when Class
549
549
  opts[:class_name] ||= opts[:class].name
550
550
  end
551
+ opts[:class_name] ||= ((self.name || '').split("::")[0..-2] + [camelize(opts.returns_array? ? singularize(name) : name)]).join('::')
551
552
 
552
553
  send(:"def_#{type}", opts)
553
554
 
@@ -625,17 +626,21 @@ module Sequel
625
626
 
626
627
  # Add the add_ instance method
627
628
  def def_add_method(opts)
628
- association_module_def(opts.add_method){|o| add_associated_object(opts, o)}
629
+ association_module_def(opts.add_method){|o,*args| add_associated_object(opts, o, *args)}
629
630
  end
630
631
 
631
- # Adds methods to the module included in the class related to getting the
632
- # dataset and associated object(s).
632
+ # Adds methods related to the association's dataset to the module included in the class.
633
633
  def def_association_dataset_methods(opts)
634
634
  # If a block is given, define a helper method for it, because it takes
635
635
  # an argument. This is unnecessary in Ruby 1.9, as that has instance_exec.
636
636
  association_module_private_def(opts.dataset_helper_method, &opts[:block]) if opts[:block]
637
637
  association_module_private_def(opts._dataset_method, &opts[:dataset])
638
638
  association_module_def(opts.dataset_method){_dataset(opts)}
639
+ def_association_method(opts)
640
+ end
641
+
642
+ # Adds method for retrieving the associated objects to the module included in the class.
643
+ def def_association_method(opts)
639
644
  association_module_def(opts.association_method){|*reload| load_associated_objects(opts, reload[0])}
640
645
  end
641
646
 
@@ -646,7 +651,6 @@ module Sequel
646
651
  left = (opts[:left_key] ||= opts.default_left_key)
647
652
  right = (opts[:right_key] ||= opts.default_right_key)
648
653
  left_pk = (opts[:left_primary_key] ||= self.primary_key)
649
- opts[:class_name] ||= camelize(singularize(name))
650
654
  opts[:cartesian_product_number] ||= 1
651
655
  join_table = (opts[:join_table] ||= opts.default_join_table)
652
656
  left_key_alias = opts[:left_key_alias] ||= opts.default_associated_key_alias
@@ -705,7 +709,6 @@ module Sequel
705
709
  opts[:key] = opts.default_key unless opts.include?(:key)
706
710
  key = opts[:key]
707
711
  opts[:cartesian_product_number] ||= 0
708
- opts[:class_name] ||= camelize(name)
709
712
  opts[:dataset] ||= proc do
710
713
  klass = opts.associated_class
711
714
  klass.filter(SQL::QualifiedIdentifier.new(klass.table_name, opts.primary_key)=>send(key))
@@ -750,7 +753,6 @@ module Sequel
750
753
  model = self
751
754
  key = (opts[:key] ||= opts.default_key)
752
755
  primary_key = (opts[:primary_key] ||= self.primary_key)
753
- opts[:class_name] ||= camelize(singularize(name))
754
756
  opts[:dataset] ||= proc do
755
757
  klass = opts.associated_class
756
758
  klass.filter(SQL::QualifiedIdentifier.new(klass.table_name, key) => send(primary_key))
@@ -828,8 +830,8 @@ module Sequel
828
830
 
829
831
  # Add the remove_ and remove_all instance methods
830
832
  def def_remove_methods(opts)
831
- association_module_def(opts.remove_method){|o| remove_associated_object(opts, o)}
832
- association_module_def(opts.remove_all_method){remove_all_associated_objects(opts)}
833
+ association_module_def(opts.remove_method){|o,*args| remove_associated_object(opts, o, *args)}
834
+ association_module_def(opts.remove_all_method){|*args| remove_all_associated_objects(opts, *args)}
833
835
  end
834
836
  end
835
837
 
@@ -878,11 +880,11 @@ module Sequel
878
880
  end
879
881
 
880
882
  # Add the given associated object to the given association
881
- def add_associated_object(opts, o)
883
+ def add_associated_object(opts, o, *args)
882
884
  raise(Sequel::Error, "model object #{model} does not have a primary key") unless pk
883
885
  raise(Sequel::Error, "associated object #{o.model} does not have a primary key") if opts.need_associated_primary_key? && !o.pk
884
886
  return if run_association_callbacks(opts, :before_add, o) == false
885
- send(opts._add_method, o)
887
+ send(opts._add_method, o, *args)
886
888
  associations[opts[:name]].push(o) if associations.include?(opts[:name])
887
889
  add_reciprocal_object(opts, o)
888
890
  run_association_callbacks(opts, :after_add, o)
@@ -921,20 +923,20 @@ module Sequel
921
923
  end
922
924
 
923
925
  # Remove all associated objects from the given association
924
- def remove_all_associated_objects(opts)
926
+ def remove_all_associated_objects(opts, *args)
925
927
  raise(Sequel::Error, "model object #{model} does not have a primary key") unless pk
926
- send(opts._remove_all_method)
928
+ send(opts._remove_all_method, *args)
927
929
  ret = associations[opts[:name]].each{|o| remove_reciprocal_object(opts, o)} if associations.include?(opts[:name])
928
930
  associations[opts[:name]] = []
929
931
  ret
930
932
  end
931
933
 
932
934
  # Remove the given associated object from the given association
933
- def remove_associated_object(opts, o)
935
+ def remove_associated_object(opts, o, *args)
934
936
  raise(Sequel::Error, "model object #{model} does not have a primary key") unless pk
935
937
  raise(Sequel::Error, "associated object #{o.model} does not have a primary key") if opts.need_associated_primary_key? && !o.pk
936
938
  return if run_association_callbacks(opts, :before_remove, o) == false
937
- send(opts._remove_method, o)
939
+ send(opts._remove_method, o, *args)
938
940
  associations[opts[:name]].delete_if{|x| o === x} if associations.include?(opts[:name])
939
941
  remove_reciprocal_object(opts, o)
940
942
  run_association_callbacks(opts, :after_remove, o)