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,174 @@
1
+ module ActiveRecord
2
+ module Associations
3
+ class JoinDependency # :nodoc:
4
+ class JoinAssociation < JoinPart # :nodoc:
5
+ include JoinHelper
6
+
7
+ # The reflection of the association represented
8
+ attr_reader :reflection
9
+
10
+ # The JoinDependency object which this JoinAssociation exists within. This is mainly
11
+ # relevant for generating aliases which do not conflict with other joins which are
12
+ # part of the query.
13
+ attr_reader :join_dependency
14
+
15
+ # A JoinBase instance representing the active record we are joining onto.
16
+ # (So in Author.has_many :posts, the Author would be that base record.)
17
+ attr_reader :parent
18
+
19
+ # What type of join will be generated, either Arel::InnerJoin (default) or Arel::OuterJoin
20
+ attr_accessor :join_type
21
+
22
+ # These implement abstract methods from the superclass
23
+ attr_reader :aliased_prefix
24
+
25
+ attr_reader :tables
26
+
27
+ delegate :options, :through_reflection, :source_reflection, :chain, :to => :reflection
28
+ delegate :table, :table_name, :to => :parent, :prefix => :parent
29
+ delegate :alias_tracker, :to => :join_dependency
30
+
31
+ alias :alias_suffix :parent_table_name
32
+
33
+ def initialize(reflection, join_dependency, parent = nil)
34
+ reflection.check_validity!
35
+
36
+ if reflection.options[:polymorphic]
37
+ raise EagerLoadPolymorphicError.new(reflection)
38
+ end
39
+
40
+ super(reflection.klass)
41
+
42
+ @reflection = reflection
43
+ @join_dependency = join_dependency
44
+ @parent = parent
45
+ @join_type = Arel::InnerJoin
46
+ @aliased_prefix = "t#{ join_dependency.join_parts.size }"
47
+ @tables = construct_tables.reverse
48
+ end
49
+
50
+ def ==(other)
51
+ other.class == self.class &&
52
+ other.reflection == reflection &&
53
+ other.parent == parent
54
+ end
55
+
56
+ def find_parent_in(other_join_dependency)
57
+ other_join_dependency.join_parts.detect do |join_part|
58
+ case parent
59
+ when JoinBase
60
+ parent.base_klass == join_part.base_klass
61
+ else
62
+ parent == join_part
63
+ end
64
+ end
65
+ end
66
+
67
+ def join_to(manager)
68
+ tables = @tables.dup
69
+ foreign_table = parent_table
70
+ foreign_klass = parent.base_klass
71
+
72
+ # The chain starts with the target table, but we want to end with it here (makes
73
+ # more sense in this context), so we reverse
74
+ chain.reverse.each_with_index do |reflection, i|
75
+ table = tables.shift
76
+
77
+ case reflection.source_macro
78
+ when :belongs_to
79
+ key = reflection.association_primary_key
80
+ foreign_key = reflection.foreign_key
81
+ when :has_and_belongs_to_many
82
+ # Join the join table first...
83
+ manager.from(join(
84
+ table,
85
+ table[reflection.foreign_key].
86
+ eq(foreign_table[reflection.active_record_primary_key])
87
+ ))
88
+
89
+ foreign_table, table = table, tables.shift
90
+
91
+ key = reflection.association_primary_key
92
+ foreign_key = reflection.association_foreign_key
93
+ else
94
+ key = reflection.foreign_key
95
+ foreign_key = reflection.active_record_primary_key
96
+ end
97
+
98
+ constraint = build_constraint(reflection, table, key, foreign_table, foreign_key)
99
+
100
+ scope_chain_items = scope_chain[i]
101
+
102
+ if reflection.type
103
+ scope_chain_items += [
104
+ ActiveRecord::Relation.new(reflection.klass, table)
105
+ .where(reflection.type => foreign_klass.base_class.name)
106
+ ]
107
+ end
108
+
109
+ scope_chain_items.each do |item|
110
+ unless item.is_a?(Relation)
111
+ item = ActiveRecord::Relation.new(reflection.klass, table).instance_exec(self, &item)
112
+ end
113
+
114
+ constraint = constraint.and(item.arel.constraints) unless item.arel.constraints.empty?
115
+ end
116
+
117
+ manager.from(join(table, constraint))
118
+
119
+ # The current table in this iteration becomes the foreign table in the next
120
+ foreign_table, foreign_klass = table, reflection.klass
121
+ end
122
+
123
+ manager
124
+ end
125
+
126
+ # Builds equality condition.
127
+ #
128
+ # Example:
129
+ #
130
+ # class Physician < ActiveRecord::Base
131
+ # has_many :appointments
132
+ # end
133
+ #
134
+ # If I execute `Physician.joins(:appointments).to_a` then
135
+ # reflection #=> #<ActiveRecord::Reflection::AssociationReflection @macro=:has_many ...>
136
+ # table #=> #<Arel::Table @name="appointments" ...>
137
+ # key #=> physician_id
138
+ # foreign_table #=> #<Arel::Table @name="physicians" ...>
139
+ # foreign_key #=> id
140
+ #
141
+ def build_constraint(reflection, table, key, foreign_table, foreign_key)
142
+ constraint = table[key].eq(foreign_table[foreign_key])
143
+
144
+ if reflection.klass.finder_needs_type_condition?
145
+ constraint = table.create_and([
146
+ constraint,
147
+ reflection.klass.send(:type_condition, table)
148
+ ])
149
+ end
150
+
151
+ constraint
152
+ end
153
+
154
+ def join_relation(joining_relation)
155
+ self.join_type = Arel::OuterJoin
156
+ joining_relation.joins(self)
157
+ end
158
+
159
+ def table
160
+ tables.last
161
+ end
162
+
163
+ def aliased_table_name
164
+ table.table_alias || table.name
165
+ end
166
+
167
+ def scope_chain
168
+ @scope_chain ||= reflection.scope_chain.reverse
169
+ end
170
+
171
+ end
172
+ end
173
+ end
174
+ end
@@ -0,0 +1,24 @@
1
+ module ActiveRecord
2
+ module Associations
3
+ class JoinDependency # :nodoc:
4
+ class JoinBase < JoinPart # :nodoc:
5
+ def ==(other)
6
+ other.class == self.class &&
7
+ other.base_klass == base_klass
8
+ end
9
+
10
+ def aliased_prefix
11
+ "t0"
12
+ end
13
+
14
+ def table
15
+ Arel::Table.new(table_name, arel_engine)
16
+ end
17
+
18
+ def aliased_table_name
19
+ base_klass.table_name
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,78 @@
1
+ module ActiveRecord
2
+ module Associations
3
+ class JoinDependency # :nodoc:
4
+ # A JoinPart represents a part of a JoinDependency. It is inherited
5
+ # by JoinBase and JoinAssociation. A JoinBase represents the Active Record which
6
+ # everything else is being joined onto. A JoinAssociation represents an association which
7
+ # is joining to the base. A JoinAssociation may result in more than one actual join
8
+ # operations (for example a has_and_belongs_to_many JoinAssociation would result in
9
+ # two; one for the join table and one for the target table).
10
+ class JoinPart # :nodoc:
11
+ # The Active Record class which this join part is associated 'about'; for a JoinBase
12
+ # this is the actual base model, for a JoinAssociation this is the target model of the
13
+ # association.
14
+ attr_reader :base_klass
15
+
16
+ delegate :table_name, :column_names, :primary_key, :reflections, :arel_engine, :to => :base_klass
17
+
18
+ def initialize(base_klass)
19
+ @base_klass = base_klass
20
+ @cached_record = {}
21
+ @column_names_with_alias = nil
22
+ end
23
+
24
+ def aliased_table
25
+ Arel::Nodes::TableAlias.new table, aliased_table_name
26
+ end
27
+
28
+ def ==(other)
29
+ raise NotImplementedError
30
+ end
31
+
32
+ # An Arel::Table for the active_record
33
+ def table
34
+ raise NotImplementedError
35
+ end
36
+
37
+ # The prefix to be used when aliasing columns in the active_record's table
38
+ def aliased_prefix
39
+ raise NotImplementedError
40
+ end
41
+
42
+ # The alias for the active_record's table
43
+ def aliased_table_name
44
+ raise NotImplementedError
45
+ end
46
+
47
+ # The alias for the primary key of the active_record's table
48
+ def aliased_primary_key
49
+ "#{aliased_prefix}_r0"
50
+ end
51
+
52
+ # An array of [column_name, alias] pairs for the table
53
+ def column_names_with_alias
54
+ unless @column_names_with_alias
55
+ @column_names_with_alias = []
56
+
57
+ ([primary_key] + (column_names - [primary_key])).compact.each_with_index do |column_name, i|
58
+ @column_names_with_alias << [column_name, "#{aliased_prefix}_r#{i}"]
59
+ end
60
+ end
61
+ @column_names_with_alias
62
+ end
63
+
64
+ def extract_record(row)
65
+ Hash[column_names_with_alias.map{|cn, an| [cn, row[an]]}]
66
+ end
67
+
68
+ def record_id(row)
69
+ row[aliased_primary_key]
70
+ end
71
+
72
+ def instantiate(row)
73
+ @cached_record[record_id(row)] ||= base_klass.instantiate(extract_record(row))
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,235 @@
1
+ module ActiveRecord
2
+ module Associations
3
+ class JoinDependency # :nodoc:
4
+ autoload :JoinPart, 'active_record/associations/join_dependency/join_part'
5
+ autoload :JoinBase, 'active_record/associations/join_dependency/join_base'
6
+ autoload :JoinAssociation, 'active_record/associations/join_dependency/join_association'
7
+
8
+ attr_reader :join_parts, :reflections, :alias_tracker, :base_klass
9
+
10
+ # base is the base class on which operation is taking place.
11
+ # associations is the list of associations which are joined using hash, symbol or array.
12
+ # joins is the list of all string join commnads and arel nodes.
13
+ #
14
+ # Example :
15
+ #
16
+ # class Physician < ActiveRecord::Base
17
+ # has_many :appointments
18
+ # has_many :patients, through: :appointments
19
+ # end
20
+ #
21
+ # If I execute `@physician.patients.to_a` then
22
+ # base #=> Physician
23
+ # associations #=> []
24
+ # joins #=> [#<Arel::Nodes::InnerJoin: ...]
25
+ #
26
+ # However if I execute `Physician.joins(:appointments).to_a` then
27
+ # base #=> Physician
28
+ # associations #=> [:appointments]
29
+ # joins #=> []
30
+ #
31
+ def initialize(base, associations, joins)
32
+ @base_klass = base
33
+ @table_joins = joins
34
+ @join_parts = [JoinBase.new(base)]
35
+ @associations = {}
36
+ @reflections = []
37
+ @alias_tracker = AliasTracker.new(base.connection, joins)
38
+ @alias_tracker.aliased_name_for(base.table_name) # Updates the count for base.table_name to 1
39
+ build(associations)
40
+ end
41
+
42
+ def graft(*associations)
43
+ associations.each do |association|
44
+ join_associations.detect {|a| association == a} ||
45
+ build(association.reflection.name, association.find_parent_in(self) || join_base, association.join_type)
46
+ end
47
+ self
48
+ end
49
+
50
+ def join_associations
51
+ join_parts.last(join_parts.length - 1)
52
+ end
53
+
54
+ def join_base
55
+ join_parts.first
56
+ end
57
+
58
+ def columns
59
+ join_parts.collect { |join_part|
60
+ table = join_part.aliased_table
61
+ join_part.column_names_with_alias.collect{ |column_name, aliased_name|
62
+ table[column_name].as Arel.sql(aliased_name)
63
+ }
64
+ }.flatten
65
+ end
66
+
67
+ def instantiate(rows)
68
+ primary_key = join_base.aliased_primary_key
69
+ parents = {}
70
+
71
+ records = rows.map { |model|
72
+ primary_id = model[primary_key]
73
+ parent = parents[primary_id] ||= join_base.instantiate(model)
74
+ construct(parent, @associations, join_associations, model)
75
+ parent
76
+ }.uniq
77
+
78
+ remove_duplicate_results!(base_klass, records, @associations)
79
+ records
80
+ end
81
+
82
+ protected
83
+
84
+ def remove_duplicate_results!(base, records, associations)
85
+ case associations
86
+ when Symbol, String
87
+ reflection = base.reflections[associations]
88
+ remove_uniq_by_reflection(reflection, records)
89
+ when Array
90
+ associations.each do |association|
91
+ remove_duplicate_results!(base, records, association)
92
+ end
93
+ when Hash
94
+ associations.each_key do |name|
95
+ reflection = base.reflections[name]
96
+ remove_uniq_by_reflection(reflection, records)
97
+
98
+ parent_records = []
99
+ records.each do |record|
100
+ if descendant = record.send(reflection.name)
101
+ if reflection.collection?
102
+ parent_records.concat descendant.target.uniq
103
+ else
104
+ parent_records << descendant
105
+ end
106
+ end
107
+ end
108
+
109
+ remove_duplicate_results!(reflection.klass, parent_records, associations[name]) unless parent_records.empty?
110
+ end
111
+ end
112
+ end
113
+
114
+ def cache_joined_association(association)
115
+ associations = []
116
+ parent = association.parent
117
+ while parent != join_base
118
+ associations.unshift(parent.reflection.name)
119
+ parent = parent.parent
120
+ end
121
+ ref = @associations
122
+ associations.each do |key|
123
+ ref = ref[key]
124
+ end
125
+ ref[association.reflection.name] ||= {}
126
+ end
127
+
128
+ def build(associations, parent = nil, join_type = Arel::InnerJoin)
129
+ parent ||= join_parts.last
130
+ case associations
131
+ when Symbol, String
132
+ reflection = parent.reflections[associations.intern] or
133
+ raise ConfigurationError, "Association named '#{ associations }' was not found on #{ parent.base_klass.name }; perhaps you misspelled it?"
134
+ unless join_association = find_join_association(reflection, parent)
135
+ @reflections << reflection
136
+ join_association = build_join_association(reflection, parent)
137
+ join_association.join_type = join_type
138
+ @join_parts << join_association
139
+ cache_joined_association(join_association)
140
+ end
141
+ join_association
142
+ when Array
143
+ associations.each do |association|
144
+ build(association, parent, join_type)
145
+ end
146
+ when Hash
147
+ associations.keys.sort_by { |a| a.to_s }.each do |name|
148
+ join_association = build(name, parent, join_type)
149
+ build(associations[name], join_association, join_type)
150
+ end
151
+ else
152
+ raise ConfigurationError, associations.inspect
153
+ end
154
+ end
155
+
156
+ def find_join_association(name_or_reflection, parent)
157
+ if String === name_or_reflection
158
+ name_or_reflection = name_or_reflection.to_sym
159
+ end
160
+
161
+ join_associations.detect { |j|
162
+ j.reflection == name_or_reflection && j.parent == parent
163
+ }
164
+ end
165
+
166
+ def remove_uniq_by_reflection(reflection, records)
167
+ if reflection && reflection.collection?
168
+ records.each { |record| record.send(reflection.name).target.uniq! }
169
+ end
170
+ end
171
+
172
+ def build_join_association(reflection, parent)
173
+ JoinAssociation.new(reflection, self, parent)
174
+ end
175
+
176
+ def construct(parent, associations, join_parts, row)
177
+ case associations
178
+ when Symbol, String
179
+ name = associations.to_s
180
+
181
+ join_part = join_parts.detect { |j|
182
+ j.reflection.name.to_s == name &&
183
+ j.parent_table_name == parent.class.table_name }
184
+
185
+ raise(ConfigurationError, "No such association") unless join_part
186
+
187
+ join_parts.delete(join_part)
188
+ construct_association(parent, join_part, row)
189
+ when Array
190
+ associations.each do |association|
191
+ construct(parent, association, join_parts, row)
192
+ end
193
+ when Hash
194
+ associations.sort_by { |k,_| k.to_s }.each do |association_name, assoc|
195
+ association = construct(parent, association_name, join_parts, row)
196
+ construct(association, assoc, join_parts, row) if association
197
+ end
198
+ else
199
+ raise ConfigurationError, associations.inspect
200
+ end
201
+ end
202
+
203
+ def construct_association(record, join_part, row)
204
+ return if record.id.to_s != join_part.parent.record_id(row).to_s
205
+
206
+ macro = join_part.reflection.macro
207
+ if macro == :has_one
208
+ return record.association(join_part.reflection.name).target if record.association_cache.key?(join_part.reflection.name)
209
+ association = join_part.instantiate(row) unless row[join_part.aliased_primary_key].nil?
210
+ set_target_and_inverse(join_part, association, record)
211
+ else
212
+ association = join_part.instantiate(row) unless row[join_part.aliased_primary_key].nil?
213
+ case macro
214
+ when :has_many, :has_and_belongs_to_many
215
+ other = record.association(join_part.reflection.name)
216
+ other.loaded!
217
+ other.target.push(association) if association
218
+ other.set_inverse_instance(association)
219
+ when :belongs_to
220
+ set_target_and_inverse(join_part, association, record)
221
+ else
222
+ raise ConfigurationError, "unknown macro: #{join_part.reflection.macro}"
223
+ end
224
+ end
225
+ association
226
+ end
227
+
228
+ def set_target_and_inverse(join_part, association, record)
229
+ other = record.association(join_part.reflection.name)
230
+ other.target = association
231
+ other.set_inverse_instance(association)
232
+ end
233
+ end
234
+ end
235
+ end
@@ -0,0 +1,45 @@
1
+ module ActiveRecord
2
+ module Associations
3
+ # Helper class module which gets mixed into JoinDependency::JoinAssociation and AssociationScope
4
+ module JoinHelper #:nodoc:
5
+
6
+ def join_type
7
+ Arel::InnerJoin
8
+ end
9
+
10
+ private
11
+
12
+ def construct_tables
13
+ tables = []
14
+ chain.each do |reflection|
15
+ tables << alias_tracker.aliased_table_for(
16
+ table_name_for(reflection),
17
+ table_alias_for(reflection, reflection != self.reflection)
18
+ )
19
+
20
+ if reflection.source_macro == :has_and_belongs_to_many
21
+ tables << alias_tracker.aliased_table_for(
22
+ (reflection.source_reflection || reflection).join_table,
23
+ table_alias_for(reflection, true)
24
+ )
25
+ end
26
+ end
27
+ tables
28
+ end
29
+
30
+ def table_name_for(reflection)
31
+ reflection.table_name
32
+ end
33
+
34
+ def table_alias_for(reflection, join = false)
35
+ name = "#{reflection.plural_name}_#{alias_suffix}"
36
+ name << "_join" if join
37
+ name
38
+ end
39
+
40
+ def join(table, constraint)
41
+ table.create_join(table, table.create_on(constraint), join_type)
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,121 @@
1
+ module ActiveRecord
2
+ module Associations
3
+ class Preloader
4
+ class Association #:nodoc:
5
+ attr_reader :owners, :reflection, :preload_scope, :model, :klass
6
+
7
+ def initialize(klass, owners, reflection, preload_scope)
8
+ @klass = klass
9
+ @owners = owners
10
+ @reflection = reflection
11
+ @preload_scope = preload_scope
12
+ @model = owners.first && owners.first.class
13
+ @scope = nil
14
+ @owners_by_key = nil
15
+ end
16
+
17
+ def run
18
+ unless owners.first.association(reflection.name).loaded?
19
+ preload
20
+ end
21
+ end
22
+
23
+ def preload
24
+ raise NotImplementedError
25
+ end
26
+
27
+ def scope
28
+ @scope ||= build_scope
29
+ end
30
+
31
+ def records_for(ids)
32
+ scope.where(association_key.in(ids))
33
+ end
34
+
35
+ def table
36
+ klass.arel_table
37
+ end
38
+
39
+ # The name of the key on the associated records
40
+ def association_key_name
41
+ raise NotImplementedError
42
+ end
43
+
44
+ # This is overridden by HABTM as the condition should be on the foreign_key column in
45
+ # the join table
46
+ def association_key
47
+ table[association_key_name]
48
+ end
49
+
50
+ # The name of the key on the model which declares the association
51
+ def owner_key_name
52
+ raise NotImplementedError
53
+ end
54
+
55
+ # We're converting to a string here because postgres will return the aliased association
56
+ # key in a habtm as a string (for whatever reason)
57
+ def owners_by_key
58
+ @owners_by_key ||= owners.group_by do |owner|
59
+ key = owner[owner_key_name]
60
+ key && key.to_s
61
+ end
62
+ end
63
+
64
+ def options
65
+ reflection.options
66
+ end
67
+
68
+ private
69
+
70
+ def associated_records_by_owner
71
+ owners_map = owners_by_key
72
+ owner_keys = owners_map.keys.compact
73
+
74
+ if klass.nil? || owner_keys.empty?
75
+ records = []
76
+ else
77
+ # Some databases impose a limit on the number of ids in a list (in Oracle it's 1000)
78
+ # Make several smaller queries if necessary or make one query if the adapter supports it
79
+ sliced = owner_keys.each_slice(klass.connection.in_clause_length || owner_keys.size)
80
+ records = sliced.map { |slice| records_for(slice).to_a }.flatten
81
+ end
82
+
83
+ # Each record may have multiple owners, and vice-versa
84
+ records_by_owner = Hash[owners.map { |owner| [owner, []] }]
85
+ records.each do |record|
86
+ owner_key = record[association_key_name].to_s
87
+
88
+ owners_map[owner_key].each do |owner|
89
+ records_by_owner[owner] << record
90
+ end
91
+ end
92
+ records_by_owner
93
+ end
94
+
95
+ def reflection_scope
96
+ @reflection_scope ||= reflection.scope ? klass.unscoped.instance_exec(nil, &reflection.scope) : klass.unscoped
97
+ end
98
+
99
+ def build_scope
100
+ scope = klass.unscoped
101
+ scope.default_scoped = true
102
+
103
+ values = reflection_scope.values
104
+ preload_values = preload_scope.values
105
+
106
+ scope.where_values = Array(values[:where]) + Array(preload_values[:where])
107
+ scope.references_values = Array(values[:references]) + Array(preload_values[:references])
108
+
109
+ scope.select! preload_values[:select] || values[:select] || table[Arel.star]
110
+ scope.includes! preload_values[:includes] || values[:includes]
111
+
112
+ if options[:as]
113
+ scope.where!(klass.table_name => { reflection.type => model.base_class.sti_name })
114
+ end
115
+
116
+ scope
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,17 @@
1
+ module ActiveRecord
2
+ module Associations
3
+ class Preloader
4
+ class BelongsTo < SingularAssociation #:nodoc:
5
+
6
+ def association_key_name
7
+ reflection.options[:primary_key] || klass && klass.primary_key
8
+ end
9
+
10
+ def owner_key_name
11
+ reflection.foreign_key
12
+ end
13
+
14
+ end
15
+ end
16
+ end
17
+ end