sequel 3.2.0 → 3.3.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 (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)