sequel 2.6.0 → 2.7.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 +64 -0
  2. data/Rakefile +1 -1
  3. data/lib/sequel_core/adapters/jdbc.rb +6 -2
  4. data/lib/sequel_core/adapters/jdbc/oracle.rb +23 -0
  5. data/lib/sequel_core/adapters/oracle.rb +4 -77
  6. data/lib/sequel_core/adapters/postgres.rb +39 -26
  7. data/lib/sequel_core/adapters/shared/mssql.rb +0 -1
  8. data/lib/sequel_core/adapters/shared/mysql.rb +1 -1
  9. data/lib/sequel_core/adapters/shared/oracle.rb +82 -0
  10. data/lib/sequel_core/adapters/shared/postgres.rb +65 -46
  11. data/lib/sequel_core/core_ext.rb +10 -0
  12. data/lib/sequel_core/core_sql.rb +7 -0
  13. data/lib/sequel_core/database.rb +22 -0
  14. data/lib/sequel_core/database/schema.rb +1 -1
  15. data/lib/sequel_core/dataset.rb +29 -11
  16. data/lib/sequel_core/dataset/sql.rb +27 -7
  17. data/lib/sequel_core/migration.rb +20 -2
  18. data/lib/sequel_core/object_graph.rb +24 -10
  19. data/lib/sequel_core/schema/generator.rb +22 -9
  20. data/lib/sequel_core/schema/sql.rb +13 -9
  21. data/lib/sequel_core/sql.rb +27 -2
  22. data/lib/sequel_model/association_reflection.rb +251 -141
  23. data/lib/sequel_model/associations.rb +114 -61
  24. data/lib/sequel_model/base.rb +25 -21
  25. data/lib/sequel_model/eager_loading.rb +17 -40
  26. data/lib/sequel_model/hooks.rb +25 -24
  27. data/lib/sequel_model/record.rb +29 -51
  28. data/lib/sequel_model/schema.rb +1 -1
  29. data/lib/sequel_model/validations.rb +13 -3
  30. data/spec/adapters/postgres_spec.rb +104 -18
  31. data/spec/adapters/spec_helper.rb +4 -1
  32. data/spec/integration/eager_loader_test.rb +5 -4
  33. data/spec/integration/spec_helper.rb +4 -1
  34. data/spec/sequel_core/connection_pool_spec.rb +24 -24
  35. data/spec/sequel_core/core_sql_spec.rb +12 -0
  36. data/spec/sequel_core/dataset_spec.rb +77 -2
  37. data/spec/sequel_core/expression_filters_spec.rb +6 -0
  38. data/spec/sequel_core/object_graph_spec.rb +40 -2
  39. data/spec/sequel_core/schema_spec.rb +13 -0
  40. data/spec/sequel_model/association_reflection_spec.rb +8 -8
  41. data/spec/sequel_model/associations_spec.rb +164 -3
  42. data/spec/sequel_model/caching_spec.rb +2 -1
  43. data/spec/sequel_model/eager_loading_spec.rb +107 -3
  44. data/spec/sequel_model/hooks_spec.rb +38 -22
  45. data/spec/sequel_model/model_spec.rb +11 -35
  46. data/spec/sequel_model/plugins_spec.rb +4 -2
  47. data/spec/sequel_model/record_spec.rb +8 -5
  48. data/spec/sequel_model/validations_spec.rb +25 -0
  49. data/spec/spec_config.rb +4 -3
  50. metadata +21 -19
@@ -33,9 +33,7 @@ module Sequel
33
33
  # or an object that responds to .dataset and yields a symbol or a dataset
34
34
  # * join_conditions - Any condition(s) allowed by join_table.
35
35
  # * options - A hash of graph options. The following options are currently used:
36
- # * :table_alias - The alias to use for the table. If not specified, doesn't
37
- # alias the table. You will get an error if the the alias (or table) name is
38
- # used more than once.
36
+ # * :implicit_qualifier - The qualifier of implicit conditions, see #join_table.
39
37
  # * :join_type - The type of join to use (passed to join_table). Defaults to
40
38
  # :left_outer.
41
39
  # * :select - An array of columns to select. When not used, selects
@@ -43,6 +41,9 @@ module Sequel
43
41
  # columns and is like simply joining the tables, though graph keeps
44
42
  # some metadata about join that makes it important to use graph instead
45
43
  # of join.
44
+ # * :table_alias - The alias to use for the table. If not specified, doesn't
45
+ # alias the table. You will get an error if the the alias (or table) name is
46
+ # used more than once.
46
47
  # * block - A block that is passed to join_table.
47
48
  def graph(dataset, join_conditions = nil, options = {}, &block)
48
49
  # Allow the use of a model, dataset, or symbol as the first argument
@@ -69,7 +70,7 @@ module Sequel
69
70
  raise_alias_error.call if @opts[:graph] && @opts[:graph][:table_aliases] && @opts[:graph][:table_aliases].include?(table_alias)
70
71
 
71
72
  # Join the table early in order to avoid cloning the dataset twice
72
- ds = join_table(options[:join_type] || :left_outer, table, join_conditions, table_alias, &block)
73
+ ds = join_table(options[:join_type] || :left_outer, table, join_conditions, :table_alias=>table_alias, :implicit_qualifier=>options[:implicit_qualifier], &block)
73
74
  opts = ds.opts
74
75
 
75
76
  # Whether to include the table in the result set
@@ -155,18 +156,31 @@ module Sequel
155
156
  # The first element of the array should be the table alias,
156
157
  # and the second should be the actual column name.
157
158
  def set_graph_aliases(graph_aliases)
158
- cols = graph_aliases.collect do |col_alias, tc|
159
- identifier = tc[1].qualify(tc[0])
160
- identifier = identifier.as(col_alias) unless tc[1] == col_alias
161
- identifier
162
- end
163
- ds = select(*cols)
159
+ ds = select(*graph_alias_columns(graph_aliases))
164
160
  ds.opts[:graph_aliases] = graph_aliases
165
161
  ds
166
162
  end
167
163
 
164
+ # Adds the give graph aliases to the list of graph aliases to use,
165
+ # unlike #set_graph_aliases, which replaces the list. See
166
+ # #set_graph_aliases.
167
+ def add_graph_aliases(graph_aliases)
168
+ ds = select_more(*graph_alias_columns(graph_aliases))
169
+ ds.opts[:graph_aliases] = (ds.opts[:graph_aliases] || {}).merge(graph_aliases)
170
+ ds
171
+ end
172
+
168
173
  private
169
174
 
175
+ # Transform the hash of graph aliases to an array of columns
176
+ def graph_alias_columns(graph_aliases)
177
+ graph_aliases.collect do |col_alias, tc|
178
+ identifier = tc[2] || tc[1].qualify(tc[0])
179
+ identifier = SQL::AliasedExpression.new(identifier, col_alias) if tc[2] or tc[1] != col_alias
180
+ identifier
181
+ end
182
+ end
183
+
170
184
  # Fetch the rows, split them into component table parts,
171
185
  # tranform and run the row_proc on each part (if applicable),
172
186
  # and yield a hash of the parts.
@@ -2,15 +2,16 @@ module Sequel
2
2
  # The Schema module holds the schema generators and the SQL code relating
3
3
  # to SQL DDL (Data Definition Language).
4
4
  module Schema
5
- # Schema::Generator is used to create tables. It takes a Database
5
+ # Schema::Generator is an internal class that the user is not expected
6
+ # to instantiate directly. Instances are created by Database#create_table.
7
+ # It is used to specify table creation parameters. It takes a Database
6
8
  # object and a block of column/index/constraint specifications, and
7
- # creates a table in the database based on the specifications.
9
+ # gives the Database a table description, which the database uses to
10
+ # create a table.
8
11
  #
9
12
  # Schema::Generator has some methods but also includes method_missing,
10
13
  # allowing users to specify column type as a method instead of using
11
14
  # the column method, which makes for a nicer DSL.
12
- #
13
- # See Database#create_table.
14
15
  class Generator
15
16
  # Set the database in which to create the table, and evaluate the block
16
17
  # in the context of this object.
@@ -118,9 +119,17 @@ module Sequel
118
119
  name ? column(name, type, opts) : super
119
120
  end
120
121
 
121
- # Add a column with the given name and primary key options to the DDL. You
122
- # can optionally provide a type argument and/or an options hash argument
123
- # to change the primary key options. See column for available options.
122
+ # Add primary key information to the DDL. Takes between one and three
123
+ # arguments. The last one is an options hash as for Generator#column.
124
+ # The first one distinguishes two modes: an array of existing column
125
+ # names adds a composite primary key constraint. A single symbol adds a
126
+ # new column of that name and makes it the primary key. In that case the
127
+ # optional middle argument denotes the type.
128
+ #
129
+ # Examples:
130
+ # primary_key(:id) primary_key(:name, :text)
131
+ # primary_key(:zip_code, :null => false)
132
+ # primary_key([:street_number, :house_number])
124
133
  def primary_key(name, *args)
125
134
  return composite_primary_key(name, *args) if name.is_a?(Array)
126
135
  @primary_key = @db.serial_primary_key_options.merge({:name => name})
@@ -165,8 +174,12 @@ module Sequel
165
174
  end
166
175
  end
167
176
 
168
- # The Schema::AlterTableGenerator creates DDL operations on existing tables,
169
- # such as adding/removing/modifying columns/indexes/constraints.
177
+ # Schema::AlterTableGenerator is an internal class that the user is not expected
178
+ # to instantiate directly. Instances are created by Database#alter_table.
179
+ # It is used to specify table alteration parameters. It takes a Database
180
+ # object and a block of operations to perform on the table, and
181
+ # gives the Database a table an array of operations, which the database uses to
182
+ # alter a table's description.
170
183
  class AlterTableGenerator
171
184
  # An array of DDL operations to perform
172
185
  attr_reader :operations
@@ -20,7 +20,6 @@ module Sequel
20
20
  # The SQL to execute to modify the DDL for the given table name. op
21
21
  # should be one of the operations returned by the AlterTableGenerator.
22
22
  def alter_table_sql(table, op)
23
- quoted_table = quote_identifier(table)
24
23
  quoted_name = quote_identifier(op[:name]) if op[:name]
25
24
  alter_table_op = case op[:op]
26
25
  when :add_column
@@ -46,7 +45,7 @@ module Sequel
46
45
  else
47
46
  raise Error, "Unsupported ALTER TABLE operation"
48
47
  end
49
- "ALTER TABLE #{quoted_table} #{alter_table_op}"
48
+ "ALTER TABLE #{quote_schema_table(table)} #{alter_table_op}"
50
49
  end
51
50
 
52
51
  # Array of SQL DDL modification statements for the given table,
@@ -83,7 +82,7 @@ module Sequel
83
82
 
84
83
  # SQL DDL fragment for column foreign key references
85
84
  def column_references_sql(column)
86
- sql = " REFERENCES #{quote_identifier(column[:table])}"
85
+ sql = " REFERENCES #{quote_schema_table(column[:table])}"
87
86
  sql << "(#{Array(column[:key]).map{|x| quote_identifier(x)}.join(COMMA_SEPARATOR)})" if column[:key]
88
87
  sql << " ON DELETE #{on_delete_clause(column[:on_delete])}" if column[:on_delete]
89
88
  sql << " ON UPDATE #{on_delete_clause(column[:on_update])}" if column[:on_update]
@@ -112,7 +111,7 @@ module Sequel
112
111
  # name and column specifications, and the others for specifying indexes on
113
112
  # the table.
114
113
  def create_table_sql_list(name, columns, indexes = nil)
115
- sql = ["CREATE TABLE #{quote_identifier(name)} (#{column_list_sql(columns)})"]
114
+ sql = ["CREATE TABLE #{quote_schema_table(name)} (#{column_list_sql(columns)})"]
116
115
  sql.concat(index_list_sql_list(name, indexes)) if indexes && !indexes.empty?
117
116
  sql
118
117
  end
@@ -120,7 +119,8 @@ module Sequel
120
119
  # Default index name for the table and columns, may be too long
121
120
  # for certain databases.
122
121
  def default_index_name(table_name, columns)
123
- "#{table_name}_#{columns.join(UNDERSCORE)}_index"
122
+ schema, table = schema_and_table(table_name)
123
+ "#{"#{schema}_" if schema and schema != default_schema}#{table}_#{columns.join(UNDERSCORE)}_index"
124
124
  end
125
125
 
126
126
  # The SQL to drop an index for the table.
@@ -130,7 +130,7 @@ module Sequel
130
130
 
131
131
  # SQL DDL statement to drop the table with the given name.
132
132
  def drop_table_sql(name)
133
- "DROP TABLE #{quote_identifier(name)}"
133
+ "DROP TABLE #{quote_schema_table(name)}"
134
134
  end
135
135
 
136
136
  # Proxy the filter_expr call to the dataset, used for creating constraints.
@@ -187,6 +187,11 @@ module Sequel
187
187
  end
188
188
  end
189
189
 
190
+ def quote_schema_table(table)
191
+ schema, table = schema_and_table(table)
192
+ "#{"#{quote_identifier(schema)}." if schema}#{quote_identifier(table)}"
193
+ end
194
+
190
195
  # Proxy the quote_identifier method to the dataset, used for quoting tables and columns.
191
196
  def quote_identifier(v)
192
197
  schema_utility_dataset.quote_identifier(v)
@@ -194,7 +199,7 @@ module Sequel
194
199
 
195
200
  # SQL DDL statement for renaming a table.
196
201
  def rename_table_sql(name, new_name)
197
- "ALTER TABLE #{quote_identifier(name)} RENAME TO #{quote_identifier(new_name)}"
202
+ "ALTER TABLE #{quote_schema_table(name)} RENAME TO #{quote_schema_table(new_name)}"
198
203
  end
199
204
 
200
205
  # Parse the schema from the database using the SQL standard INFORMATION_SCHEMA.
@@ -249,7 +254,6 @@ module Sequel
249
254
  @schema_utility_dataset ||= dataset
250
255
  end
251
256
 
252
-
253
257
  private
254
258
 
255
259
  # Match the database's column type to a ruby type via a
@@ -273,7 +277,7 @@ module Sequel
273
277
  :boolean
274
278
  when /\A(real|float|double( precision)?)\z/
275
279
  :float
276
- when /\A(numeric|decimal|money)\z/
280
+ when /\A(numeric(\(\d+,\d+\))?|decimal|money)\z/
277
281
  :decimal
278
282
  when "bytea"
279
283
  :blob
@@ -98,6 +98,15 @@ module Sequel
98
98
  def to_s(ds)
99
99
  ds.complex_expression_sql(@op, @args)
100
100
  end
101
+
102
+ # Returns true if the receiver is the same expression as the
103
+ # the +other+ expression.
104
+ def eql?( other )
105
+ return other.is_a?( self.class ) &&
106
+ @op.eql?( other.op ) &&
107
+ @args.eql?( other.args )
108
+ end
109
+ alias_method :==, :eql?
101
110
  end
102
111
 
103
112
  # The base class for expressions that can be used in multiple places in
@@ -398,6 +407,22 @@ module Sequel
398
407
  end
399
408
  end
400
409
 
410
+ # Represents an SQL array. Added so it is possible to deal with a
411
+ # ruby array of all two pairs as an SQL array instead of an ordered
412
+ # hash-like conditions specifier.
413
+ class SQLArray < Expression
414
+ # Create an object with the given array.
415
+ def initialize(array)
416
+ @array = array
417
+ end
418
+
419
+ # Delegate the creation of the resulting SQL to the given dataset,
420
+ # since it may be database dependent.
421
+ def to_s(ds)
422
+ ds.array_sql(@array)
423
+ end
424
+ end
425
+
401
426
  # Blob is used to represent binary data in the Ruby environment that is
402
427
  # stored as a blob type in the database. In PostgreSQL, the blob type is
403
428
  # called bytea. Sequel represents binary data as a Blob object because
@@ -438,7 +463,7 @@ module Sequel
438
463
  ce = case r
439
464
  when Range
440
465
  new(:AND, new(:>=, l, r.begin), new(r.exclude_end? ? :< : :<=, l, r.end))
441
- when Array, ::Sequel::Dataset
466
+ when Array, ::Sequel::Dataset, SQLArray
442
467
  new(:IN, l, r)
443
468
  when NilClass
444
469
  new(:IS, l, r)
@@ -732,7 +757,7 @@ module Sequel
732
757
  def self.like(l, *ces)
733
758
  case_insensitive = ces.extract_options![:case_insensitive]
734
759
  ces.collect! do |ce|
735
- op, expr = Regexp === ce ? [ce.casefold? || case_insensitive ? :'~*' : :~, ce.source] : [case_insensitive ? :ILIKE : :LIKE, ce.to_s]
760
+ op, expr = Regexp === ce ? [ce.casefold? || case_insensitive ? :'~*' : :~, ce.source] : [case_insensitive ? :ILIKE : :LIKE, ce]
736
761
  BooleanExpression.new(op, l, expr)
737
762
  end
738
763
  ces.length == 1 ? ces.at(0) : BooleanExpression.new(:OR, *ces)
@@ -1,157 +1,267 @@
1
- module Sequel
2
- class Model
3
- module Associations
4
- # AssociationReflection is a Hash subclass that keeps information on Sequel::Model associations. It
5
- # provides methods to reduce internal code duplication. It should not
6
- # be instantiated by the user.
7
- class AssociationReflection < Hash
8
- ASSOCIATION_TYPES = [:many_to_one, :one_to_many, :many_to_many]
9
- RECIPROCAL_ASSOCIATIONS = {:many_to_one=>:one_to_many, :one_to_many=>:many_to_one, :many_to_many=>:many_to_many}
10
-
11
- # Name symbol for _add_ internal association method
12
- def _add_method
13
- :"_add_#{self[:name].to_s.singularize}"
14
- end
15
-
16
- # Name symbol for _dataset association method
17
- def _dataset_method
18
- :"_#{self[:name]}_dataset"
19
- end
20
-
21
- # Name symbol for _remove_all internal association method
22
- def _remove_all_method
23
- :"_remove_all_#{self[:name]}"
24
- end
25
-
26
- # Name symbol for _remove_ internal association method
27
- def _remove_method
28
- :"_remove_#{self[:name].to_s.singularize}"
29
- end
30
-
31
- # Name symbol for setter association method
32
- def _setter_method
33
- :"_#{self[:name]}="
34
- end
35
-
36
- # Name symbol for add_ association method
37
- def add_method
38
- :"add_#{self[:name].to_s.singularize}"
39
- end
40
-
41
- # Name symbol for association method, the same as the name of the association.
42
- def association_method
43
- self[:name]
44
- end
45
-
46
- # The class associated to the current model class via this association
47
- def associated_class
48
- self[:class] ||= self[:class_name].constantize
49
- end
1
+ module Sequel::Model::Associations
2
+ # Map of association type symbols to association reflection classes.
3
+ ASSOCIATION_TYPES = {}
4
+
5
+ # AssociationReflection is a Hash subclass that keeps information on Sequel::Model associations. It
6
+ # provides methods to reduce internal code duplication. It should not
7
+ # be instantiated by the user.
8
+ class AssociationReflection < Hash
9
+ # Name symbol for _add_ internal association method
10
+ def _add_method
11
+ :"_add_#{self[:name].to_s.singularize}"
12
+ end
50
13
 
51
- # The associated class's primary key (used for caching)
52
- def associated_primary_key
53
- self[:associated_primary_key] ||= associated_class.primary_key
54
- end
14
+ # Name symbol for _dataset association method
15
+ def _dataset_method
16
+ :"_#{self[:name]}_dataset"
17
+ end
55
18
 
56
- # Name symbol for dataset association method
57
- def dataset_method
58
- :"#{self[:name]}_dataset"
59
- end
60
-
61
- # Name symbol for _helper internal association method
62
- def dataset_helper_method
63
- :"_#{self[:name]}_dataset_helper"
64
- end
65
-
66
- # Whether the dataset needs a primary key to function
67
- def dataset_need_primary_key?
68
- self[:type] != :many_to_one
69
- end
19
+ # Name symbol for _remove_all internal association method
20
+ def _remove_all_method
21
+ :"_remove_all_#{self[:name]}"
22
+ end
23
+
24
+ # Name symbol for _remove_ internal association method
25
+ def _remove_method
26
+ :"_remove_#{self[:name].to_s.singularize}"
27
+ end
28
+
29
+ # Name symbol for setter association method
30
+ def _setter_method
31
+ :"_#{self[:name]}="
32
+ end
33
+
34
+ # Name symbol for add_ association method
35
+ def add_method
36
+ :"add_#{self[:name].to_s.singularize}"
37
+ end
38
+
39
+ # Name symbol for association method, the same as the name of the association.
40
+ def association_method
41
+ self[:name]
42
+ end
43
+
44
+ # The class associated to the current model class via this association
45
+ def associated_class
46
+ self[:class] ||= self[:class_name].constantize
47
+ end
48
+
49
+ # Name symbol for dataset association method
50
+ def dataset_method
51
+ :"#{self[:name]}_dataset"
52
+ end
53
+
54
+ # Name symbol for _helper internal association method
55
+ def dataset_helper_method
56
+ :"_#{self[:name]}_dataset_helper"
57
+ end
58
+
59
+ # Whether the dataset needs a primary key to function, true by default.
60
+ def dataset_need_primary_key?
61
+ true
62
+ end
70
63
 
71
- # Name symbol for default join table
72
- def default_join_table
73
- ([self[:class_name].demodulize, self[:model].name.to_s.demodulize]. \
74
- map{|i| i.pluralize.underscore}.sort.join('_')).to_sym
75
- end
76
-
77
- # Default foreign key name symbol for key in associated table that points to
78
- # current table's primary key.
79
- def default_left_key
80
- :"#{self[:model].name.to_s.demodulize.underscore}_id"
81
- end
64
+ # Whether to eagerly graph a lazy dataset, true by default.
65
+ def eager_graph_lazy_dataset?
66
+ true
67
+ end
82
68
 
83
- # Default foreign key name symbol for foreign key in current model's table that points to
84
- # the given association's table's primary key.
85
- def default_right_key
86
- :"#{self[:type] == :many_to_one ? self[:name] : self[:name].to_s.singularize}_id"
87
- end
88
-
89
- # Whether to eagerly graph a lazy dataset
90
- def eager_graph_lazy_dataset?
91
- self[:type] != :many_to_one or self[:key].nil?
92
- end
69
+ # Whether the associated object needs a primary key to be added/removed,
70
+ # false by default.
71
+ def need_associated_primary_key?
72
+ false
73
+ end
93
74
 
94
- # Whether the associated object needs a primary key to be added/removed
95
- def need_associated_primary_key?
96
- self[:type] == :many_to_many
75
+ # Returns/sets the reciprocal association variable, if one exists
76
+ def reciprocal
77
+ return self[:reciprocal] if include?(:reciprocal)
78
+ r_type = reciprocal_type
79
+ key = self[:key]
80
+ associated_class.all_association_reflections.each do |assoc_reflect|
81
+ if assoc_reflect[:type] == r_type && assoc_reflect[:key] == key
82
+ return self[:reciprocal] = assoc_reflect[:name]
97
83
  end
84
+ end
85
+ self[:reciprocal] = nil
86
+ end
98
87
 
99
- # Returns/sets the reciprocal association variable, if one exists
100
- def reciprocal
101
- return self[:reciprocal] if include?(:reciprocal)
102
- reciprocal_type = RECIPROCAL_ASSOCIATIONS[self[:type]]
103
- if reciprocal_type == :many_to_many
104
- left_key = self[:left_key]
105
- right_key = self[:right_key]
106
- join_table = self[:join_table]
107
- associated_class.all_association_reflections.each do |assoc_reflect|
108
- if assoc_reflect[:type] == :many_to_many && assoc_reflect[:left_key] == right_key \
109
- && assoc_reflect[:right_key] == left_key && assoc_reflect[:join_table] == join_table
110
- return self[:reciprocal] = assoc_reflect[:name]
111
- end
112
- end
113
- else
114
- key = self[:key]
115
- associated_class.all_association_reflections.each do |assoc_reflect|
116
- if assoc_reflect[:type] == reciprocal_type && assoc_reflect[:key] == key
117
- return self[:reciprocal] = assoc_reflect[:name]
118
- end
119
- end
120
- end
121
- self[:reciprocal] = nil
122
- end
88
+ # Whether the reciprocal of this association returns an array of objects instead of a single object,
89
+ # true by default.
90
+ def reciprocal_array?
91
+ true
92
+ end
123
93
 
124
- # Name symbol for remove_all_ association method
125
- def remove_all_method
126
- :"remove_all_#{self[:name]}"
127
- end
128
-
129
- # Name symbol for remove_ association method
130
- def remove_method
131
- :"remove_#{self[:name].to_s.singularize}"
132
- end
133
-
134
- # The columns to select when loading the association
135
- def select
136
- return self[:select] if include?(:select)
137
- self[:select] = self[:type] == :many_to_many ? associated_class.table_name.* : nil
138
- end
94
+ # Name symbol for remove_all_ association method
95
+ def remove_all_method
96
+ :"remove_all_#{self[:name]}"
97
+ end
98
+
99
+ # Name symbol for remove_ association method
100
+ def remove_method
101
+ :"remove_#{self[:name].to_s.singularize}"
102
+ end
103
+
104
+ # Whether this association returns an array of objects instead of a single object,
105
+ # true by default.
106
+ def returns_array?
107
+ true
108
+ end
139
109
 
140
- # Whether to set the reciprocal to the current object when loading
141
- def set_reciprocal_to_self?
142
- self[:type] == :one_to_many
143
- end
110
+ # The columns to select when loading the association, nil by default.
111
+ def select
112
+ self[:select]
113
+ end
144
114
 
145
- # Name symbol for setter association method
146
- def setter_method
147
- :"#{self[:name]}="
148
- end
115
+ # By default, associations shouldn't set the reciprocal association to self.
116
+ def set_reciprocal_to_self?
117
+ false
118
+ end
119
+
120
+ # Name symbol for setter association method
121
+ def setter_method
122
+ :"#{self[:name]}="
123
+ end
124
+
125
+ end
126
+
127
+ class ManyToOneAssociationReflection < AssociationReflection
128
+ ASSOCIATION_TYPES[:many_to_one] = self
129
+
130
+ # Whether the dataset needs a primary key to function, false for many_to_one associations.
131
+ def dataset_need_primary_key?
132
+ false
133
+ end
134
+
135
+ # Default foreign key name symbol for foreign key in current model's table that points to
136
+ # the given association's table's primary key.
137
+ def default_key
138
+ :"#{self[:name]}_id"
139
+ end
140
+
141
+ # Whether to eagerly graph a lazy dataset, true for many_to_one associations
142
+ # only if the key is nil.
143
+ def eager_graph_lazy_dataset?
144
+ self[:key].nil?
145
+ end
146
+
147
+ # The key to use for the key hash when eager loading
148
+ def eager_loader_key
149
+ self[:key]
150
+ end
151
+
152
+ # The column in the associated table that the key in the current table references.
153
+ def primary_key
154
+ self[:primary_key] ||= associated_class.primary_key
155
+ end
156
+
157
+ # Whether this association returns an array of objects instead of a single object,
158
+ # false for a many_to_one association.
159
+ def returns_array?
160
+ false
161
+ end
162
+
163
+ private
164
+
165
+ # The reciprocal type of a many_to_one association is a one_to_many association.
166
+ def reciprocal_type
167
+ :one_to_many
168
+ end
169
+ end
170
+
171
+ class OneToManyAssociationReflection < AssociationReflection
172
+ ASSOCIATION_TYPES[:one_to_many] = self
173
+
174
+ # Default foreign key name symbol for key in associated table that points to
175
+ # current table's primary key.
176
+ def default_key
177
+ :"#{self[:model].name.to_s.demodulize.underscore}_id"
178
+ end
179
+
180
+ # The key to use for the key hash when eager loading
181
+ def eager_loader_key
182
+ primary_key
183
+ end
184
+
185
+ # The column in the current table that the key in the associated table references.
186
+ def primary_key
187
+ self[:primary_key] ||= self[:model].primary_key
188
+ end
189
+
190
+ # One to many associations set the reciprocal to self.
191
+ def set_reciprocal_to_self?
192
+ true
193
+ end
194
+
195
+ # Whether the reciprocal of this association returns an array of objects instead of a single object,
196
+ # false for a one_to_many association.
197
+ def reciprocal_array?
198
+ false
199
+ end
200
+
201
+ private
202
+
203
+ # The reciprocal type of a one_to_many association is a many_to_one association.
204
+ def reciprocal_type
205
+ :many_to_one
206
+ end
207
+ end
208
+
209
+ class ManyToManyAssociationReflection < AssociationReflection
210
+ ASSOCIATION_TYPES[:many_to_many] = self
211
+
212
+ # Default name symbol for the join table.
213
+ def default_join_table
214
+ ([self[:class_name].demodulize, self[:model].name.to_s.demodulize]. \
215
+ map{|i| i.pluralize.underscore}.sort.join('_')).to_sym
216
+ end
217
+
218
+ # Default foreign key name symbol for key in join table that points to
219
+ # current table's primary key (or :left_primary_key column).
220
+ def default_left_key
221
+ :"#{self[:model].name.to_s.demodulize.underscore}_id"
222
+ end
223
+
224
+ # Default foreign key name symbol for foreign key in join table that points to
225
+ # the association's table's primary key (or :right_primary_key column).
226
+ def default_right_key
227
+ :"#{self[:name].to_s.singularize}_id"
228
+ end
229
+
230
+ # The key to use for the key hash when eager loading
231
+ def eager_loader_key
232
+ self[:left_primary_key]
233
+ end
149
234
 
150
- # Whether the association should return a single object or multiple objects.
151
- def single_associated_object?
152
- self[:type] == :many_to_one
235
+ # Whether the associated object needs a primary key to be added/removed,
236
+ # true for many_to_many associations.
237
+ def need_associated_primary_key?
238
+ true
239
+ end
240
+
241
+ # Returns/sets the reciprocal association variable, if one exists
242
+ def reciprocal
243
+ return self[:reciprocal] if include?(:reciprocal)
244
+ left_key = self[:left_key]
245
+ right_key = self[:right_key]
246
+ join_table = self[:join_table]
247
+ associated_class.all_association_reflections.each do |assoc_reflect|
248
+ if assoc_reflect[:type] == :many_to_many && assoc_reflect[:left_key] == right_key \
249
+ && assoc_reflect[:right_key] == left_key && assoc_reflect[:join_table] == join_table
250
+ return self[:reciprocal] = assoc_reflect[:name]
153
251
  end
154
252
  end
253
+ self[:reciprocal] = nil
254
+ end
255
+
256
+ # The primary key column to use in the associated table.
257
+ def right_primary_key
258
+ self[:right_primary_key] ||= associated_class.primary_key
259
+ end
260
+
261
+ # The columns to select when loading the association, associated_class.table_name.* by default.
262
+ def select
263
+ return self[:select] if include?(:select)
264
+ self[:select] ||= associated_class.table_name.*
155
265
  end
156
266
  end
157
267
  end