sequel 2.6.0 → 2.7.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 +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