activerecord 3.2.22.5 → 4.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (162) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1024 -543
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +20 -29
  5. data/examples/performance.rb +1 -1
  6. data/lib/active_record.rb +55 -44
  7. data/lib/active_record/aggregations.rb +40 -34
  8. data/lib/active_record/associations.rb +204 -276
  9. data/lib/active_record/associations/alias_tracker.rb +1 -1
  10. data/lib/active_record/associations/association.rb +30 -35
  11. data/lib/active_record/associations/association_scope.rb +40 -40
  12. data/lib/active_record/associations/belongs_to_association.rb +15 -2
  13. data/lib/active_record/associations/builder/association.rb +81 -28
  14. data/lib/active_record/associations/builder/belongs_to.rb +35 -57
  15. data/lib/active_record/associations/builder/collection_association.rb +54 -40
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +23 -41
  17. data/lib/active_record/associations/builder/has_many.rb +8 -64
  18. data/lib/active_record/associations/builder/has_one.rb +13 -50
  19. data/lib/active_record/associations/builder/singular_association.rb +13 -13
  20. data/lib/active_record/associations/collection_association.rb +92 -88
  21. data/lib/active_record/associations/collection_proxy.rb +913 -63
  22. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +12 -10
  23. data/lib/active_record/associations/has_many_association.rb +35 -9
  24. data/lib/active_record/associations/has_many_through_association.rb +24 -14
  25. data/lib/active_record/associations/has_one_association.rb +33 -13
  26. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  27. data/lib/active_record/associations/join_dependency.rb +2 -2
  28. data/lib/active_record/associations/join_dependency/join_association.rb +17 -22
  29. data/lib/active_record/associations/join_dependency/join_part.rb +1 -1
  30. data/lib/active_record/associations/join_helper.rb +1 -11
  31. data/lib/active_record/associations/preloader.rb +14 -17
  32. data/lib/active_record/associations/preloader/association.rb +29 -33
  33. data/lib/active_record/associations/preloader/collection_association.rb +1 -1
  34. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +1 -1
  35. data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
  36. data/lib/active_record/associations/preloader/has_one.rb +1 -1
  37. data/lib/active_record/associations/preloader/through_association.rb +13 -17
  38. data/lib/active_record/associations/singular_association.rb +11 -11
  39. data/lib/active_record/associations/through_association.rb +2 -2
  40. data/lib/active_record/attribute_assignment.rb +133 -153
  41. data/lib/active_record/attribute_methods.rb +196 -93
  42. data/lib/active_record/attribute_methods/before_type_cast.rb +44 -5
  43. data/lib/active_record/attribute_methods/dirty.rb +31 -28
  44. data/lib/active_record/attribute_methods/primary_key.rb +38 -30
  45. data/lib/active_record/attribute_methods/query.rb +5 -4
  46. data/lib/active_record/attribute_methods/read.rb +62 -91
  47. data/lib/active_record/attribute_methods/serialization.rb +97 -66
  48. data/lib/active_record/attribute_methods/time_zone_conversion.rb +39 -45
  49. data/lib/active_record/attribute_methods/write.rb +32 -39
  50. data/lib/active_record/autosave_association.rb +56 -70
  51. data/lib/active_record/base.rb +53 -450
  52. data/lib/active_record/callbacks.rb +53 -18
  53. data/lib/active_record/coders/yaml_column.rb +11 -9
  54. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +353 -197
  55. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  56. data/lib/active_record/connection_adapters/abstract/database_statements.rb +130 -131
  57. data/lib/active_record/connection_adapters/abstract/query_cache.rb +24 -19
  58. data/lib/active_record/connection_adapters/abstract/quoting.rb +23 -3
  59. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +101 -91
  60. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +59 -0
  61. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +225 -96
  62. data/lib/active_record/connection_adapters/abstract/transaction.rb +203 -0
  63. data/lib/active_record/connection_adapters/abstract_adapter.rb +99 -46
  64. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +114 -36
  65. data/lib/active_record/connection_adapters/column.rb +46 -24
  66. data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
  67. data/lib/active_record/connection_adapters/mysql2_adapter.rb +16 -32
  68. data/lib/active_record/connection_adapters/mysql_adapter.rb +181 -64
  69. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +97 -0
  70. data/lib/active_record/connection_adapters/postgresql/cast.rb +132 -0
  71. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +242 -0
  72. data/lib/active_record/connection_adapters/postgresql/oid.rb +347 -0
  73. data/lib/active_record/connection_adapters/postgresql/quoting.rb +158 -0
  74. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  75. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +448 -0
  76. data/lib/active_record/connection_adapters/postgresql_adapter.rb +454 -885
  77. data/lib/active_record/connection_adapters/schema_cache.rb +48 -16
  78. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +574 -13
  79. data/lib/active_record/connection_handling.rb +98 -0
  80. data/lib/active_record/core.rb +428 -0
  81. data/lib/active_record/counter_cache.rb +106 -108
  82. data/lib/active_record/dynamic_matchers.rb +110 -63
  83. data/lib/active_record/errors.rb +25 -8
  84. data/lib/active_record/explain.rb +8 -58
  85. data/lib/active_record/explain_subscriber.rb +6 -3
  86. data/lib/active_record/fixture_set/file.rb +56 -0
  87. data/lib/active_record/fixtures.rb +146 -148
  88. data/lib/active_record/inheritance.rb +77 -59
  89. data/lib/active_record/integration.rb +5 -5
  90. data/lib/active_record/locale/en.yml +8 -1
  91. data/lib/active_record/locking/optimistic.rb +38 -42
  92. data/lib/active_record/locking/pessimistic.rb +4 -4
  93. data/lib/active_record/log_subscriber.rb +19 -9
  94. data/lib/active_record/migration.rb +318 -153
  95. data/lib/active_record/migration/command_recorder.rb +90 -31
  96. data/lib/active_record/migration/join_table.rb +15 -0
  97. data/lib/active_record/model_schema.rb +69 -92
  98. data/lib/active_record/nested_attributes.rb +113 -148
  99. data/lib/active_record/null_relation.rb +65 -0
  100. data/lib/active_record/persistence.rb +188 -97
  101. data/lib/active_record/query_cache.rb +18 -36
  102. data/lib/active_record/querying.rb +19 -15
  103. data/lib/active_record/railtie.rb +91 -36
  104. data/lib/active_record/railties/console_sandbox.rb +0 -2
  105. data/lib/active_record/railties/controller_runtime.rb +2 -2
  106. data/lib/active_record/railties/databases.rake +90 -309
  107. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  108. data/lib/active_record/readonly_attributes.rb +7 -3
  109. data/lib/active_record/reflection.rb +72 -56
  110. data/lib/active_record/relation.rb +241 -157
  111. data/lib/active_record/relation/batches.rb +25 -22
  112. data/lib/active_record/relation/calculations.rb +143 -121
  113. data/lib/active_record/relation/delegation.rb +96 -18
  114. data/lib/active_record/relation/finder_methods.rb +117 -183
  115. data/lib/active_record/relation/merger.rb +133 -0
  116. data/lib/active_record/relation/predicate_builder.rb +90 -42
  117. data/lib/active_record/relation/query_methods.rb +666 -136
  118. data/lib/active_record/relation/spawn_methods.rb +43 -150
  119. data/lib/active_record/result.rb +33 -6
  120. data/lib/active_record/sanitization.rb +24 -50
  121. data/lib/active_record/schema.rb +19 -12
  122. data/lib/active_record/schema_dumper.rb +31 -39
  123. data/lib/active_record/schema_migration.rb +36 -0
  124. data/lib/active_record/scoping.rb +0 -124
  125. data/lib/active_record/scoping/default.rb +48 -45
  126. data/lib/active_record/scoping/named.rb +74 -103
  127. data/lib/active_record/serialization.rb +6 -2
  128. data/lib/active_record/serializers/xml_serializer.rb +9 -15
  129. data/lib/active_record/store.rb +119 -15
  130. data/lib/active_record/tasks/database_tasks.rb +158 -0
  131. data/lib/active_record/tasks/mysql_database_tasks.rb +138 -0
  132. data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
  133. data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
  134. data/lib/active_record/test_case.rb +61 -38
  135. data/lib/active_record/timestamp.rb +8 -9
  136. data/lib/active_record/transactions.rb +65 -51
  137. data/lib/active_record/validations.rb +17 -15
  138. data/lib/active_record/validations/associated.rb +20 -14
  139. data/lib/active_record/validations/presence.rb +65 -0
  140. data/lib/active_record/validations/uniqueness.rb +93 -52
  141. data/lib/active_record/version.rb +4 -4
  142. data/lib/rails/generators/active_record.rb +3 -5
  143. data/lib/rails/generators/active_record/migration/migration_generator.rb +37 -7
  144. data/lib/rails/generators/active_record/migration/templates/migration.rb +20 -15
  145. data/lib/rails/generators/active_record/model/model_generator.rb +4 -3
  146. data/lib/rails/generators/active_record/model/templates/model.rb +1 -6
  147. data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
  148. metadata +53 -46
  149. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
  150. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
  151. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
  152. data/lib/active_record/dynamic_finder_match.rb +0 -68
  153. data/lib/active_record/dynamic_scope_match.rb +0 -23
  154. data/lib/active_record/fixtures/file.rb +0 -65
  155. data/lib/active_record/identity_map.rb +0 -162
  156. data/lib/active_record/observer.rb +0 -121
  157. data/lib/active_record/session_store.rb +0 -360
  158. data/lib/rails/generators/active_record/migration.rb +0 -15
  159. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  160. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  161. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  162. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -8,7 +8,7 @@ module ActiveRecord
8
8
  attr_reader :aliases, :table_joins, :connection
9
9
 
10
10
  # table_joins is an array of arel joins which might conflict with the aliases we assign here
11
- def initialize(connection = ActiveRecord::Model.connection, table_joins = [])
11
+ def initialize(connection = Base.connection, table_joins = [])
12
12
  @aliases = Hash.new { |h,k| h[k] = initial_count_for(k) }
13
13
  @table_joins = table_joins
14
14
  @connection = connection
@@ -1,5 +1,4 @@
1
1
  require 'active_support/core_ext/array/wrap'
2
- require 'active_support/core_ext/object/inclusion'
3
2
 
4
3
  module ActiveRecord
5
4
  module Associations
@@ -25,9 +24,7 @@ module ActiveRecord
25
24
  def initialize(owner, reflection)
26
25
  reflection.check_validity!
27
26
 
28
- @target = nil
29
27
  @owner, @reflection = owner, reflection
30
- @updated = false
31
28
 
32
29
  reset
33
30
  reset_scope
@@ -38,13 +35,12 @@ module ActiveRecord
38
35
  # post.comments.aliased_table_name # => "comments"
39
36
  #
40
37
  def aliased_table_name
41
- reflection.klass.table_name
38
+ klass.table_name
42
39
  end
43
40
 
44
41
  # Resets the \loaded flag to +false+ and sets the \target to +nil+.
45
42
  def reset
46
43
  @loaded = false
47
- IdentityMap.remove(target) if IdentityMap.enabled? && target
48
44
  @target = nil
49
45
  @stale_state = nil
50
46
  end
@@ -84,10 +80,15 @@ module ActiveRecord
84
80
  loaded!
85
81
  end
86
82
 
87
- def scoped
83
+ def scope
88
84
  target_scope.merge(association_scope)
89
85
  end
90
86
 
87
+ def scoped
88
+ ActiveSupport::Deprecation.warn "#scoped is deprecated. use #scope instead."
89
+ scope
90
+ end
91
+
91
92
  # The scope for this association.
92
93
  #
93
94
  # Note that the association_scope is merged into the target_scope only when the
@@ -121,7 +122,7 @@ module ActiveRecord
121
122
  # Can be overridden (i.e. in ThroughAssociation) to merge in other scopes (i.e. the
122
123
  # through association's scope)
123
124
  def target_scope
124
- klass.scoped
125
+ klass.all
125
126
  end
126
127
 
127
128
  # Loads the \target if needed and returns it.
@@ -129,28 +130,14 @@ module ActiveRecord
129
130
  # This method is abstract in the sense that it relies on +find_target+,
130
131
  # which is expected to be provided by descendants.
131
132
  #
132
- # If the \target is stale(the target no longer points to the record(s) that the
133
- # relevant foreign_key(s) refers to.), force reload the \target.
134
- #
135
- # Otherwise if the \target is already \loaded it is just returned. Thus, you can
136
- # call +load_target+ unconditionally to get the \target.
133
+ # If the \target is already \loaded it is just returned. Thus, you can call
134
+ # +load_target+ unconditionally to get the \target.
137
135
  #
138
136
  # ActiveRecord::RecordNotFound is rescued within the method, and it is
139
137
  # not reraised. The proxy is \reset and +nil+ is the return value.
140
138
  def load_target
141
- if (@stale_state && stale_target?) || find_target?
142
- begin
143
- if IdentityMap.enabled? && association_class && association_class.respond_to?(:base_class)
144
- @target = IdentityMap.get(association_class, owner[reflection.foreign_key])
145
- elsif @stale_state && stale_target?
146
- @target = find_target
147
- end
148
- rescue NameError
149
- nil
150
- ensure
151
- @target ||= find_target
152
- end
153
- end
139
+ @target = find_target if (@stale_state && stale_target?) || find_target?
140
+
154
141
  loaded! unless loaded?
155
142
  target
156
143
  rescue ActiveRecord::RecordNotFound
@@ -158,13 +145,25 @@ module ActiveRecord
158
145
  end
159
146
 
160
147
  def interpolate(sql, record = nil)
161
- if sql.respond_to?(:to_proc) && !sql.is_a?(Hash)
148
+ if sql.respond_to?(:to_proc)
162
149
  owner.send(:instance_exec, record, &sql)
163
150
  else
164
151
  sql
165
152
  end
166
153
  end
167
154
 
155
+ # We can't dump @reflection since it contains the scope proc
156
+ def marshal_dump
157
+ ivars = (instance_variables - [:@reflection]).map { |name| [name, instance_variable_get(name)] }
158
+ [@reflection.name, ivars]
159
+ end
160
+
161
+ def marshal_load(data)
162
+ reflection_name, ivars = data
163
+ ivars.each { |name, val| instance_variable_set(name, val) }
164
+ @reflection = @owner.class.reflect_on_association(reflection_name)
165
+ end
166
+
168
167
  private
169
168
 
170
169
  def find_target?
@@ -174,7 +173,7 @@ module ActiveRecord
174
173
  def creation_attributes
175
174
  attributes = {}
176
175
 
177
- if reflection.macro.in?([:has_one, :has_many]) && !options[:through]
176
+ if (reflection.macro == :has_one || reflection.macro == :has_many) && !options[:through]
178
177
  attributes[reflection.foreign_key] = owner[reflection.active_record_primary_key]
179
178
 
180
179
  if reflection.options[:as]
@@ -224,22 +223,18 @@ module ActiveRecord
224
223
  end
225
224
 
226
225
  # This should be implemented to return the values of the relevant key(s) on the owner,
227
- # so that when state_state is different from the value stored on the last find_target,
226
+ # so that when stale_state is different from the value stored on the last find_target,
228
227
  # the target is stale.
229
228
  #
230
229
  # This is only relevant to certain associations, which is why it returns nil by default.
231
230
  def stale_state
232
231
  end
233
232
 
234
- def association_class
235
- @reflection.klass
236
- end
237
-
238
- def build_record(attributes, options)
239
- reflection.build_association(attributes, options) do |record|
233
+ def build_record(attributes)
234
+ reflection.build_association(attributes) do |record|
240
235
  skip_assign = [reflection.foreign_key, reflection.type].compact
241
236
  attributes = create_scope.except(*(record.changed - skip_assign))
242
- record.assign_attributes(attributes, :without_protection => true)
237
+ record.assign_attributes(attributes)
243
238
  end
244
239
  end
245
240
  end
@@ -6,7 +6,7 @@ module ActiveRecord
6
6
  attr_reader :association, :alias_tracker
7
7
 
8
8
  delegate :klass, :owner, :reflection, :interpolate, :to => :association
9
- delegate :chain, :conditions, :options, :source_options, :active_record, :to => :reflection
9
+ delegate :chain, :scope_chain, :options, :source_options, :active_record, :to => :reflection
10
10
 
11
11
  def initialize(association)
12
12
  @association = association
@@ -15,23 +15,28 @@ module ActiveRecord
15
15
 
16
16
  def scope
17
17
  scope = klass.unscoped
18
- scope = scope.extending(*Array.wrap(options[:extend]))
19
-
20
- # It's okay to just apply all these like this. The options will only be present if the
21
- # association supports that option; this is enforced by the association builder.
22
- scope = scope.apply_finder_options(options.slice(
23
- :readonly, :include, :order, :limit, :joins, :group, :having, :offset, :select))
18
+ scope.extending! Array(options[:extend])
19
+ add_constraints(scope)
20
+ end
24
21
 
25
- if options[:through] && !options[:include]
26
- scope = scope.includes(source_options[:include])
27
- end
22
+ private
28
23
 
29
- scope = scope.uniq if options[:uniq]
24
+ def column_for(table_name, column_name)
25
+ columns = alias_tracker.connection.schema_cache.columns_hash[table_name]
26
+ columns[column_name]
27
+ end
30
28
 
31
- add_constraints(scope)
29
+ def bind_value(scope, column, value)
30
+ substitute = alias_tracker.connection.substitute_at(
31
+ column, scope.bind_values.length)
32
+ scope.bind_values += [[column, value]]
33
+ substitute
32
34
  end
33
35
 
34
- private
36
+ def bind(scope, table_name, column_name, value)
37
+ column = column_for table_name, column_name
38
+ bind_value scope, column, value
39
+ end
35
40
 
36
41
  def add_constraints(scope)
37
42
  tables = construct_tables
@@ -53,7 +58,7 @@ module ActiveRecord
53
58
 
54
59
  if reflection.source_macro == :belongs_to
55
60
  if reflection.options[:polymorphic]
56
- key = reflection.association_primary_key(klass)
61
+ key = reflection.association_primary_key(self.klass)
57
62
  else
58
63
  key = reflection.association_primary_key
59
64
  end
@@ -64,21 +69,14 @@ module ActiveRecord
64
69
  foreign_key = reflection.active_record_primary_key
65
70
  end
66
71
 
67
- conditions = self.conditions[i]
68
-
69
72
  if reflection == chain.last
70
- scope = scope.where(table[key].eq(owner[foreign_key]))
73
+ bind_val = bind scope, table.table_name, key.to_s, owner[foreign_key]
74
+ scope = scope.where(table[key].eq(bind_val))
71
75
 
72
76
  if reflection.type
73
- scope = scope.where(table[reflection.type].eq(owner.class.base_class.name))
74
- end
75
-
76
- conditions.each do |condition|
77
- if options[:through] && condition.is_a?(Hash)
78
- condition = disambiguate_condition(table, condition)
79
- end
80
-
81
- scope = scope.where(interpolate(condition))
77
+ value = owner.class.base_class.name
78
+ bind_val = bind scope, table.table_name, reflection.type.to_s, value
79
+ scope = scope.where(table[reflection.type].eq(bind_val))
82
80
  end
83
81
  else
84
82
  constraint = table[key].eq(foreign_table[foreign_key])
@@ -89,10 +87,20 @@ module ActiveRecord
89
87
  end
90
88
 
91
89
  scope = scope.joins(join(foreign_table, constraint))
90
+ end
91
+
92
+ # Exclude the scope of the association itself, because that
93
+ # was already merged in the #scope method.
94
+ scope_chain[i].each do |scope_chain_item|
95
+ klass = i == 0 ? self.klass : reflection.klass
96
+ item = eval_scope(klass, scope_chain_item)
92
97
 
93
- unless conditions.empty?
94
- scope = scope.where(sanitize(conditions, table))
98
+ if scope_chain_item == self.reflection.scope
99
+ scope.merge! item.except(:where, :includes)
95
100
  end
101
+
102
+ scope.includes! item.includes_values
103
+ scope.where_values += item.where_values
96
104
  end
97
105
  end
98
106
 
@@ -114,19 +122,11 @@ module ActiveRecord
114
122
  end
115
123
  end
116
124
 
117
- def disambiguate_condition(table, condition)
118
- if condition.is_a?(Hash)
119
- Hash[
120
- condition.map do |k, v|
121
- if v.is_a?(Hash)
122
- [k, v]
123
- else
124
- [table.table_alias || table.name, { k => v }]
125
- end
126
- end
127
- ]
125
+ def eval_scope(klass, scope)
126
+ if scope.is_a?(Relation)
127
+ scope
128
128
  else
129
- condition
129
+ klass.unscoped.instance_exec(owner, &scope)
130
130
  end
131
131
  end
132
132
  end
@@ -2,6 +2,11 @@ module ActiveRecord
2
2
  # = Active Record Belongs To Associations
3
3
  module Associations
4
4
  class BelongsToAssociation < SingularAssociation #:nodoc:
5
+
6
+ def handle_dependency
7
+ target.send(options[:dependent]) if load_target
8
+ end
9
+
5
10
  def replace(record)
6
11
  raise_on_type_mismatch(record) if record
7
12
 
@@ -14,6 +19,11 @@ module ActiveRecord
14
19
  self.target = record
15
20
  end
16
21
 
22
+ def reset
23
+ super
24
+ @updated = false
25
+ end
26
+
17
27
  def updated?
18
28
  @updated
19
29
  end
@@ -40,8 +50,11 @@ module ActiveRecord
40
50
 
41
51
  # Checks whether record is different to the current target, without loading it
42
52
  def different_target?(record)
43
- record.nil? && owner[reflection.foreign_key] ||
44
- record && record.id != owner[reflection.foreign_key]
53
+ if record.nil?
54
+ owner[reflection.foreign_key]
55
+ else
56
+ record.id != owner[reflection.foreign_key]
57
+ end
45
58
  end
46
59
 
47
60
  def replace_keys(record)
@@ -1,55 +1,108 @@
1
1
  module ActiveRecord::Associations::Builder
2
2
  class Association #:nodoc:
3
- class_attribute :valid_options
4
- self.valid_options = [:class_name, :foreign_key, :select, :conditions, :include, :extend, :readonly, :validate]
3
+ class << self
4
+ attr_accessor :valid_options
5
+ end
5
6
 
6
- # Set by subclasses
7
- class_attribute :macro
7
+ self.valid_options = [:class_name, :foreign_key, :validate]
8
8
 
9
- attr_reader :model, :name, :options, :reflection
9
+ attr_reader :model, :name, :scope, :options, :reflection
10
10
 
11
- def self.build(model, name, options)
12
- new(model, name, options).build
11
+ def self.build(*args, &block)
12
+ new(*args, &block).build
13
13
  end
14
14
 
15
- def initialize(model, name, options)
16
- @model, @name, @options = model, name, options
15
+ def initialize(model, name, scope, options)
16
+ raise ArgumentError, "association names must be a Symbol" unless name.kind_of?(Symbol)
17
+
18
+ @model = model
19
+ @name = name
20
+
21
+ if scope.is_a?(Hash)
22
+ @scope = nil
23
+ @options = scope
24
+ else
25
+ @scope = scope
26
+ @options = options
27
+ end
28
+
29
+ if @scope && @scope.arity == 0
30
+ prev_scope = @scope
31
+ @scope = proc { instance_exec(&prev_scope) }
32
+ end
17
33
  end
18
34
 
19
35
  def mixin
20
36
  @model.generated_feature_methods
21
37
  end
22
38
 
39
+ include Module.new { def build; end }
40
+
23
41
  def build
24
42
  validate_options
25
- reflection = model.create_reflection(self.class.macro, name, options, model)
26
43
  define_accessors
27
- reflection
44
+ configure_dependency if options[:dependent]
45
+ @reflection = model.create_reflection(macro, name, scope, options, model)
46
+ super # provides an extension point
47
+ @reflection
28
48
  end
29
49
 
30
- private
50
+ def macro
51
+ raise NotImplementedError
52
+ end
31
53
 
32
- def validate_options
33
- options.assert_valid_keys(self.class.valid_options)
34
- end
54
+ def valid_options
55
+ Association.valid_options
56
+ end
35
57
 
36
- def define_accessors
37
- define_readers
38
- define_writers
39
- end
58
+ def validate_options
59
+ options.assert_valid_keys(valid_options)
60
+ end
61
+
62
+ def define_accessors
63
+ define_readers
64
+ define_writers
65
+ end
40
66
 
41
- def define_readers
42
- name = self.name
43
- mixin.redefine_method(name) do |*params|
44
- association(name).reader(*params)
67
+ def define_readers
68
+ mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
69
+ def #{name}(*args)
70
+ association(:#{name}).reader(*args)
45
71
  end
46
- end
72
+ CODE
73
+ end
47
74
 
48
- def define_writers
49
- name = self.name
50
- mixin.redefine_method("#{name}=") do |value|
51
- association(name).writer(value)
75
+ def define_writers
76
+ mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
77
+ def #{name}=(value)
78
+ association(:#{name}).writer(value)
52
79
  end
80
+ CODE
81
+ end
82
+
83
+ def configure_dependency
84
+ unless valid_dependent_options.include? options[:dependent]
85
+ raise ArgumentError, "The :dependent option must be one of #{valid_dependent_options}, but is :#{options[:dependent]}"
86
+ end
87
+
88
+ if options[:dependent] == :restrict
89
+ ActiveSupport::Deprecation.warn(
90
+ "The :restrict option is deprecated. Please use :restrict_with_exception instead, which " \
91
+ "provides the same functionality."
92
+ )
53
93
  end
94
+
95
+ mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
96
+ def #{macro}_dependent_for_#{name}
97
+ association(:#{name}).handle_dependency
98
+ end
99
+ CODE
100
+
101
+ model.before_destroy "#{macro}_dependent_for_#{name}"
102
+ end
103
+
104
+ def valid_dependent_options
105
+ raise NotImplementedError
106
+ end
54
107
  end
55
108
  end