activerecord 1.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 (255) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +2102 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +213 -0
  5. data/examples/performance.rb +172 -0
  6. data/examples/simple.rb +14 -0
  7. data/lib/active_record/aggregations.rb +180 -84
  8. data/lib/active_record/associations/alias_tracker.rb +76 -0
  9. data/lib/active_record/associations/association.rb +248 -0
  10. data/lib/active_record/associations/association_scope.rb +135 -0
  11. data/lib/active_record/associations/belongs_to_association.rb +92 -0
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +35 -0
  13. data/lib/active_record/associations/builder/association.rb +108 -0
  14. data/lib/active_record/associations/builder/belongs_to.rb +98 -0
  15. data/lib/active_record/associations/builder/collection_association.rb +89 -0
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +39 -0
  17. data/lib/active_record/associations/builder/has_many.rb +15 -0
  18. data/lib/active_record/associations/builder/has_one.rb +25 -0
  19. data/lib/active_record/associations/builder/singular_association.rb +32 -0
  20. data/lib/active_record/associations/collection_association.rb +608 -0
  21. data/lib/active_record/associations/collection_proxy.rb +986 -0
  22. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +58 -39
  23. data/lib/active_record/associations/has_many_association.rb +116 -85
  24. data/lib/active_record/associations/has_many_through_association.rb +197 -0
  25. data/lib/active_record/associations/has_one_association.rb +102 -0
  26. data/lib/active_record/associations/has_one_through_association.rb +36 -0
  27. data/lib/active_record/associations/join_dependency/join_association.rb +174 -0
  28. data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
  29. data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
  30. data/lib/active_record/associations/join_dependency.rb +235 -0
  31. data/lib/active_record/associations/join_helper.rb +45 -0
  32. data/lib/active_record/associations/preloader/association.rb +121 -0
  33. data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
  34. data/lib/active_record/associations/preloader/collection_association.rb +24 -0
  35. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
  36. data/lib/active_record/associations/preloader/has_many.rb +17 -0
  37. data/lib/active_record/associations/preloader/has_many_through.rb +19 -0
  38. data/lib/active_record/associations/preloader/has_one.rb +23 -0
  39. data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
  40. data/lib/active_record/associations/preloader/singular_association.rb +21 -0
  41. data/lib/active_record/associations/preloader/through_association.rb +63 -0
  42. data/lib/active_record/associations/preloader.rb +178 -0
  43. data/lib/active_record/associations/singular_association.rb +64 -0
  44. data/lib/active_record/associations/through_association.rb +87 -0
  45. data/lib/active_record/associations.rb +1437 -431
  46. data/lib/active_record/attribute_assignment.rb +201 -0
  47. data/lib/active_record/attribute_methods/before_type_cast.rb +70 -0
  48. data/lib/active_record/attribute_methods/dirty.rb +118 -0
  49. data/lib/active_record/attribute_methods/primary_key.rb +122 -0
  50. data/lib/active_record/attribute_methods/query.rb +40 -0
  51. data/lib/active_record/attribute_methods/read.rb +107 -0
  52. data/lib/active_record/attribute_methods/serialization.rb +162 -0
  53. data/lib/active_record/attribute_methods/time_zone_conversion.rb +59 -0
  54. data/lib/active_record/attribute_methods/write.rb +63 -0
  55. data/lib/active_record/attribute_methods.rb +393 -0
  56. data/lib/active_record/autosave_association.rb +426 -0
  57. data/lib/active_record/base.rb +268 -930
  58. data/lib/active_record/callbacks.rb +203 -230
  59. data/lib/active_record/coders/yaml_column.rb +38 -0
  60. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +638 -0
  61. data/lib/active_record/connection_adapters/abstract/database_limits.rb +67 -0
  62. data/lib/active_record/connection_adapters/abstract/database_statements.rb +390 -0
  63. data/lib/active_record/connection_adapters/abstract/query_cache.rb +95 -0
  64. data/lib/active_record/connection_adapters/abstract/quoting.rb +129 -0
  65. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +501 -0
  66. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +70 -0
  67. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +873 -0
  68. data/lib/active_record/connection_adapters/abstract/transaction.rb +203 -0
  69. data/lib/active_record/connection_adapters/abstract_adapter.rb +389 -275
  70. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +782 -0
  71. data/lib/active_record/connection_adapters/column.rb +318 -0
  72. data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
  73. data/lib/active_record/connection_adapters/mysql2_adapter.rb +273 -0
  74. data/lib/active_record/connection_adapters/mysql_adapter.rb +517 -90
  75. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +97 -0
  76. data/lib/active_record/connection_adapters/postgresql/cast.rb +152 -0
  77. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +242 -0
  78. data/lib/active_record/connection_adapters/postgresql/oid.rb +366 -0
  79. data/lib/active_record/connection_adapters/postgresql/quoting.rb +171 -0
  80. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  81. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +489 -0
  82. data/lib/active_record/connection_adapters/postgresql_adapter.rb +911 -138
  83. data/lib/active_record/connection_adapters/schema_cache.rb +129 -0
  84. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +624 -0
  85. data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
  86. data/lib/active_record/connection_handling.rb +98 -0
  87. data/lib/active_record/core.rb +463 -0
  88. data/lib/active_record/counter_cache.rb +122 -0
  89. data/lib/active_record/dynamic_matchers.rb +131 -0
  90. data/lib/active_record/errors.rb +213 -0
  91. data/lib/active_record/explain.rb +38 -0
  92. data/lib/active_record/explain_registry.rb +30 -0
  93. data/lib/active_record/explain_subscriber.rb +29 -0
  94. data/lib/active_record/fixture_set/file.rb +55 -0
  95. data/lib/active_record/fixtures.rb +892 -138
  96. data/lib/active_record/inheritance.rb +200 -0
  97. data/lib/active_record/integration.rb +60 -0
  98. data/lib/active_record/locale/en.yml +47 -0
  99. data/lib/active_record/locking/optimistic.rb +181 -0
  100. data/lib/active_record/locking/pessimistic.rb +77 -0
  101. data/lib/active_record/log_subscriber.rb +82 -0
  102. data/lib/active_record/migration/command_recorder.rb +164 -0
  103. data/lib/active_record/migration/join_table.rb +15 -0
  104. data/lib/active_record/migration.rb +1015 -0
  105. data/lib/active_record/model_schema.rb +345 -0
  106. data/lib/active_record/nested_attributes.rb +546 -0
  107. data/lib/active_record/null_relation.rb +65 -0
  108. data/lib/active_record/persistence.rb +509 -0
  109. data/lib/active_record/query_cache.rb +56 -0
  110. data/lib/active_record/querying.rb +62 -0
  111. data/lib/active_record/railtie.rb +205 -0
  112. data/lib/active_record/railties/console_sandbox.rb +5 -0
  113. data/lib/active_record/railties/controller_runtime.rb +50 -0
  114. data/lib/active_record/railties/databases.rake +402 -0
  115. data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
  116. data/lib/active_record/readonly_attributes.rb +30 -0
  117. data/lib/active_record/reflection.rb +544 -87
  118. data/lib/active_record/relation/batches.rb +93 -0
  119. data/lib/active_record/relation/calculations.rb +399 -0
  120. data/lib/active_record/relation/delegation.rb +125 -0
  121. data/lib/active_record/relation/finder_methods.rb +349 -0
  122. data/lib/active_record/relation/merger.rb +161 -0
  123. data/lib/active_record/relation/predicate_builder.rb +106 -0
  124. data/lib/active_record/relation/query_methods.rb +1044 -0
  125. data/lib/active_record/relation/spawn_methods.rb +73 -0
  126. data/lib/active_record/relation.rb +655 -0
  127. data/lib/active_record/result.rb +67 -0
  128. data/lib/active_record/runtime_registry.rb +17 -0
  129. data/lib/active_record/sanitization.rb +168 -0
  130. data/lib/active_record/schema.rb +65 -0
  131. data/lib/active_record/schema_dumper.rb +204 -0
  132. data/lib/active_record/schema_migration.rb +39 -0
  133. data/lib/active_record/scoping/default.rb +146 -0
  134. data/lib/active_record/scoping/named.rb +175 -0
  135. data/lib/active_record/scoping.rb +82 -0
  136. data/lib/active_record/serialization.rb +22 -0
  137. data/lib/active_record/serializers/xml_serializer.rb +197 -0
  138. data/lib/active_record/statement_cache.rb +26 -0
  139. data/lib/active_record/store.rb +156 -0
  140. data/lib/active_record/tasks/database_tasks.rb +203 -0
  141. data/lib/active_record/tasks/firebird_database_tasks.rb +56 -0
  142. data/lib/active_record/tasks/mysql_database_tasks.rb +143 -0
  143. data/lib/active_record/tasks/oracle_database_tasks.rb +45 -0
  144. data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
  145. data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
  146. data/lib/active_record/tasks/sqlserver_database_tasks.rb +48 -0
  147. data/lib/active_record/test_case.rb +96 -0
  148. data/lib/active_record/timestamp.rb +119 -0
  149. data/lib/active_record/transactions.rb +366 -69
  150. data/lib/active_record/translation.rb +22 -0
  151. data/lib/active_record/validations/associated.rb +49 -0
  152. data/lib/active_record/validations/presence.rb +65 -0
  153. data/lib/active_record/validations/uniqueness.rb +225 -0
  154. data/lib/active_record/validations.rb +64 -185
  155. data/lib/active_record/version.rb +11 -0
  156. data/lib/active_record.rb +149 -24
  157. data/lib/rails/generators/active_record/migration/migration_generator.rb +62 -0
  158. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +19 -0
  159. data/lib/rails/generators/active_record/migration/templates/migration.rb +39 -0
  160. data/lib/rails/generators/active_record/model/model_generator.rb +48 -0
  161. data/lib/rails/generators/active_record/model/templates/model.rb +10 -0
  162. data/lib/rails/generators/active_record/model/templates/module.rb +7 -0
  163. data/lib/rails/generators/active_record.rb +23 -0
  164. metadata +261 -161
  165. data/CHANGELOG +0 -581
  166. data/README +0 -361
  167. data/RUNNING_UNIT_TESTS +0 -36
  168. data/dev-utils/eval_debugger.rb +0 -9
  169. data/examples/associations.png +0 -0
  170. data/examples/associations.rb +0 -87
  171. data/examples/shared_setup.rb +0 -15
  172. data/examples/validation.rb +0 -88
  173. data/install.rb +0 -60
  174. data/lib/active_record/associations/association_collection.rb +0 -70
  175. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -107
  176. data/lib/active_record/deprecated_associations.rb +0 -70
  177. data/lib/active_record/observer.rb +0 -71
  178. data/lib/active_record/support/class_attribute_accessors.rb +0 -43
  179. data/lib/active_record/support/class_inheritable_attributes.rb +0 -37
  180. data/lib/active_record/support/clean_logger.rb +0 -10
  181. data/lib/active_record/support/inflector.rb +0 -70
  182. data/lib/active_record/vendor/mysql.rb +0 -1117
  183. data/lib/active_record/vendor/simple.rb +0 -702
  184. data/lib/active_record/wrappers/yaml_wrapper.rb +0 -15
  185. data/lib/active_record/wrappings.rb +0 -59
  186. data/rakefile +0 -122
  187. data/test/abstract_unit.rb +0 -16
  188. data/test/aggregations_test.rb +0 -34
  189. data/test/all.sh +0 -8
  190. data/test/associations_test.rb +0 -477
  191. data/test/base_test.rb +0 -513
  192. data/test/class_inheritable_attributes_test.rb +0 -33
  193. data/test/connections/native_mysql/connection.rb +0 -24
  194. data/test/connections/native_postgresql/connection.rb +0 -24
  195. data/test/connections/native_sqlite/connection.rb +0 -24
  196. data/test/deprecated_associations_test.rb +0 -336
  197. data/test/finder_test.rb +0 -67
  198. data/test/fixtures/accounts/signals37 +0 -3
  199. data/test/fixtures/accounts/unknown +0 -2
  200. data/test/fixtures/auto_id.rb +0 -4
  201. data/test/fixtures/column_name.rb +0 -3
  202. data/test/fixtures/companies/first_client +0 -6
  203. data/test/fixtures/companies/first_firm +0 -4
  204. data/test/fixtures/companies/second_client +0 -6
  205. data/test/fixtures/company.rb +0 -37
  206. data/test/fixtures/company_in_module.rb +0 -33
  207. data/test/fixtures/course.rb +0 -3
  208. data/test/fixtures/courses/java +0 -2
  209. data/test/fixtures/courses/ruby +0 -2
  210. data/test/fixtures/customer.rb +0 -30
  211. data/test/fixtures/customers/david +0 -6
  212. data/test/fixtures/db_definitions/mysql.sql +0 -96
  213. data/test/fixtures/db_definitions/mysql2.sql +0 -4
  214. data/test/fixtures/db_definitions/postgresql.sql +0 -113
  215. data/test/fixtures/db_definitions/postgresql2.sql +0 -4
  216. data/test/fixtures/db_definitions/sqlite.sql +0 -85
  217. data/test/fixtures/db_definitions/sqlite2.sql +0 -4
  218. data/test/fixtures/default.rb +0 -2
  219. data/test/fixtures/developer.rb +0 -8
  220. data/test/fixtures/developers/david +0 -2
  221. data/test/fixtures/developers/jamis +0 -2
  222. data/test/fixtures/developers_projects/david_action_controller +0 -2
  223. data/test/fixtures/developers_projects/david_active_record +0 -2
  224. data/test/fixtures/developers_projects/jamis_active_record +0 -2
  225. data/test/fixtures/entrant.rb +0 -3
  226. data/test/fixtures/entrants/first +0 -3
  227. data/test/fixtures/entrants/second +0 -3
  228. data/test/fixtures/entrants/third +0 -3
  229. data/test/fixtures/fixture_database.sqlite +0 -0
  230. data/test/fixtures/fixture_database_2.sqlite +0 -0
  231. data/test/fixtures/movie.rb +0 -5
  232. data/test/fixtures/movies/first +0 -2
  233. data/test/fixtures/movies/second +0 -2
  234. data/test/fixtures/project.rb +0 -3
  235. data/test/fixtures/projects/action_controller +0 -2
  236. data/test/fixtures/projects/active_record +0 -2
  237. data/test/fixtures/reply.rb +0 -21
  238. data/test/fixtures/subscriber.rb +0 -5
  239. data/test/fixtures/subscribers/first +0 -2
  240. data/test/fixtures/subscribers/second +0 -2
  241. data/test/fixtures/topic.rb +0 -20
  242. data/test/fixtures/topics/first +0 -9
  243. data/test/fixtures/topics/second +0 -8
  244. data/test/fixtures_test.rb +0 -20
  245. data/test/inflector_test.rb +0 -104
  246. data/test/inheritance_test.rb +0 -125
  247. data/test/lifecycle_test.rb +0 -110
  248. data/test/modules_test.rb +0 -21
  249. data/test/multiple_db_test.rb +0 -46
  250. data/test/pk_test.rb +0 -57
  251. data/test/reflection_test.rb +0 -78
  252. data/test/thread_safety_test.rb +0 -33
  253. data/test/transactions_test.rb +0 -83
  254. data/test/unconnected_test.rb +0 -24
  255. data/test/validations_test.rb +0 -126
@@ -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
@@ -0,0 +1,92 @@
1
+ module ActiveRecord
2
+ # = Active Record Belongs To Association
3
+ module Associations
4
+ class BelongsToAssociation < SingularAssociation #:nodoc:
5
+
6
+ def handle_dependency
7
+ target.send(options[:dependent]) if load_target
8
+ end
9
+
10
+ def replace(record)
11
+ raise_on_type_mismatch!(record) if record
12
+
13
+ update_counters(record)
14
+ replace_keys(record)
15
+ set_inverse_instance(record)
16
+
17
+ @updated = true if record
18
+
19
+ self.target = record
20
+ end
21
+
22
+ def reset
23
+ super
24
+ @updated = false
25
+ end
26
+
27
+ def updated?
28
+ @updated
29
+ end
30
+
31
+ private
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
48
+ end
49
+ end
50
+
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]
70
+ end
71
+
72
+ # NOTE - for now, we're only supporting inverse setting from belongs_to back onto
73
+ # has_one associations.
74
+ def invertible_for?(record)
75
+ inverse = inverse_reflection_for(record)
76
+ inverse && inverse.macro == :has_one
77
+ end
78
+
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
85
+ end
86
+
87
+ def stale_state
88
+ owner[reflection.foreign_key] && owner[reflection.foreign_key].to_s
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,35 @@
1
+ module ActiveRecord
2
+ # = Active Record Belongs To Polymorphic Association
3
+ module Associations
4
+ class BelongsToPolymorphicAssociation < BelongsToAssociation #:nodoc:
5
+ def klass
6
+ type = owner[reflection.foreign_type]
7
+ type.presence && type.constantize
8
+ end
9
+
10
+ private
11
+
12
+ def replace_keys(record)
13
+ super
14
+ owner[reflection.foreign_type] = record && record.class.base_class.name
15
+ end
16
+
17
+ def different_target?(record)
18
+ super || record.class != klass
19
+ end
20
+
21
+ def inverse_reflection_for(record)
22
+ reflection.polymorphic_inverse_of(record.class)
23
+ end
24
+
25
+ def raise_on_type_mismatch!(record)
26
+ # A polymorphic association cannot have a type mismatch, by definition
27
+ end
28
+
29
+ def stale_state
30
+ foreign_key = super
31
+ foreign_key && [foreign_key.to_s, owner[reflection.foreign_type].to_s]
32
+ end
33
+ end
34
+ end
35
+ 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
@@ -0,0 +1,39 @@
1
+ module ActiveRecord::Associations::Builder
2
+ class HasAndBelongsToMany < CollectionAssociation #:nodoc:
3
+ def macro
4
+ :has_and_belongs_to_many
5
+ end
6
+
7
+ def valid_options
8
+ super + [:join_table, :association_foreign_key, :delete_sql, :insert_sql]
9
+ end
10
+
11
+ def build
12
+ reflection = super
13
+ define_destroy_hook
14
+ reflection
15
+ end
16
+
17
+ def show_deprecation_warnings
18
+ super
19
+
20
+ [:delete_sql, :insert_sql].each do |name|
21
+ if options.include? name
22
+ ActiveSupport::Deprecation.warn("The :#{name} association option is deprecated. Please find an alternative (such as using has_many :through).")
23
+ end
24
+ end
25
+ end
26
+
27
+ def define_destroy_hook
28
+ name = self.name
29
+ model.send(:include, Module.new {
30
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
31
+ def destroy_associations
32
+ association(:#{name}).delete_all
33
+ super
34
+ end
35
+ RUBY
36
+ })
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,15 @@
1
+ module ActiveRecord::Associations::Builder
2
+ class HasMany < CollectionAssociation #:nodoc:
3
+ def macro
4
+ :has_many
5
+ end
6
+
7
+ def valid_options
8
+ super + [:primary_key, :dependent, :as, :through, :source, :source_type, :inverse_of, :counter_cache]
9
+ end
10
+
11
+ def valid_dependent_options
12
+ [:destroy, :delete_all, :nullify, :restrict, :restrict_with_error, :restrict_with_exception]
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,25 @@
1
+ module ActiveRecord::Associations::Builder
2
+ class HasOne < SingularAssociation #:nodoc:
3
+ def macro
4
+ :has_one
5
+ end
6
+
7
+ def valid_options
8
+ valid = super + [:order, :as]
9
+ valid += [:through, :source, :source_type] if options[:through]
10
+ valid
11
+ end
12
+
13
+ def constructable?
14
+ !options[:through]
15
+ end
16
+
17
+ def configure_dependency
18
+ super unless options[:through]
19
+ end
20
+
21
+ def valid_dependent_options
22
+ [:destroy, :delete, :nullify, :restrict, :restrict_with_error, :restrict_with_exception]
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,32 @@
1
+ module ActiveRecord::Associations::Builder
2
+ class SingularAssociation < Association #:nodoc:
3
+ def valid_options
4
+ super + [:remote, :dependent, :counter_cache, :primary_key, :inverse_of]
5
+ end
6
+
7
+ def constructable?
8
+ true
9
+ end
10
+
11
+ def define_accessors
12
+ super
13
+ define_constructors if constructable?
14
+ end
15
+
16
+ def define_constructors
17
+ mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
18
+ def build_#{name}(*args, &block)
19
+ association(:#{name}).build(*args, &block)
20
+ end
21
+
22
+ def create_#{name}(*args, &block)
23
+ association(:#{name}).create(*args, &block)
24
+ end
25
+
26
+ def create_#{name}!(*args, &block)
27
+ association(:#{name}).create!(*args, &block)
28
+ end
29
+ CODE
30
+ end
31
+ end
32
+ end