activerecord 3.0.0 → 4.0.0

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 (181) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +2102 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +35 -44
  5. data/examples/performance.rb +110 -100
  6. data/lib/active_record/aggregations.rb +59 -75
  7. data/lib/active_record/associations/alias_tracker.rb +76 -0
  8. data/lib/active_record/associations/association.rb +248 -0
  9. data/lib/active_record/associations/association_scope.rb +135 -0
  10. data/lib/active_record/associations/belongs_to_association.rb +60 -59
  11. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +16 -59
  12. data/lib/active_record/associations/builder/association.rb +108 -0
  13. data/lib/active_record/associations/builder/belongs_to.rb +98 -0
  14. data/lib/active_record/associations/builder/collection_association.rb +89 -0
  15. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +39 -0
  16. data/lib/active_record/associations/builder/has_many.rb +15 -0
  17. data/lib/active_record/associations/builder/has_one.rb +25 -0
  18. data/lib/active_record/associations/builder/singular_association.rb +32 -0
  19. data/lib/active_record/associations/collection_association.rb +608 -0
  20. data/lib/active_record/associations/collection_proxy.rb +986 -0
  21. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +40 -112
  22. data/lib/active_record/associations/has_many_association.rb +83 -76
  23. data/lib/active_record/associations/has_many_through_association.rb +147 -66
  24. data/lib/active_record/associations/has_one_association.rb +67 -108
  25. data/lib/active_record/associations/has_one_through_association.rb +21 -25
  26. data/lib/active_record/associations/join_dependency/join_association.rb +174 -0
  27. data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
  28. data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
  29. data/lib/active_record/associations/join_dependency.rb +235 -0
  30. data/lib/active_record/associations/join_helper.rb +45 -0
  31. data/lib/active_record/associations/preloader/association.rb +121 -0
  32. data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
  33. data/lib/active_record/associations/preloader/collection_association.rb +24 -0
  34. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
  35. data/lib/active_record/associations/preloader/has_many.rb +17 -0
  36. data/lib/active_record/associations/preloader/has_many_through.rb +19 -0
  37. data/lib/active_record/associations/preloader/has_one.rb +23 -0
  38. data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
  39. data/lib/active_record/associations/preloader/singular_association.rb +21 -0
  40. data/lib/active_record/associations/preloader/through_association.rb +63 -0
  41. data/lib/active_record/associations/preloader.rb +178 -0
  42. data/lib/active_record/associations/singular_association.rb +64 -0
  43. data/lib/active_record/associations/through_association.rb +87 -0
  44. data/lib/active_record/associations.rb +512 -1224
  45. data/lib/active_record/attribute_assignment.rb +201 -0
  46. data/lib/active_record/attribute_methods/before_type_cast.rb +49 -12
  47. data/lib/active_record/attribute_methods/dirty.rb +51 -28
  48. data/lib/active_record/attribute_methods/primary_key.rb +94 -22
  49. data/lib/active_record/attribute_methods/query.rb +5 -4
  50. data/lib/active_record/attribute_methods/read.rb +63 -72
  51. data/lib/active_record/attribute_methods/serialization.rb +162 -0
  52. data/lib/active_record/attribute_methods/time_zone_conversion.rb +39 -41
  53. data/lib/active_record/attribute_methods/write.rb +39 -13
  54. data/lib/active_record/attribute_methods.rb +362 -29
  55. data/lib/active_record/autosave_association.rb +132 -75
  56. data/lib/active_record/base.rb +83 -1627
  57. data/lib/active_record/callbacks.rb +69 -47
  58. data/lib/active_record/coders/yaml_column.rb +38 -0
  59. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +411 -138
  60. data/lib/active_record/connection_adapters/abstract/database_limits.rb +21 -11
  61. data/lib/active_record/connection_adapters/abstract/database_statements.rb +234 -173
  62. data/lib/active_record/connection_adapters/abstract/query_cache.rb +36 -22
  63. data/lib/active_record/connection_adapters/abstract/quoting.rb +82 -25
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +176 -414
  65. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +70 -0
  66. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +562 -232
  67. data/lib/active_record/connection_adapters/abstract/transaction.rb +203 -0
  68. data/lib/active_record/connection_adapters/abstract_adapter.rb +281 -53
  69. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +782 -0
  70. data/lib/active_record/connection_adapters/column.rb +318 -0
  71. data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
  72. data/lib/active_record/connection_adapters/mysql2_adapter.rb +273 -0
  73. data/lib/active_record/connection_adapters/mysql_adapter.rb +365 -450
  74. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +97 -0
  75. data/lib/active_record/connection_adapters/postgresql/cast.rb +152 -0
  76. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +242 -0
  77. data/lib/active_record/connection_adapters/postgresql/oid.rb +366 -0
  78. data/lib/active_record/connection_adapters/postgresql/quoting.rb +171 -0
  79. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  80. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +489 -0
  81. data/lib/active_record/connection_adapters/postgresql_adapter.rb +672 -752
  82. data/lib/active_record/connection_adapters/schema_cache.rb +129 -0
  83. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +588 -17
  84. data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
  85. data/lib/active_record/connection_handling.rb +98 -0
  86. data/lib/active_record/core.rb +463 -0
  87. data/lib/active_record/counter_cache.rb +108 -101
  88. data/lib/active_record/dynamic_matchers.rb +131 -0
  89. data/lib/active_record/errors.rb +54 -13
  90. data/lib/active_record/explain.rb +38 -0
  91. data/lib/active_record/explain_registry.rb +30 -0
  92. data/lib/active_record/explain_subscriber.rb +29 -0
  93. data/lib/active_record/fixture_set/file.rb +55 -0
  94. data/lib/active_record/fixtures.rb +703 -785
  95. data/lib/active_record/inheritance.rb +200 -0
  96. data/lib/active_record/integration.rb +60 -0
  97. data/lib/active_record/locale/en.yml +8 -1
  98. data/lib/active_record/locking/optimistic.rb +69 -60
  99. data/lib/active_record/locking/pessimistic.rb +34 -12
  100. data/lib/active_record/log_subscriber.rb +40 -6
  101. data/lib/active_record/migration/command_recorder.rb +164 -0
  102. data/lib/active_record/migration/join_table.rb +15 -0
  103. data/lib/active_record/migration.rb +614 -216
  104. data/lib/active_record/model_schema.rb +345 -0
  105. data/lib/active_record/nested_attributes.rb +248 -119
  106. data/lib/active_record/null_relation.rb +65 -0
  107. data/lib/active_record/persistence.rb +275 -57
  108. data/lib/active_record/query_cache.rb +29 -9
  109. data/lib/active_record/querying.rb +62 -0
  110. data/lib/active_record/railtie.rb +135 -21
  111. data/lib/active_record/railties/console_sandbox.rb +5 -0
  112. data/lib/active_record/railties/controller_runtime.rb +17 -5
  113. data/lib/active_record/railties/databases.rake +249 -359
  114. data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
  115. data/lib/active_record/readonly_attributes.rb +30 -0
  116. data/lib/active_record/reflection.rb +283 -103
  117. data/lib/active_record/relation/batches.rb +38 -34
  118. data/lib/active_record/relation/calculations.rb +252 -139
  119. data/lib/active_record/relation/delegation.rb +125 -0
  120. data/lib/active_record/relation/finder_methods.rb +182 -188
  121. data/lib/active_record/relation/merger.rb +161 -0
  122. data/lib/active_record/relation/predicate_builder.rb +86 -21
  123. data/lib/active_record/relation/query_methods.rb +917 -134
  124. data/lib/active_record/relation/spawn_methods.rb +53 -92
  125. data/lib/active_record/relation.rb +405 -143
  126. data/lib/active_record/result.rb +67 -0
  127. data/lib/active_record/runtime_registry.rb +17 -0
  128. data/lib/active_record/sanitization.rb +168 -0
  129. data/lib/active_record/schema.rb +20 -14
  130. data/lib/active_record/schema_dumper.rb +55 -46
  131. data/lib/active_record/schema_migration.rb +39 -0
  132. data/lib/active_record/scoping/default.rb +146 -0
  133. data/lib/active_record/scoping/named.rb +175 -0
  134. data/lib/active_record/scoping.rb +82 -0
  135. data/lib/active_record/serialization.rb +8 -46
  136. data/lib/active_record/serializers/xml_serializer.rb +21 -68
  137. data/lib/active_record/statement_cache.rb +26 -0
  138. data/lib/active_record/store.rb +156 -0
  139. data/lib/active_record/tasks/database_tasks.rb +203 -0
  140. data/lib/active_record/tasks/firebird_database_tasks.rb +56 -0
  141. data/lib/active_record/tasks/mysql_database_tasks.rb +143 -0
  142. data/lib/active_record/tasks/oracle_database_tasks.rb +45 -0
  143. data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
  144. data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
  145. data/lib/active_record/tasks/sqlserver_database_tasks.rb +48 -0
  146. data/lib/active_record/test_case.rb +57 -28
  147. data/lib/active_record/timestamp.rb +49 -18
  148. data/lib/active_record/transactions.rb +106 -63
  149. data/lib/active_record/translation.rb +22 -0
  150. data/lib/active_record/validations/associated.rb +25 -24
  151. data/lib/active_record/validations/presence.rb +65 -0
  152. data/lib/active_record/validations/uniqueness.rb +123 -83
  153. data/lib/active_record/validations.rb +29 -29
  154. data/lib/active_record/version.rb +7 -5
  155. data/lib/active_record.rb +83 -34
  156. data/lib/rails/generators/active_record/migration/migration_generator.rb +46 -9
  157. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +19 -0
  158. data/lib/rails/generators/active_record/migration/templates/migration.rb +30 -8
  159. data/lib/rails/generators/active_record/model/model_generator.rb +15 -5
  160. data/lib/rails/generators/active_record/model/templates/model.rb +7 -2
  161. data/lib/rails/generators/active_record/model/templates/module.rb +3 -1
  162. data/lib/rails/generators/active_record.rb +4 -8
  163. metadata +163 -121
  164. data/CHANGELOG +0 -6023
  165. data/examples/associations.png +0 -0
  166. data/lib/active_record/association_preload.rb +0 -403
  167. data/lib/active_record/associations/association_collection.rb +0 -562
  168. data/lib/active_record/associations/association_proxy.rb +0 -295
  169. data/lib/active_record/associations/through_association_scope.rb +0 -154
  170. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -113
  171. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -401
  172. data/lib/active_record/dynamic_finder_match.rb +0 -53
  173. data/lib/active_record/dynamic_scope_match.rb +0 -32
  174. data/lib/active_record/named_scope.rb +0 -138
  175. data/lib/active_record/observer.rb +0 -140
  176. data/lib/active_record/session_store.rb +0 -340
  177. data/lib/rails/generators/active_record/model/templates/migration.rb +0 -16
  178. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  179. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -2
  180. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -24
  181. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -16
@@ -0,0 +1,135 @@
1
+ module ActiveRecord
2
+ module Associations
3
+ class AssociationScope #:nodoc:
4
+ include JoinHelper
5
+
6
+ attr_reader :association, :alias_tracker
7
+
8
+ delegate :klass, :owner, :reflection, :interpolate, :to => :association
9
+ delegate :chain, :scope_chain, :options, :source_options, :active_record, :to => :reflection
10
+
11
+ def initialize(association)
12
+ @association = association
13
+ @alias_tracker = AliasTracker.new klass.connection
14
+ end
15
+
16
+ def scope
17
+ scope = klass.unscoped
18
+ scope.extending! Array(options[:extend])
19
+ add_constraints(scope)
20
+ end
21
+
22
+ private
23
+
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
28
+
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
34
+ end
35
+
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
40
+
41
+ def add_constraints(scope)
42
+ tables = construct_tables
43
+
44
+ chain.each_with_index do |reflection, i|
45
+ table, foreign_table = tables.shift, tables.first
46
+
47
+ if reflection.source_macro == :has_and_belongs_to_many
48
+ join_table = tables.shift
49
+
50
+ scope = scope.joins(join(
51
+ join_table,
52
+ table[reflection.association_primary_key].
53
+ eq(join_table[reflection.association_foreign_key])
54
+ ))
55
+
56
+ table, foreign_table = join_table, tables.first
57
+ end
58
+
59
+ if reflection.source_macro == :belongs_to
60
+ if reflection.options[:polymorphic]
61
+ key = reflection.association_primary_key(self.klass)
62
+ else
63
+ key = reflection.association_primary_key
64
+ end
65
+
66
+ foreign_key = reflection.foreign_key
67
+ else
68
+ key = reflection.foreign_key
69
+ foreign_key = reflection.active_record_primary_key
70
+ end
71
+
72
+ if reflection == chain.last
73
+ bind_val = bind scope, table.table_name, key.to_s, owner[foreign_key]
74
+ scope = scope.where(table[key].eq(bind_val))
75
+
76
+ if reflection.type
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))
80
+ end
81
+ else
82
+ constraint = table[key].eq(foreign_table[foreign_key])
83
+
84
+ if reflection.type
85
+ type = chain[i + 1].klass.base_class.name
86
+ constraint = constraint.and(table[reflection.type].eq(type))
87
+ end
88
+
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)
97
+
98
+ if scope_chain_item == self.reflection.scope
99
+ scope.merge! item.except(:where, :includes)
100
+ end
101
+
102
+ scope.includes! item.includes_values
103
+ scope.where_values += item.where_values
104
+ scope.order_values |= item.order_values
105
+ end
106
+ end
107
+
108
+ scope
109
+ end
110
+
111
+ def alias_suffix
112
+ reflection.name
113
+ end
114
+
115
+ def table_name_for(reflection)
116
+ if reflection == self.reflection
117
+ # If this is a polymorphic belongs_to, we want to get the klass from the
118
+ # association because it depends on the polymorphic_type attribute of
119
+ # the owner
120
+ klass.table_name
121
+ else
122
+ reflection.table_name
123
+ end
124
+ end
125
+
126
+ def eval_scope(klass, scope)
127
+ if scope.is_a?(Relation)
128
+ scope
129
+ else
130
+ klass.unscoped.instance_exec(owner, &scope)
131
+ end
132
+ end
133
+ end
134
+ end
135
+ end
@@ -1,41 +1,27 @@
1
1
  module ActiveRecord
2
- # = Active Record Belongs To Associations
2
+ # = Active Record Belongs To Association
3
3
  module Associations
4
- class BelongsToAssociation < AssociationProxy #:nodoc:
5
- def create(attributes = {})
6
- replace(@reflection.create_association(attributes))
7
- end
4
+ class BelongsToAssociation < SingularAssociation #:nodoc:
8
5
 
9
- def build(attributes = {})
10
- replace(@reflection.build_association(attributes))
6
+ def handle_dependency
7
+ target.send(options[:dependent]) if load_target
11
8
  end
12
9
 
13
10
  def replace(record)
14
- counter_cache_name = @reflection.counter_cache_column
15
-
16
- if record.nil?
17
- if counter_cache_name && !@owner.new_record?
18
- @reflection.klass.decrement_counter(counter_cache_name, previous_record_id) if @owner[@reflection.primary_key_name]
19
- end
11
+ raise_on_type_mismatch!(record) if record
20
12
 
21
- @target = @owner[@reflection.primary_key_name] = nil
22
- else
23
- raise_on_type_mismatch(record)
24
-
25
- if counter_cache_name && !@owner.new_record? && record.id != @owner[@reflection.primary_key_name]
26
- @reflection.klass.increment_counter(counter_cache_name, record.id)
27
- @reflection.klass.decrement_counter(counter_cache_name, @owner[@reflection.primary_key_name]) if @owner[@reflection.primary_key_name]
28
- end
13
+ update_counters(record)
14
+ replace_keys(record)
15
+ set_inverse_instance(record)
29
16
 
30
- @target = (AssociationProxy === record ? record.target : record)
31
- @owner[@reflection.primary_key_name] = record_id(record) unless record.new_record?
32
- @updated = true
33
- end
17
+ @updated = true if record
34
18
 
35
- set_inverse_instance(record, @owner)
19
+ self.target = record
20
+ end
36
21
 
37
- loaded
38
- record
22
+ def reset
23
+ super
24
+ @updated = false
39
25
  end
40
26
 
41
27
  def updated?
@@ -43,48 +29,63 @@ module ActiveRecord
43
29
  end
44
30
 
45
31
  private
46
- def find_target
47
- find_method = if @reflection.options[:primary_key]
48
- "find_by_#{@reflection.options[:primary_key]}"
49
- else
50
- "find"
51
- end
52
-
53
- options = @reflection.options.dup
54
- (options.keys - [:select, :include, :readonly]).each do |key|
55
- options.delete key
32
+
33
+ def find_target?
34
+ !loaded? && foreign_key_present? && klass
35
+ end
36
+
37
+ def update_counters(record)
38
+ counter_cache_name = reflection.counter_cache_column
39
+
40
+ if counter_cache_name && owner.persisted? && different_target?(record)
41
+ if record
42
+ record.class.increment_counter(counter_cache_name, record.id)
43
+ end
44
+
45
+ if foreign_key_present?
46
+ klass.decrement_counter(counter_cache_name, target_id)
47
+ end
56
48
  end
57
- options[:conditions] = conditions
58
-
59
- the_target = @reflection.klass.send(find_method,
60
- @owner[@reflection.primary_key_name],
61
- options
62
- ) if @owner[@reflection.primary_key_name]
63
- set_inverse_instance(the_target, @owner)
64
- the_target
65
49
  end
66
50
 
67
- def foreign_key_present
68
- !@owner[@reflection.primary_key_name].nil?
51
+ # Checks whether record is different to the current target, without loading it
52
+ def different_target?(record)
53
+ if record.nil?
54
+ owner[reflection.foreign_key]
55
+ else
56
+ record.id != owner[reflection.foreign_key]
57
+ end
58
+ end
59
+
60
+ def replace_keys(record)
61
+ if record
62
+ owner[reflection.foreign_key] = record[reflection.association_primary_key(record.class)]
63
+ else
64
+ owner[reflection.foreign_key] = nil
65
+ end
66
+ end
67
+
68
+ def foreign_key_present?
69
+ owner[reflection.foreign_key]
69
70
  end
70
71
 
71
72
  # NOTE - for now, we're only supporting inverse setting from belongs_to back onto
72
73
  # has_one associations.
73
- def we_can_set_the_inverse_on_this?(record)
74
- @reflection.has_inverse? && @reflection.inverse_of.macro == :has_one
74
+ def invertible_for?(record)
75
+ inverse = inverse_reflection_for(record)
76
+ inverse && inverse.macro == :has_one
75
77
  end
76
78
 
77
- def record_id(record)
78
- record.send(@reflection.options[:primary_key] || :id)
79
+ def target_id
80
+ if options[:primary_key]
81
+ owner.send(reflection.name).try(:id)
82
+ else
83
+ owner[reflection.foreign_key]
84
+ end
79
85
  end
80
86
 
81
- def previous_record_id
82
- @previous_record_id ||= if @reflection.options[:primary_key]
83
- previous_record = @owner.send(@reflection.name)
84
- previous_record.nil? ? nil : previous_record.id
85
- else
86
- @owner[@reflection.primary_key_name]
87
- end
87
+ def stale_state
88
+ owner[reflection.foreign_key] && owner[reflection.foreign_key].to_s
88
89
  end
89
90
  end
90
91
  end
@@ -1,77 +1,34 @@
1
1
  module ActiveRecord
2
2
  # = Active Record Belongs To Polymorphic Association
3
3
  module Associations
4
- class BelongsToPolymorphicAssociation < AssociationProxy #:nodoc:
5
- def replace(record)
6
- if record.nil?
7
- @target = @owner[@reflection.primary_key_name] = @owner[@reflection.options[:foreign_type]] = nil
8
- else
9
- @target = (AssociationProxy === record ? record.target : record)
10
-
11
- @owner[@reflection.primary_key_name] = record_id(record)
12
- @owner[@reflection.options[:foreign_type]] = record.class.base_class.name.to_s
13
-
14
- @updated = true
15
- end
16
-
17
- set_inverse_instance(record, @owner)
18
- loaded
19
- record
20
- end
21
-
22
- def updated?
23
- @updated
4
+ class BelongsToPolymorphicAssociation < BelongsToAssociation #:nodoc:
5
+ def klass
6
+ type = owner[reflection.foreign_type]
7
+ type.presence && type.constantize
24
8
  end
25
9
 
26
10
  private
27
11
 
28
- # NOTE - for now, we're only supporting inverse setting from belongs_to back onto
29
- # has_one associations.
30
- def we_can_set_the_inverse_on_this?(record)
31
- if @reflection.has_inverse?
32
- inverse_association = @reflection.polymorphic_inverse_of(record.class)
33
- inverse_association && inverse_association.macro == :has_one
34
- else
35
- false
36
- end
37
- end
38
-
39
- def set_inverse_instance(record, instance)
40
- return if record.nil? || !we_can_set_the_inverse_on_this?(record)
41
- inverse_relationship = @reflection.polymorphic_inverse_of(record.class)
42
- unless inverse_relationship.nil?
43
- record.send(:"set_#{inverse_relationship.name}_target", instance)
44
- end
12
+ def replace_keys(record)
13
+ super
14
+ owner[reflection.foreign_type] = record && record.class.base_class.name
45
15
  end
46
16
 
47
- def find_target
48
- return nil if association_class.nil?
49
-
50
- target =
51
- if @reflection.options[:conditions]
52
- association_class.find(
53
- @owner[@reflection.primary_key_name],
54
- :select => @reflection.options[:select],
55
- :conditions => conditions,
56
- :include => @reflection.options[:include]
57
- )
58
- else
59
- association_class.find(@owner[@reflection.primary_key_name], :select => @reflection.options[:select], :include => @reflection.options[:include])
60
- end
61
- set_inverse_instance(target, @owner)
62
- target
17
+ def different_target?(record)
18
+ super || record.class != klass
63
19
  end
64
20
 
65
- def foreign_key_present
66
- !@owner[@reflection.primary_key_name].nil?
21
+ def inverse_reflection_for(record)
22
+ reflection.polymorphic_inverse_of(record.class)
67
23
  end
68
24
 
69
- def record_id(record)
70
- record.send(@reflection.options[:primary_key] || :id)
25
+ def raise_on_type_mismatch!(record)
26
+ # A polymorphic association cannot have a type mismatch, by definition
71
27
  end
72
28
 
73
- def association_class
74
- @owner[@reflection.options[:foreign_type]] ? @owner[@reflection.options[:foreign_type]].constantize : nil
29
+ def stale_state
30
+ foreign_key = super
31
+ foreign_key && [foreign_key.to_s, owner[reflection.foreign_type].to_s]
75
32
  end
76
33
  end
77
34
  end
@@ -0,0 +1,108 @@
1
+ module ActiveRecord::Associations::Builder
2
+ class Association #:nodoc:
3
+ class << self
4
+ attr_accessor :valid_options
5
+ end
6
+
7
+ self.valid_options = [:class_name, :foreign_key, :validate]
8
+
9
+ attr_reader :model, :name, :scope, :options, :reflection
10
+
11
+ def self.build(*args, &block)
12
+ new(*args, &block).build
13
+ end
14
+
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
33
+ end
34
+
35
+ def mixin
36
+ @model.generated_feature_methods
37
+ end
38
+
39
+ include Module.new { def build; end }
40
+
41
+ def build
42
+ validate_options
43
+ define_accessors
44
+ configure_dependency if options[:dependent]
45
+ @reflection = model.create_reflection(macro, name, scope, options, model)
46
+ super # provides an extension point
47
+ @reflection
48
+ end
49
+
50
+ def macro
51
+ raise NotImplementedError
52
+ end
53
+
54
+ def valid_options
55
+ Association.valid_options
56
+ end
57
+
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
66
+
67
+ def define_readers
68
+ mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
69
+ def #{name}(*args)
70
+ association(:#{name}).reader(*args)
71
+ end
72
+ CODE
73
+ end
74
+
75
+ def define_writers
76
+ mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
77
+ def #{name}=(value)
78
+ association(:#{name}).writer(value)
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
+ )
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
107
+ end
108
+ end
@@ -0,0 +1,98 @@
1
+ module ActiveRecord::Associations::Builder
2
+ class BelongsTo < SingularAssociation #:nodoc:
3
+ def macro
4
+ :belongs_to
5
+ end
6
+
7
+ def valid_options
8
+ super + [:foreign_type, :polymorphic, :touch]
9
+ end
10
+
11
+ def constructable?
12
+ !options[:polymorphic]
13
+ end
14
+
15
+ def build
16
+ reflection = super
17
+ add_counter_cache_callbacks(reflection) if options[:counter_cache]
18
+ add_touch_callbacks(reflection) if options[:touch]
19
+ reflection
20
+ end
21
+
22
+ def add_counter_cache_callbacks(reflection)
23
+ cache_column = reflection.counter_cache_column
24
+ foreign_key = reflection.foreign_key
25
+
26
+ mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
27
+ def belongs_to_counter_cache_after_create_for_#{name}
28
+ if record = #{name}
29
+ record.class.increment_counter(:#{cache_column}, record.id)
30
+ @_after_create_counter_called = true
31
+ end
32
+ end
33
+
34
+ def belongs_to_counter_cache_before_destroy_for_#{name}
35
+ unless destroyed_by_association && destroyed_by_association.foreign_key.to_sym == #{foreign_key.to_sym.inspect}
36
+ record = #{name}
37
+ record.class.decrement_counter(:#{cache_column}, record.id) unless record.nil?
38
+ end
39
+ end
40
+
41
+ def belongs_to_counter_cache_after_update_for_#{name}
42
+ if (@_after_create_counter_called ||= false)
43
+ @_after_create_counter_called = false
44
+ elsif self.#{foreign_key}_changed? && !new_record? && defined?(#{name.to_s.camelize})
45
+ model = #{name.to_s.camelize}
46
+ foreign_key_was = self.#{foreign_key}_was
47
+ foreign_key = self.#{foreign_key}
48
+
49
+ if foreign_key && model.respond_to?(:increment_counter)
50
+ model.increment_counter(:#{cache_column}, foreign_key)
51
+ end
52
+ if foreign_key_was && model.respond_to?(:decrement_counter)
53
+ model.decrement_counter(:#{cache_column}, foreign_key_was)
54
+ end
55
+ end
56
+ end
57
+ CODE
58
+
59
+ model.after_create "belongs_to_counter_cache_after_create_for_#{name}"
60
+ model.before_destroy "belongs_to_counter_cache_before_destroy_for_#{name}"
61
+ model.after_update "belongs_to_counter_cache_after_update_for_#{name}"
62
+
63
+ klass = reflection.class_name.safe_constantize
64
+ klass.attr_readonly cache_column if klass && klass.respond_to?(:attr_readonly)
65
+ end
66
+
67
+ def add_touch_callbacks(reflection)
68
+ mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
69
+ def belongs_to_touch_after_save_or_destroy_for_#{name}
70
+ foreign_key_field = #{reflection.foreign_key.inspect}
71
+ old_foreign_id = attribute_was(foreign_key_field)
72
+
73
+ if old_foreign_id
74
+ klass = association(#{name.inspect}).klass
75
+ old_record = klass.find_by(klass.primary_key => old_foreign_id)
76
+
77
+ if old_record
78
+ old_record.touch #{options[:touch].inspect if options[:touch] != true}
79
+ end
80
+ end
81
+
82
+ record = #{name}
83
+ unless record.nil? || record.new_record?
84
+ record.touch #{options[:touch].inspect if options[:touch] != true}
85
+ end
86
+ end
87
+ CODE
88
+
89
+ model.after_save "belongs_to_touch_after_save_or_destroy_for_#{name}"
90
+ model.after_touch "belongs_to_touch_after_save_or_destroy_for_#{name}"
91
+ model.after_destroy "belongs_to_touch_after_save_or_destroy_for_#{name}"
92
+ end
93
+
94
+ def valid_dependent_options
95
+ [:destroy, :delete]
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,89 @@
1
+ require 'active_record/associations'
2
+
3
+ module ActiveRecord::Associations::Builder
4
+ class CollectionAssociation < Association #:nodoc:
5
+
6
+ CALLBACKS = [:before_add, :after_add, :before_remove, :after_remove]
7
+
8
+ def valid_options
9
+ super + [:table_name, :finder_sql, :counter_sql, :before_add,
10
+ :after_add, :before_remove, :after_remove, :extend]
11
+ end
12
+
13
+ attr_reader :block_extension, :extension_module
14
+
15
+ def initialize(*args, &extension)
16
+ super(*args)
17
+ @block_extension = extension
18
+ end
19
+
20
+ def build
21
+ show_deprecation_warnings
22
+ wrap_block_extension
23
+ reflection = super
24
+ CALLBACKS.each { |callback_name| define_callback(callback_name) }
25
+ reflection
26
+ end
27
+
28
+ def writable?
29
+ true
30
+ end
31
+
32
+ def show_deprecation_warnings
33
+ [:finder_sql, :counter_sql].each do |name|
34
+ if options.include? name
35
+ ActiveSupport::Deprecation.warn("The :#{name} association option is deprecated. Please find an alternative (such as using scopes).")
36
+ end
37
+ end
38
+ end
39
+
40
+ def wrap_block_extension
41
+ if block_extension
42
+ @extension_module = mod = Module.new(&block_extension)
43
+ silence_warnings do
44
+ model.parent.const_set(extension_module_name, mod)
45
+ end
46
+
47
+ prev_scope = @scope
48
+
49
+ if prev_scope
50
+ @scope = proc { |owner| instance_exec(owner, &prev_scope).extending(mod) }
51
+ else
52
+ @scope = proc { extending(mod) }
53
+ end
54
+ end
55
+ end
56
+
57
+ def extension_module_name
58
+ @extension_module_name ||= "#{model.name.demodulize}#{name.to_s.camelize}AssociationExtension"
59
+ end
60
+
61
+ def define_callback(callback_name)
62
+ full_callback_name = "#{callback_name}_for_#{name}"
63
+
64
+ # TODO : why do i need method_defined? I think its because of the inheritance chain
65
+ model.class_attribute full_callback_name.to_sym unless model.method_defined?(full_callback_name)
66
+ model.send("#{full_callback_name}=", Array(options[callback_name.to_sym]))
67
+ end
68
+
69
+ def define_readers
70
+ super
71
+
72
+ mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
73
+ def #{name.to_s.singularize}_ids
74
+ association(:#{name}).ids_reader
75
+ end
76
+ CODE
77
+ end
78
+
79
+ def define_writers
80
+ super
81
+
82
+ mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
83
+ def #{name.to_s.singularize}_ids=(ids)
84
+ association(:#{name}).ids_writer(ids)
85
+ end
86
+ CODE
87
+ end
88
+ end
89
+ end