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
@@ -1,126 +1,583 @@
1
1
  module ActiveRecord
2
+ # = Active Record Reflection
2
3
  module Reflection # :nodoc:
3
- def self.append_features(base)
4
- super
5
- base.extend(ClassMethods)
4
+ extend ActiveSupport::Concern
6
5
 
7
- base.class_eval do
8
- class << self
9
- alias_method :composed_of_without_reflection, :composed_of
10
-
11
- def composed_of_with_reflection(part_id, options = {})
12
- composed_of_without_reflection(part_id, options)
13
- write_inheritable_array "aggregations", [ AggregateReflection.new(part_id, options, self) ]
14
- end
6
+ included do
7
+ class_attribute :reflections
8
+ self.reflections = {}
9
+ end
15
10
 
16
- alias_method :composed_of, :composed_of_with_reflection
11
+ # Reflection enables to interrogate Active Record classes and objects
12
+ # about their associations and aggregations. This information can,
13
+ # for example, be used in a form builder that takes an Active Record object
14
+ # and creates input fields for all of the attributes depending on their type
15
+ # and displays the associations to other objects.
16
+ #
17
+ # MacroReflection class has info for AggregateReflection and AssociationReflection
18
+ # classes.
19
+ module ClassMethods
20
+ def create_reflection(macro, name, scope, options, active_record)
21
+ case macro
22
+ when :has_many, :belongs_to, :has_one, :has_and_belongs_to_many
23
+ klass = options[:through] ? ThroughReflection : AssociationReflection
24
+ reflection = klass.new(macro, name, scope, options, active_record)
25
+ when :composed_of
26
+ reflection = AggregateReflection.new(macro, name, scope, options, active_record)
17
27
  end
18
- end
19
-
20
- for association_type in %w( belongs_to has_one has_many has_and_belongs_to_many )
21
- base.module_eval <<-"end_eval"
22
- class << self
23
- alias_method :#{association_type}_without_reflection, :#{association_type}
24
28
 
25
- def #{association_type}_with_reflection(association_id, options = {})
26
- #{association_type}_without_reflection(association_id, options)
27
- write_inheritable_array "associations", [ AssociationReflection.new(association_id, options, self) ]
28
- end
29
-
30
- alias_method :#{association_type}, :#{association_type}_with_reflection
31
- end
32
- end_eval
29
+ self.reflections = self.reflections.merge(name => reflection)
30
+ reflection
33
31
  end
34
- end
35
32
 
36
- # Reflection allows you to interrogate Active Record classes and objects about their associations and aggregations.
37
- # This information can for example be used in a form builder that took an Active Record object and created input
38
- # fields for all of the attributes depending on their type and displayed the associations to other objects.
39
- #
40
- # You can find the interface for the AggregateReflection and AssociationReflection classes in the abstract MacroReflection class.
41
- module ClassMethods
42
33
  # Returns an array of AggregateReflection objects for all the aggregations in the class.
43
34
  def reflect_on_all_aggregations
44
- read_inheritable_attribute "aggregations"
35
+ reflections.values.grep(AggregateReflection)
45
36
  end
46
-
47
- # Returns the AggregateReflection object for the named +aggregation+ (use the symbol). Example:
48
- # Account.reflect_on_aggregation(:balance) # returns the balance AggregateReflection
37
+
38
+ # Returns the AggregateReflection object for the named +aggregation+ (use the symbol).
39
+ #
40
+ # Account.reflect_on_aggregation(:balance) # => the balance AggregateReflection
41
+ #
49
42
  def reflect_on_aggregation(aggregation)
50
- reflect_on_all_aggregations.find { |reflection| reflection.name == aggregation } unless reflect_on_all_aggregations.nil?
43
+ reflection = reflections[aggregation]
44
+ reflection if reflection.is_a?(AggregateReflection)
51
45
  end
52
46
 
53
- # Returns an array of AssociationReflection objects for all the aggregations in the class.
54
- def reflect_on_all_associations
55
- read_inheritable_attribute "associations"
47
+ # Returns an array of AssociationReflection objects for all the
48
+ # associations in the class. If you only want to reflect on a certain
49
+ # association type, pass in the symbol (<tt>:has_many</tt>, <tt>:has_one</tt>,
50
+ # <tt>:belongs_to</tt>) as the first parameter.
51
+ #
52
+ # Example:
53
+ #
54
+ # Account.reflect_on_all_associations # returns an array of all associations
55
+ # Account.reflect_on_all_associations(:has_many) # returns an array of all has_many associations
56
+ #
57
+ def reflect_on_all_associations(macro = nil)
58
+ association_reflections = reflections.values.grep(AssociationReflection)
59
+ macro ? association_reflections.select { |reflection| reflection.macro == macro } : association_reflections
56
60
  end
57
-
58
- # Returns the AssociationReflection object for the named +aggregation+ (use the symbol). Example:
59
- # Account.reflect_on_association(:owner) # returns the owner AssociationReflection
61
+
62
+ # Returns the AssociationReflection object for the +association+ (use the symbol).
63
+ #
64
+ # Account.reflect_on_association(:owner) # returns the owner AssociationReflection
65
+ # Invoice.reflect_on_association(:line_items).macro # returns :has_many
66
+ #
60
67
  def reflect_on_association(association)
61
- reflect_on_all_associations.find { |reflection| reflection.name == association } unless reflect_on_all_associations.nil?
68
+ reflection = reflections[association]
69
+ reflection if reflection.is_a?(AssociationReflection)
70
+ end
71
+
72
+ # Returns an array of AssociationReflection objects for all associations which have <tt>:autosave</tt> enabled.
73
+ def reflect_on_all_autosave_associations
74
+ reflections.values.select { |reflection| reflection.options[:autosave] }
62
75
  end
63
76
  end
64
-
65
77
 
66
- # Abstract base class for AggregateReflection and AssociationReflection that describes the interface available for both of
67
- # those classes. Objects of AggregateReflection and AssociationReflection are returned by the Reflection::ClassMethods.
78
+ # Base class for AggregateReflection and AssociationReflection. Objects of
79
+ # AggregateReflection and AssociationReflection are returned by the Reflection::ClassMethods.
80
+ #
81
+ # MacroReflection
82
+ # AggregateReflection
83
+ # AssociationReflection
84
+ # ThroughReflection
68
85
  class MacroReflection
86
+ # Returns the name of the macro.
87
+ #
88
+ # <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>:balance</tt>
89
+ # <tt>has_many :clients</tt> returns <tt>:clients</tt>
90
+ attr_reader :name
91
+
92
+ # Returns the macro type.
93
+ #
94
+ # <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>:composed_of</tt>
95
+ # <tt>has_many :clients</tt> returns <tt>:has_many</tt>
96
+ attr_reader :macro
97
+
98
+ attr_reader :scope
99
+
100
+ # Returns the hash of options used for the macro.
101
+ #
102
+ # <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>{ class_name: "Money" }</tt>
103
+ # <tt>has_many :clients</tt> returns +{}+
104
+ attr_reader :options
105
+
69
106
  attr_reader :active_record
70
- def initialize(name, options, active_record)
71
- @name, @options, @active_record = name, options, active_record
72
- end
73
-
74
- # Returns the name of the macro, so it would return :balance for "composed_of :balance, :class_name => 'Money'" or
75
- # :clients for "has_many :clients".
76
- def name
77
- @name
78
- end
79
-
80
- # Returns the hash of options used for the macro, so it would return { :class_name => "Money" } for
81
- # "composed_of :balance, :class_name => 'Money'" or {} for "has_many :clients".
82
- def options
83
- @options
84
- end
85
-
86
- # Returns the class for the macro, so "composed_of :balance, :class_name => 'Money'" would return the Money class and
87
- # "has_many :clients" would return the Client class.
88
- def klass() end
89
-
107
+
108
+ attr_reader :plural_name # :nodoc:
109
+
110
+ def initialize(macro, name, scope, options, active_record)
111
+ @macro = macro
112
+ @name = name
113
+ @scope = scope
114
+ @options = options
115
+ @active_record = active_record
116
+ @plural_name = active_record.pluralize_table_names ?
117
+ name.to_s.pluralize : name.to_s
118
+ end
119
+
120
+ # Returns the class for the macro.
121
+ #
122
+ # <tt>composed_of :balance, class_name: 'Money'</tt> returns the Money class
123
+ # <tt>has_many :clients</tt> returns the Client class
124
+ def klass
125
+ @klass ||= class_name.constantize
126
+ end
127
+
128
+ # Returns the class name for the macro.
129
+ #
130
+ # <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>'Money'</tt>
131
+ # <tt>has_many :clients</tt> returns <tt>'Client'</tt>
132
+ def class_name
133
+ @class_name ||= (options[:class_name] || derive_class_name).to_s
134
+ end
135
+
136
+ # Returns +true+ if +self+ and +other_aggregation+ have the same +name+ attribute, +active_record+ attribute,
137
+ # and +other_aggregation+ has an options hash assigned to it.
90
138
  def ==(other_aggregation)
91
- name == other_aggregation.name && other_aggregation.options && active_record == other_aggregation.active_record
139
+ super ||
140
+ other_aggregation.kind_of?(self.class) &&
141
+ name == other_aggregation.name &&
142
+ other_aggregation.options &&
143
+ active_record == other_aggregation.active_record
92
144
  end
145
+
146
+ private
147
+ def derive_class_name
148
+ name.to_s.camelize
149
+ end
93
150
  end
94
151
 
95
152
 
96
- # Holds all the meta-data about an aggregation as it was specified in the Active Record class.
153
+ # Holds all the meta-data about an aggregation as it was specified in the
154
+ # Active Record class.
97
155
  class AggregateReflection < MacroReflection #:nodoc:
98
- def klass
99
- Object.const_get(options[:class_name] || name_to_class_name(name.id2name))
156
+ def mapping
157
+ mapping = options[:mapping] || [name, name]
158
+ mapping.first.is_a?(Array) ? mapping : [mapping]
100
159
  end
101
-
102
- private
103
- def name_to_class_name(name)
104
- name.capitalize.gsub(/_(.)/) { |s| $1.capitalize }
105
- end
106
160
  end
107
161
 
108
- # Holds all the meta-data about an association as it was specified in the Active Record class.
162
+ # Holds all the meta-data about an association as it was specified in the
163
+ # Active Record class.
109
164
  class AssociationReflection < MacroReflection #:nodoc:
165
+ # Returns the target association's class.
166
+ #
167
+ # class Author < ActiveRecord::Base
168
+ # has_many :books
169
+ # end
170
+ #
171
+ # Author.reflect_on_association(:books).klass
172
+ # # => Book
173
+ #
174
+ # <b>Note:</b> Do not call +klass.new+ or +klass.create+ to instantiate
175
+ # a new association object. Use +build_association+ or +create_association+
176
+ # instead. This allows plugins to hook into association object creation.
110
177
  def klass
111
- active_record.send(:compute_type, (name_to_class_name(name.id2name)))
178
+ @klass ||= active_record.send(:compute_type, class_name)
179
+ end
180
+
181
+ def initialize(*args)
182
+ super
183
+ @collection = [:has_many, :has_and_belongs_to_many].include?(macro)
184
+ end
185
+
186
+ # Returns a new, unsaved instance of the associated class. +attributes+ will
187
+ # be passed to the class's constructor.
188
+ def build_association(attributes, &block)
189
+ klass.new(attributes, &block)
190
+ end
191
+
192
+ def table_name
193
+ @table_name ||= klass.table_name
194
+ end
195
+
196
+ def quoted_table_name
197
+ @quoted_table_name ||= klass.quoted_table_name
198
+ end
199
+
200
+ def join_table
201
+ @join_table ||= options[:join_table] || derive_join_table
202
+ end
203
+
204
+ def foreign_key
205
+ @foreign_key ||= options[:foreign_key] || derive_foreign_key
206
+ end
207
+
208
+ def foreign_type
209
+ @foreign_type ||= options[:foreign_type] || "#{name}_type"
210
+ end
211
+
212
+ def type
213
+ @type ||= options[:as] && "#{options[:as]}_type"
214
+ end
215
+
216
+ def primary_key_column
217
+ @primary_key_column ||= klass.columns.find { |c| c.name == klass.primary_key }
218
+ end
219
+
220
+ def association_foreign_key
221
+ @association_foreign_key ||= options[:association_foreign_key] || class_name.foreign_key
222
+ end
223
+
224
+ # klass option is necessary to support loading polymorphic associations
225
+ def association_primary_key(klass = nil)
226
+ options[:primary_key] || primary_key(klass || self.klass)
227
+ end
228
+
229
+ def active_record_primary_key
230
+ @active_record_primary_key ||= options[:primary_key] || primary_key(active_record)
231
+ end
232
+
233
+ def counter_cache_column
234
+ if options[:counter_cache] == true
235
+ "#{active_record.name.demodulize.underscore.pluralize}_count"
236
+ elsif options[:counter_cache]
237
+ options[:counter_cache].to_s
238
+ end
239
+ end
240
+
241
+ def columns(tbl_name)
242
+ @columns ||= klass.connection.columns(tbl_name)
243
+ end
244
+
245
+ def reset_column_information
246
+ @columns = nil
247
+ end
248
+
249
+ def check_validity!
250
+ check_validity_of_inverse!
251
+
252
+ if has_and_belongs_to_many? && association_foreign_key == foreign_key
253
+ raise HasAndBelongsToManyAssociationForeignKeyNeeded.new(self)
254
+ end
255
+ end
256
+
257
+ def check_validity_of_inverse!
258
+ unless options[:polymorphic]
259
+ if has_inverse? && inverse_of.nil?
260
+ raise InverseOfAssociationNotFoundError.new(self)
261
+ end
262
+ end
263
+ end
264
+
265
+ def through_reflection
266
+ nil
267
+ end
268
+
269
+ def source_reflection
270
+ nil
271
+ end
272
+
273
+ # A chain of reflections from this one back to the owner. For more see the explanation in
274
+ # ThroughReflection.
275
+ def chain
276
+ [self]
277
+ end
278
+
279
+ def nested?
280
+ false
281
+ end
282
+
283
+ # An array of arrays of scopes. Each item in the outside array corresponds to a reflection
284
+ # in the #chain.
285
+ def scope_chain
286
+ scope ? [[scope]] : [[]]
287
+ end
288
+
289
+ alias :source_macro :macro
290
+
291
+ def has_inverse?
292
+ @options[:inverse_of]
293
+ end
294
+
295
+ def inverse_of
296
+ if has_inverse?
297
+ @inverse_of ||= klass.reflect_on_association(options[:inverse_of])
298
+ end
299
+ end
300
+
301
+ def polymorphic_inverse_of(associated_class)
302
+ if has_inverse?
303
+ if inverse_relationship = associated_class.reflect_on_association(options[:inverse_of])
304
+ inverse_relationship
305
+ else
306
+ raise InverseOfAssociationNotFoundError.new(self, associated_class)
307
+ end
308
+ end
309
+ end
310
+
311
+ # Returns whether or not this association reflection is for a collection
312
+ # association. Returns +true+ if the +macro+ is either +has_many+ or
313
+ # +has_and_belongs_to_many+, +false+ otherwise.
314
+ def collection?
315
+ @collection
316
+ end
317
+
318
+ # Returns whether or not the association should be validated as part of
319
+ # the parent's validation.
320
+ #
321
+ # Unless you explicitly disable validation with
322
+ # <tt>validate: false</tt>, validation will take place when:
323
+ #
324
+ # * you explicitly enable validation; <tt>validate: true</tt>
325
+ # * you use autosave; <tt>autosave: true</tt>
326
+ # * the association is a +has_many+ association
327
+ def validate?
328
+ !options[:validate].nil? ? options[:validate] : (options[:autosave] == true || macro == :has_many)
329
+ end
330
+
331
+ # Returns +true+ if +self+ is a +belongs_to+ reflection.
332
+ def belongs_to?
333
+ macro == :belongs_to
334
+ end
335
+
336
+ def has_and_belongs_to_many?
337
+ macro == :has_and_belongs_to_many
338
+ end
339
+
340
+ def association_class
341
+ case macro
342
+ when :belongs_to
343
+ if options[:polymorphic]
344
+ Associations::BelongsToPolymorphicAssociation
345
+ else
346
+ Associations::BelongsToAssociation
347
+ end
348
+ when :has_and_belongs_to_many
349
+ Associations::HasAndBelongsToManyAssociation
350
+ when :has_many
351
+ if options[:through]
352
+ Associations::HasManyThroughAssociation
353
+ else
354
+ Associations::HasManyAssociation
355
+ end
356
+ when :has_one
357
+ if options[:through]
358
+ Associations::HasOneThroughAssociation
359
+ else
360
+ Associations::HasOneAssociation
361
+ end
362
+ end
363
+ end
364
+
365
+ def polymorphic?
366
+ options.key? :polymorphic
112
367
  end
113
368
 
114
369
  private
115
- def name_to_class_name(name)
116
- if name !~ /::/
117
- class_name = active_record.send(
118
- :type_name_with_module,
119
- (options[:class_name] || active_record.class_name(active_record.table_name_prefix + name + active_record.table_name_suffix))
120
- )
370
+ def derive_class_name
371
+ class_name = name.to_s.camelize
372
+ class_name = class_name.singularize if collection?
373
+ class_name
374
+ end
375
+
376
+ def derive_foreign_key
377
+ if belongs_to?
378
+ "#{name}_id"
379
+ elsif options[:as]
380
+ "#{options[:as]}_id"
381
+ else
382
+ active_record.name.foreign_key
383
+ end
384
+ end
385
+
386
+ def derive_join_table
387
+ [active_record.table_name, klass.table_name].sort.join("\0").gsub(/^(.*_)(.+)\0\1(.+)/, '\1\2_\3').gsub("\0", "_")
388
+ end
389
+
390
+ def primary_key(klass)
391
+ klass.primary_key || raise(UnknownPrimaryKey.new(klass))
392
+ end
393
+ end
394
+
395
+ # Holds all the meta-data about a :through association as it was specified
396
+ # in the Active Record class.
397
+ class ThroughReflection < AssociationReflection #:nodoc:
398
+ delegate :foreign_key, :foreign_type, :association_foreign_key,
399
+ :active_record_primary_key, :type, :to => :source_reflection
400
+
401
+ # Gets the source of the through reflection. It checks both a singularized
402
+ # and pluralized form for <tt>:belongs_to</tt> or <tt>:has_many</tt>.
403
+ #
404
+ # class Post < ActiveRecord::Base
405
+ # has_many :taggings
406
+ # has_many :tags, through: :taggings
407
+ # end
408
+ #
409
+ # class Tagging < ActiveRecord::Base
410
+ # belongs_to :post
411
+ # belongs_to :tag
412
+ # end
413
+ #
414
+ # tags_reflection = Post.reflect_on_association(:tags)
415
+ #
416
+ # taggings_reflection = tags_reflection.source_reflection
417
+ # # => <ActiveRecord::Reflection::AssociationReflection: @macro=:belongs_to, @name=:tag, @active_record=Tagging, @plural_name="tags">
418
+ #
419
+ def source_reflection
420
+ @source_reflection ||= source_reflection_names.collect { |name| through_reflection.klass.reflect_on_association(name) }.compact.first
421
+ end
422
+
423
+ # Returns the AssociationReflection object specified in the <tt>:through</tt> option
424
+ # of a HasManyThrough or HasOneThrough association.
425
+ #
426
+ # class Post < ActiveRecord::Base
427
+ # has_many :taggings
428
+ # has_many :tags, through: :taggings
429
+ # end
430
+ #
431
+ # tags_reflection = Post.reflect_on_association(:tags)
432
+ # taggings_reflection = tags_reflection.through_reflection
433
+ #
434
+ def through_reflection
435
+ @through_reflection ||= active_record.reflect_on_association(options[:through])
436
+ end
437
+
438
+ # Returns an array of reflections which are involved in this association. Each item in the
439
+ # array corresponds to a table which will be part of the query for this association.
440
+ #
441
+ # The chain is built by recursively calling #chain on the source reflection and the through
442
+ # reflection. The base case for the recursion is a normal association, which just returns
443
+ # [self] as its #chain.
444
+ #
445
+ # class Post < ActiveRecord::Base
446
+ # has_many :taggings
447
+ # has_many :tags, through: :taggings
448
+ # end
449
+ #
450
+ # tags_reflection = Post.reflect_on_association(:tags)
451
+ # tags_reflection.chain
452
+ # # => [<ActiveRecord::Reflection::ThroughReflection: @macro=:has_many, @name=:tags, @options={:through=>:taggings}, @active_record=Post>,
453
+ # <ActiveRecord::Reflection::AssociationReflection: @macro=:has_many, @name=:taggings, @options={}, @active_record=Post>]
454
+ #
455
+ def chain
456
+ @chain ||= begin
457
+ chain = source_reflection.chain + through_reflection.chain
458
+ chain[0] = self # Use self so we don't lose the information from :source_type
459
+ chain
460
+ end
461
+ end
462
+
463
+ # Consider the following example:
464
+ #
465
+ # class Person
466
+ # has_many :articles
467
+ # has_many :comment_tags, through: :articles
468
+ # end
469
+ #
470
+ # class Article
471
+ # has_many :comments
472
+ # has_many :comment_tags, through: :comments, source: :tags
473
+ # end
474
+ #
475
+ # class Comment
476
+ # has_many :tags
477
+ # end
478
+ #
479
+ # There may be scopes on Person.comment_tags, Article.comment_tags and/or Comment.tags,
480
+ # but only Comment.tags will be represented in the #chain. So this method creates an array
481
+ # of scopes corresponding to the chain.
482
+ def scope_chain
483
+ @scope_chain ||= begin
484
+ scope_chain = source_reflection.scope_chain.map(&:dup)
485
+
486
+ # Add to it the scope from this reflection (if any)
487
+ scope_chain.first << scope if scope
488
+
489
+ through_scope_chain = through_reflection.scope_chain
490
+
491
+ if options[:source_type]
492
+ through_scope_chain.first <<
493
+ through_reflection.klass.where(foreign_type => options[:source_type])
121
494
  end
122
- return class_name || name
495
+
496
+ # Recursively fill out the rest of the array from the through reflection
497
+ scope_chain + through_scope_chain
498
+ end
499
+ end
500
+
501
+ # The macro used by the source association
502
+ def source_macro
503
+ source_reflection.source_macro
504
+ end
505
+
506
+ # A through association is nested if there would be more than one join table
507
+ def nested?
508
+ chain.length > 2 || through_reflection.macro == :has_and_belongs_to_many
509
+ end
510
+
511
+ # We want to use the klass from this reflection, rather than just delegate straight to
512
+ # the source_reflection, because the source_reflection may be polymorphic. We still
513
+ # need to respect the source_reflection's :primary_key option, though.
514
+ def association_primary_key(klass = nil)
515
+ # Get the "actual" source reflection if the immediate source reflection has a
516
+ # source reflection itself
517
+ source_reflection = self.source_reflection
518
+ while source_reflection.source_reflection
519
+ source_reflection = source_reflection.source_reflection
520
+ end
521
+
522
+ source_reflection.options[:primary_key] || primary_key(klass || self.klass)
523
+ end
524
+
525
+ # Gets an array of possible <tt>:through</tt> source reflection names in both singular and plural form.
526
+ #
527
+ # class Post < ActiveRecord::Base
528
+ # has_many :taggings
529
+ # has_many :tags, through: :taggings
530
+ # end
531
+ #
532
+ # tags_reflection = Post.reflect_on_association(:tags)
533
+ # tags_reflection.source_reflection_names
534
+ # # => [:tag, :tags]
535
+ #
536
+ def source_reflection_names
537
+ @source_reflection_names ||= (options[:source] ? [options[:source]] : [name.to_s.singularize, name]).collect { |n| n.to_sym }
538
+ end
539
+
540
+ def source_options
541
+ source_reflection.options
542
+ end
543
+
544
+ def through_options
545
+ through_reflection.options
546
+ end
547
+
548
+ def check_validity!
549
+ if through_reflection.nil?
550
+ raise HasManyThroughAssociationNotFoundError.new(active_record.name, self)
551
+ end
552
+
553
+ if through_reflection.options[:polymorphic]
554
+ raise HasManyThroughAssociationPolymorphicThroughError.new(active_record.name, self)
555
+ end
556
+
557
+ if source_reflection.nil?
558
+ raise HasManyThroughSourceAssociationNotFoundError.new(self)
559
+ end
560
+
561
+ if options[:source_type] && source_reflection.options[:polymorphic].nil?
562
+ raise HasManyThroughAssociationPointlessSourceTypeError.new(active_record.name, self, source_reflection)
563
+ end
564
+
565
+ if source_reflection.options[:polymorphic] && options[:source_type].nil?
566
+ raise HasManyThroughAssociationPolymorphicSourceError.new(active_record.name, self, source_reflection)
567
+ end
568
+
569
+ if macro == :has_one && through_reflection.collection?
570
+ raise HasOneThroughCantAssociateThroughCollection.new(active_record.name, self, through_reflection)
571
+ end
572
+
573
+ check_validity_of_inverse!
574
+ end
575
+
576
+ private
577
+ def derive_class_name
578
+ # get the class_name of the belongs_to association of the through reflection
579
+ options[:source_type] || source_reflection.class_name
123
580
  end
124
581
  end
125
582
  end
126
- end
583
+ end