activerecord 3.2.19 → 5.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 (264) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +1715 -604
  3. data/MIT-LICENSE +2 -2
  4. data/README.rdoc +40 -45
  5. data/examples/performance.rb +33 -22
  6. data/examples/simple.rb +3 -4
  7. data/lib/active_record/aggregations.rb +76 -51
  8. data/lib/active_record/association_relation.rb +35 -0
  9. data/lib/active_record/associations/alias_tracker.rb +54 -40
  10. data/lib/active_record/associations/association.rb +76 -56
  11. data/lib/active_record/associations/association_scope.rb +125 -93
  12. data/lib/active_record/associations/belongs_to_association.rb +57 -28
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +7 -2
  14. data/lib/active_record/associations/builder/association.rb +120 -32
  15. data/lib/active_record/associations/builder/belongs_to.rb +115 -62
  16. data/lib/active_record/associations/builder/collection_association.rb +61 -53
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +117 -43
  18. data/lib/active_record/associations/builder/has_many.rb +9 -65
  19. data/lib/active_record/associations/builder/has_one.rb +18 -52
  20. data/lib/active_record/associations/builder/singular_association.rb +18 -19
  21. data/lib/active_record/associations/collection_association.rb +268 -186
  22. data/lib/active_record/associations/collection_proxy.rb +1003 -63
  23. data/lib/active_record/associations/foreign_association.rb +11 -0
  24. data/lib/active_record/associations/has_many_association.rb +81 -41
  25. data/lib/active_record/associations/has_many_through_association.rb +76 -55
  26. data/lib/active_record/associations/has_one_association.rb +51 -21
  27. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  28. data/lib/active_record/associations/join_dependency/join_association.rb +83 -108
  29. data/lib/active_record/associations/join_dependency/join_base.rb +7 -9
  30. data/lib/active_record/associations/join_dependency/join_part.rb +30 -37
  31. data/lib/active_record/associations/join_dependency.rb +239 -155
  32. data/lib/active_record/associations/preloader/association.rb +97 -62
  33. data/lib/active_record/associations/preloader/collection_association.rb +2 -8
  34. data/lib/active_record/associations/preloader/has_many_through.rb +7 -3
  35. data/lib/active_record/associations/preloader/has_one.rb +0 -8
  36. data/lib/active_record/associations/preloader/singular_association.rb +3 -3
  37. data/lib/active_record/associations/preloader/through_association.rb +75 -33
  38. data/lib/active_record/associations/preloader.rb +111 -79
  39. data/lib/active_record/associations/singular_association.rb +35 -13
  40. data/lib/active_record/associations/through_association.rb +41 -19
  41. data/lib/active_record/associations.rb +727 -501
  42. data/lib/active_record/attribute/user_provided_default.rb +28 -0
  43. data/lib/active_record/attribute.rb +213 -0
  44. data/lib/active_record/attribute_assignment.rb +32 -162
  45. data/lib/active_record/attribute_decorators.rb +67 -0
  46. data/lib/active_record/attribute_methods/before_type_cast.rb +52 -7
  47. data/lib/active_record/attribute_methods/dirty.rb +101 -61
  48. data/lib/active_record/attribute_methods/primary_key.rb +50 -36
  49. data/lib/active_record/attribute_methods/query.rb +7 -6
  50. data/lib/active_record/attribute_methods/read.rb +56 -117
  51. data/lib/active_record/attribute_methods/serialization.rb +43 -96
  52. data/lib/active_record/attribute_methods/time_zone_conversion.rb +93 -42
  53. data/lib/active_record/attribute_methods/write.rb +34 -45
  54. data/lib/active_record/attribute_methods.rb +333 -144
  55. data/lib/active_record/attribute_mutation_tracker.rb +70 -0
  56. data/lib/active_record/attribute_set/builder.rb +108 -0
  57. data/lib/active_record/attribute_set.rb +108 -0
  58. data/lib/active_record/attributes.rb +265 -0
  59. data/lib/active_record/autosave_association.rb +285 -223
  60. data/lib/active_record/base.rb +95 -490
  61. data/lib/active_record/callbacks.rb +95 -61
  62. data/lib/active_record/coders/json.rb +13 -0
  63. data/lib/active_record/coders/yaml_column.rb +28 -19
  64. data/lib/active_record/collection_cache_key.rb +40 -0
  65. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +724 -277
  66. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  67. data/lib/active_record/connection_adapters/abstract/database_statements.rb +199 -192
  68. data/lib/active_record/connection_adapters/abstract/query_cache.rb +31 -26
  69. data/lib/active_record/connection_adapters/abstract/quoting.rb +140 -57
  70. data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
  71. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +147 -0
  72. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +419 -276
  73. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +105 -0
  74. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +963 -276
  75. data/lib/active_record/connection_adapters/abstract/transaction.rb +232 -0
  76. data/lib/active_record/connection_adapters/abstract_adapter.rb +397 -106
  77. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +643 -342
  78. data/lib/active_record/connection_adapters/column.rb +30 -259
  79. data/lib/active_record/connection_adapters/connection_specification.rb +263 -0
  80. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +22 -0
  81. data/lib/active_record/connection_adapters/mysql/column.rb +50 -0
  82. data/lib/active_record/connection_adapters/mysql/database_statements.rb +125 -0
  83. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +70 -0
  84. data/lib/active_record/connection_adapters/mysql/quoting.rb +51 -0
  85. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +67 -0
  86. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +93 -0
  87. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +54 -0
  88. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +32 -0
  89. data/lib/active_record/connection_adapters/mysql2_adapter.rb +47 -196
  90. data/lib/active_record/connection_adapters/postgresql/column.rb +15 -0
  91. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +170 -0
  92. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +42 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +70 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +15 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +48 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +21 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +19 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +10 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +39 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb +50 -0
  108. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +93 -0
  109. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +15 -0
  110. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +109 -0
  111. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
  112. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  113. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  114. data/lib/active_record/connection_adapters/postgresql/oid.rb +31 -0
  115. data/lib/active_record/connection_adapters/postgresql/quoting.rb +116 -0
  116. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +49 -0
  117. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +180 -0
  118. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +47 -0
  119. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +682 -0
  120. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +35 -0
  121. data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
  122. data/lib/active_record/connection_adapters/postgresql_adapter.rb +558 -1039
  123. data/lib/active_record/connection_adapters/schema_cache.rb +74 -36
  124. data/lib/active_record/connection_adapters/sql_type_metadata.rb +32 -0
  125. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +19 -0
  126. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +48 -0
  127. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  128. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +538 -24
  129. data/lib/active_record/connection_adapters/statement_pool.rb +31 -12
  130. data/lib/active_record/connection_handling.rb +155 -0
  131. data/lib/active_record/core.rb +561 -0
  132. data/lib/active_record/counter_cache.rb +146 -105
  133. data/lib/active_record/dynamic_matchers.rb +101 -64
  134. data/lib/active_record/enum.rb +234 -0
  135. data/lib/active_record/errors.rb +153 -56
  136. data/lib/active_record/explain.rb +15 -63
  137. data/lib/active_record/explain_registry.rb +30 -0
  138. data/lib/active_record/explain_subscriber.rb +10 -6
  139. data/lib/active_record/fixture_set/file.rb +77 -0
  140. data/lib/active_record/fixtures.rb +355 -232
  141. data/lib/active_record/gem_version.rb +15 -0
  142. data/lib/active_record/inheritance.rb +144 -79
  143. data/lib/active_record/integration.rb +66 -13
  144. data/lib/active_record/internal_metadata.rb +56 -0
  145. data/lib/active_record/legacy_yaml_adapter.rb +46 -0
  146. data/lib/active_record/locale/en.yml +9 -1
  147. data/lib/active_record/locking/optimistic.rb +77 -56
  148. data/lib/active_record/locking/pessimistic.rb +6 -6
  149. data/lib/active_record/log_subscriber.rb +53 -28
  150. data/lib/active_record/migration/command_recorder.rb +166 -33
  151. data/lib/active_record/migration/compatibility.rb +126 -0
  152. data/lib/active_record/migration/join_table.rb +15 -0
  153. data/lib/active_record/migration.rb +792 -264
  154. data/lib/active_record/model_schema.rb +192 -130
  155. data/lib/active_record/nested_attributes.rb +238 -145
  156. data/lib/active_record/no_touching.rb +52 -0
  157. data/lib/active_record/null_relation.rb +89 -0
  158. data/lib/active_record/persistence.rb +357 -157
  159. data/lib/active_record/query_cache.rb +22 -43
  160. data/lib/active_record/querying.rb +34 -23
  161. data/lib/active_record/railtie.rb +88 -48
  162. data/lib/active_record/railties/console_sandbox.rb +3 -4
  163. data/lib/active_record/railties/controller_runtime.rb +5 -4
  164. data/lib/active_record/railties/databases.rake +170 -422
  165. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  166. data/lib/active_record/readonly_attributes.rb +2 -5
  167. data/lib/active_record/reflection.rb +715 -189
  168. data/lib/active_record/relation/batches/batch_enumerator.rb +67 -0
  169. data/lib/active_record/relation/batches.rb +203 -50
  170. data/lib/active_record/relation/calculations.rb +203 -194
  171. data/lib/active_record/relation/delegation.rb +103 -25
  172. data/lib/active_record/relation/finder_methods.rb +457 -261
  173. data/lib/active_record/relation/from_clause.rb +32 -0
  174. data/lib/active_record/relation/merger.rb +167 -0
  175. data/lib/active_record/relation/predicate_builder/array_handler.rb +43 -0
  176. data/lib/active_record/relation/predicate_builder/association_query_handler.rb +88 -0
  177. data/lib/active_record/relation/predicate_builder/base_handler.rb +17 -0
  178. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +17 -0
  179. data/lib/active_record/relation/predicate_builder/class_handler.rb +27 -0
  180. data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +57 -0
  181. data/lib/active_record/relation/predicate_builder/range_handler.rb +33 -0
  182. data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
  183. data/lib/active_record/relation/predicate_builder.rb +153 -48
  184. data/lib/active_record/relation/query_attribute.rb +19 -0
  185. data/lib/active_record/relation/query_methods.rb +1019 -194
  186. data/lib/active_record/relation/record_fetch_warning.rb +49 -0
  187. data/lib/active_record/relation/spawn_methods.rb +46 -150
  188. data/lib/active_record/relation/where_clause.rb +174 -0
  189. data/lib/active_record/relation/where_clause_factory.rb +38 -0
  190. data/lib/active_record/relation.rb +450 -245
  191. data/lib/active_record/result.rb +104 -12
  192. data/lib/active_record/runtime_registry.rb +22 -0
  193. data/lib/active_record/sanitization.rb +120 -94
  194. data/lib/active_record/schema.rb +28 -18
  195. data/lib/active_record/schema_dumper.rb +141 -74
  196. data/lib/active_record/schema_migration.rb +50 -0
  197. data/lib/active_record/scoping/default.rb +64 -57
  198. data/lib/active_record/scoping/named.rb +93 -108
  199. data/lib/active_record/scoping.rb +73 -121
  200. data/lib/active_record/secure_token.rb +38 -0
  201. data/lib/active_record/serialization.rb +7 -5
  202. data/lib/active_record/statement_cache.rb +113 -0
  203. data/lib/active_record/store.rb +173 -15
  204. data/lib/active_record/suppressor.rb +58 -0
  205. data/lib/active_record/table_metadata.rb +68 -0
  206. data/lib/active_record/tasks/database_tasks.rb +313 -0
  207. data/lib/active_record/tasks/mysql_database_tasks.rb +151 -0
  208. data/lib/active_record/tasks/postgresql_database_tasks.rb +110 -0
  209. data/lib/active_record/tasks/sqlite_database_tasks.rb +59 -0
  210. data/lib/active_record/timestamp.rb +42 -24
  211. data/lib/active_record/touch_later.rb +58 -0
  212. data/lib/active_record/transactions.rb +233 -105
  213. data/lib/active_record/type/adapter_specific_registry.rb +130 -0
  214. data/lib/active_record/type/date.rb +7 -0
  215. data/lib/active_record/type/date_time.rb +7 -0
  216. data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
  217. data/lib/active_record/type/internal/abstract_json.rb +29 -0
  218. data/lib/active_record/type/internal/timezone.rb +15 -0
  219. data/lib/active_record/type/serialized.rb +63 -0
  220. data/lib/active_record/type/time.rb +20 -0
  221. data/lib/active_record/type/type_map.rb +64 -0
  222. data/lib/active_record/type.rb +72 -0
  223. data/lib/active_record/type_caster/connection.rb +29 -0
  224. data/lib/active_record/type_caster/map.rb +19 -0
  225. data/lib/active_record/type_caster.rb +7 -0
  226. data/lib/active_record/validations/absence.rb +23 -0
  227. data/lib/active_record/validations/associated.rb +33 -18
  228. data/lib/active_record/validations/length.rb +24 -0
  229. data/lib/active_record/validations/presence.rb +66 -0
  230. data/lib/active_record/validations/uniqueness.rb +128 -68
  231. data/lib/active_record/validations.rb +48 -40
  232. data/lib/active_record/version.rb +5 -7
  233. data/lib/active_record.rb +71 -47
  234. data/lib/rails/generators/active_record/migration/migration_generator.rb +56 -8
  235. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +24 -0
  236. data/lib/rails/generators/active_record/migration/templates/migration.rb +28 -16
  237. data/lib/rails/generators/active_record/migration.rb +18 -8
  238. data/lib/rails/generators/active_record/model/model_generator.rb +38 -16
  239. data/lib/rails/generators/active_record/model/templates/application_record.rb +5 -0
  240. data/lib/rails/generators/active_record/model/templates/model.rb +7 -6
  241. data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
  242. data/lib/rails/generators/active_record.rb +3 -11
  243. metadata +188 -134
  244. data/examples/associations.png +0 -0
  245. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -63
  246. data/lib/active_record/associations/join_helper.rb +0 -55
  247. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  248. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
  249. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
  250. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -441
  251. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
  252. data/lib/active_record/dynamic_finder_match.rb +0 -68
  253. data/lib/active_record/dynamic_scope_match.rb +0 -23
  254. data/lib/active_record/fixtures/file.rb +0 -65
  255. data/lib/active_record/identity_map.rb +0 -162
  256. data/lib/active_record/observer.rb +0 -121
  257. data/lib/active_record/serializers/xml_serializer.rb +0 -203
  258. data/lib/active_record/session_store.rb +0 -360
  259. data/lib/active_record/test_case.rb +0 -73
  260. data/lib/rails/generators/active_record/model/templates/migration.rb +0 -15
  261. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  262. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  263. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  264. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -2,74 +2,88 @@ require 'active_support/core_ext/string/conversions'
2
2
 
3
3
  module ActiveRecord
4
4
  module Associations
5
- # Keeps track of table aliases for ActiveRecord::Associations::ClassMethods::JoinDependency and
6
- # ActiveRecord::Associations::ThroughAssociationScope
5
+ # Keeps track of table aliases for ActiveRecord::Associations::JoinDependency
7
6
  class AliasTracker # :nodoc:
8
- attr_reader :aliases, :table_joins, :connection
7
+ attr_reader :aliases
9
8
 
10
- # table_joins is an array of arel joins which might conflict with the aliases we assign here
11
- def initialize(connection = ActiveRecord::Model.connection, table_joins = [])
12
- @aliases = Hash.new { |h,k| h[k] = initial_count_for(k) }
13
- @table_joins = table_joins
14
- @connection = connection
9
+ def self.create(connection, initial_table, type_caster)
10
+ aliases = Hash.new(0)
11
+ aliases[initial_table] = 1
12
+ new connection, aliases, type_caster
15
13
  end
16
14
 
17
- def aliased_table_for(table_name, aliased_name = nil)
18
- table_alias = aliased_name_for(table_name, aliased_name)
19
-
20
- if table_alias == table_name
21
- Arel::Table.new(table_name)
15
+ def self.create_with_joins(connection, initial_table, joins, type_caster)
16
+ if joins.empty?
17
+ create(connection, initial_table, type_caster)
22
18
  else
23
- Arel::Table.new(table_name).alias(table_alias)
19
+ aliases = Hash.new { |h, k|
20
+ h[k] = initial_count_for(connection, k, joins)
21
+ }
22
+ aliases[initial_table] = 1
23
+ new connection, aliases, type_caster
24
24
  end
25
25
  end
26
26
 
27
- def aliased_name_for(table_name, aliased_name = nil)
28
- aliased_name ||= table_name
27
+ def self.initial_count_for(connection, name, table_joins)
28
+ # quoted_name should be downcased as some database adapters (Oracle) return quoted name in uppercase
29
+ quoted_name = connection.quote_table_name(name).downcase
29
30
 
31
+ counts = table_joins.map do |join|
32
+ if join.is_a?(Arel::Nodes::StringJoin)
33
+ # Table names + table aliases
34
+ join.left.downcase.scan(
35
+ /join(?:\s+\w+)?\s+(\S+\s+)?#{quoted_name}\son/
36
+ ).size
37
+ elsif join.respond_to? :left
38
+ join.left.table_name == name ? 1 : 0
39
+ else
40
+ # this branch is reached by two tests:
41
+ #
42
+ # activerecord/test/cases/associations/cascaded_eager_loading_test.rb:37
43
+ # with :posts
44
+ #
45
+ # activerecord/test/cases/associations/eager_test.rb:1133
46
+ # with :comments
47
+ #
48
+ 0
49
+ end
50
+ end
51
+
52
+ counts.sum
53
+ end
54
+
55
+ # table_joins is an array of arel joins which might conflict with the aliases we assign here
56
+ def initialize(connection, aliases, type_caster)
57
+ @aliases = aliases
58
+ @connection = connection
59
+ @type_caster = type_caster
60
+ end
61
+
62
+ def aliased_table_for(table_name, aliased_name)
30
63
  if aliases[table_name].zero?
31
64
  # If it's zero, we can have our table_name
32
65
  aliases[table_name] = 1
33
- table_name
66
+ Arel::Table.new(table_name, type_caster: @type_caster)
34
67
  else
35
68
  # Otherwise, we need to use an alias
36
- aliased_name = connection.table_alias_for(aliased_name)
69
+ aliased_name = @connection.table_alias_for(aliased_name)
37
70
 
38
71
  # Update the count
39
72
  aliases[aliased_name] += 1
40
73
 
41
- if aliases[aliased_name] > 1
74
+ table_alias = if aliases[aliased_name] > 1
42
75
  "#{truncate(aliased_name)}_#{aliases[aliased_name]}"
43
76
  else
44
77
  aliased_name
45
78
  end
79
+ Arel::Table.new(table_name, type_caster: @type_caster).alias(table_alias)
46
80
  end
47
81
  end
48
82
 
49
83
  private
50
84
 
51
- def initial_count_for(name)
52
- return 0 if Arel::Table === table_joins
53
-
54
- # quoted_name should be downcased as some database adapters (Oracle) return quoted name in uppercase
55
- quoted_name = connection.quote_table_name(name).downcase
56
-
57
- counts = table_joins.map do |join|
58
- if join.is_a?(Arel::Nodes::StringJoin)
59
- # Table names + table aliases
60
- join.left.downcase.scan(
61
- /join(?:\s+\w+)?\s+(\S+\s+)?#{quoted_name}\son/
62
- ).size
63
- else
64
- join.left.table_name == name ? 1 : 0
65
- end
66
- end
67
-
68
- counts.sum
69
- end
70
-
71
85
  def truncate(name)
72
- name.slice(0, connection.table_alias_length - 2)
86
+ name.slice(0, @connection.table_alias_length - 2)
73
87
  end
74
88
  end
75
89
  end
@@ -1,5 +1,4 @@
1
1
  require 'active_support/core_ext/array/wrap'
2
- require 'active_support/core_ext/object/inclusion'
3
2
 
4
3
  module ActiveRecord
5
4
  module Associations
@@ -9,44 +8,42 @@ module ActiveRecord
9
8
  #
10
9
  # Association
11
10
  # SingularAssociation
12
- # HasOneAssociation
11
+ # HasOneAssociation + ForeignAssociation
13
12
  # HasOneThroughAssociation + ThroughAssociation
14
13
  # BelongsToAssociation
15
14
  # BelongsToPolymorphicAssociation
16
15
  # CollectionAssociation
17
- # HasAndBelongsToManyAssociation
18
- # HasManyAssociation
16
+ # HasManyAssociation + ForeignAssociation
19
17
  # HasManyThroughAssociation + ThroughAssociation
20
18
  class Association #:nodoc:
21
19
  attr_reader :owner, :target, :reflection
20
+ attr_accessor :inversed
22
21
 
23
22
  delegate :options, :to => :reflection
24
23
 
25
24
  def initialize(owner, reflection)
26
25
  reflection.check_validity!
27
26
 
28
- @target = nil
29
27
  @owner, @reflection = owner, reflection
30
- @updated = false
31
28
 
32
29
  reset
33
30
  reset_scope
34
31
  end
35
32
 
36
- # Returns the name of the table of the related class:
33
+ # Returns the name of the table of the associated class:
37
34
  #
38
35
  # post.comments.aliased_table_name # => "comments"
39
36
  #
40
37
  def aliased_table_name
41
- reflection.klass.table_name
38
+ klass.table_name
42
39
  end
43
40
 
44
41
  # Resets the \loaded flag to +false+ and sets the \target to +nil+.
45
42
  def reset
46
43
  @loaded = false
47
- IdentityMap.remove(target) if IdentityMap.enabled? && target
48
44
  @target = nil
49
45
  @stale_state = nil
46
+ @inversed = false
50
47
  end
51
48
 
52
49
  # Reloads the \target and returns +self+ on success.
@@ -64,18 +61,19 @@ module ActiveRecord
64
61
 
65
62
  # Asserts the \target has been loaded setting the \loaded flag to +true+.
66
63
  def loaded!
67
- @loaded = true
64
+ @loaded = true
68
65
  @stale_state = stale_state
66
+ @inversed = false
69
67
  end
70
68
 
71
69
  # The target is stale if the target no longer points to the record(s) that the
72
70
  # relevant foreign_key(s) refers to. If stale, the association accessor method
73
71
  # on the owner will reload the target. It's up to subclasses to implement the
74
- # state_state method if relevant.
72
+ # stale_state method if relevant.
75
73
  #
76
74
  # Note that if the target has not been loaded, it is not considered stale.
77
75
  def stale_target?
78
- loaded? && @stale_state != stale_state
76
+ !inversed && loaded? && @stale_state != stale_state
79
77
  end
80
78
 
81
79
  # Sets the target of this association to <tt>\target</tt>, and the \loaded flag to +true+.
@@ -84,19 +82,19 @@ module ActiveRecord
84
82
  loaded!
85
83
  end
86
84
 
87
- def scoped
85
+ def scope
88
86
  target_scope.merge(association_scope)
89
87
  end
90
88
 
91
89
  # The scope for this association.
92
90
  #
93
91
  # Note that the association_scope is merged into the target_scope only when the
94
- # scoped method is called. This is because at that point the call may be surrounded
92
+ # scope method is called. This is because at that point the call may be surrounded
95
93
  # by scope.scoping { ... } or with_scope { ... } etc, which affects the scope which
96
94
  # actually gets built.
97
95
  def association_scope
98
96
  if klass
99
- @association_scope ||= AssociationScope.new(self).scope
97
+ @association_scope ||= AssociationScope.scope(self, klass.connection)
100
98
  end
101
99
  end
102
100
 
@@ -106,13 +104,15 @@ module ActiveRecord
106
104
 
107
105
  # Set the inverse association, if possible
108
106
  def set_inverse_instance(record)
109
- if record && invertible_for?(record)
107
+ if invertible_for?(record)
110
108
  inverse = record.association(inverse_reflection_for(record).name)
111
109
  inverse.target = owner
110
+ inverse.inversed = true
112
111
  end
112
+ record
113
113
  end
114
114
 
115
- # This class of the target. belongs_to polymorphic overrides this to look at the
115
+ # Returns the class of the target. belongs_to polymorphic overrides this to look at the
116
116
  # polymorphic_type field on the owner.
117
117
  def klass
118
118
  reflection.klass
@@ -121,7 +121,7 @@ module ActiveRecord
121
121
  # Can be overridden (i.e. in ThroughAssociation) to merge in other scopes (i.e. the
122
122
  # through association's scope)
123
123
  def target_scope
124
- klass.scoped
124
+ AssociationRelation.create(klass, klass.arel_table, klass.predicate_builder, self).merge!(klass.all)
125
125
  end
126
126
 
127
127
  # Loads the \target if needed and returns it.
@@ -129,28 +129,14 @@ module ActiveRecord
129
129
  # This method is abstract in the sense that it relies on +find_target+,
130
130
  # which is expected to be provided by descendants.
131
131
  #
132
- # If the \target is stale(the target no longer points to the record(s) that the
133
- # relevant foreign_key(s) refers to.), force reload the \target.
134
- #
135
- # Otherwise if the \target is already \loaded it is just returned. Thus, you can
136
- # call +load_target+ unconditionally to get the \target.
132
+ # If the \target is already \loaded it is just returned. Thus, you can call
133
+ # +load_target+ unconditionally to get the \target.
137
134
  #
138
135
  # ActiveRecord::RecordNotFound is rescued within the method, and it is
139
136
  # not reraised. The proxy is \reset and +nil+ is the return value.
140
137
  def load_target
141
- if (@stale_state && stale_target?) || find_target?
142
- begin
143
- if IdentityMap.enabled? && association_class && association_class.respond_to?(:base_class)
144
- @target = IdentityMap.get(association_class, owner[reflection.foreign_key])
145
- elsif @stale_state && stale_target?
146
- @target = find_target
147
- end
148
- rescue NameError
149
- nil
150
- ensure
151
- @target ||= find_target
152
- end
153
- end
138
+ @target = find_target if (@stale_state && stale_target?) || find_target?
139
+
154
140
  loaded! unless loaded?
155
141
  target
156
142
  rescue ActiveRecord::RecordNotFound
@@ -159,12 +145,34 @@ module ActiveRecord
159
145
 
160
146
  def interpolate(sql, record = nil)
161
147
  if sql.respond_to?(:to_proc)
162
- owner.send(:instance_exec, record, &sql)
148
+ owner.instance_exec(record, &sql)
163
149
  else
164
150
  sql
165
151
  end
166
152
  end
167
153
 
154
+ # We can't dump @reflection since it contains the scope proc
155
+ def marshal_dump
156
+ ivars = (instance_variables - [:@reflection]).map { |name| [name, instance_variable_get(name)] }
157
+ [@reflection.name, ivars]
158
+ end
159
+
160
+ def marshal_load(data)
161
+ reflection_name, ivars = data
162
+ ivars.each { |name, val| instance_variable_set(name, val) }
163
+ @reflection = @owner.class._reflect_on_association(reflection_name)
164
+ end
165
+
166
+ def initialize_attributes(record, except_from_scope_attributes = nil) #:nodoc:
167
+ except_from_scope_attributes ||= {}
168
+ skip_assign = [reflection.foreign_key, reflection.type].compact
169
+ assigned_keys = record.changed
170
+ assigned_keys += except_from_scope_attributes.keys.map(&:to_s)
171
+ attributes = create_scope.except(*(assigned_keys - skip_assign))
172
+ record.assign_attributes(attributes)
173
+ set_inverse_instance(record)
174
+ end
175
+
168
176
  private
169
177
 
170
178
  def find_target?
@@ -174,7 +182,7 @@ module ActiveRecord
174
182
  def creation_attributes
175
183
  attributes = {}
176
184
 
177
- if reflection.macro.in?([:has_one, :has_many]) && !options[:through]
185
+ if (reflection.has_one? || reflection.collection?) && !options[:through]
178
186
  attributes[reflection.foreign_key] = owner[reflection.active_record_primary_key]
179
187
 
180
188
  if reflection.options[:as]
@@ -190,13 +198,14 @@ module ActiveRecord
190
198
  creation_attributes.each { |key, value| record[key] = value }
191
199
  end
192
200
 
193
- # Should be true if there is a foreign key present on the owner which
201
+ # Returns true if there is a foreign key present on the owner which
194
202
  # references the target. This is used to determine whether we can load
195
203
  # the target if the owner is currently a new record (and therefore
196
- # without a key).
204
+ # without a key). If the owner is a new record then foreign_key must
205
+ # be present in order to load target.
197
206
  #
198
207
  # Currently implemented by belongs_to (vanilla and polymorphic) and
199
- # has_one/has_many :through associations which go through a belongs_to
208
+ # has_one/has_many :through associations which go through a belongs_to.
200
209
  def foreign_key_present?
201
210
  false
202
211
  end
@@ -204,10 +213,13 @@ module ActiveRecord
204
213
  # Raises ActiveRecord::AssociationTypeMismatch unless +record+ is of
205
214
  # the kind of the class of the associated objects. Meant to be used as
206
215
  # a sanity check when you are about to assign an associated record.
207
- def raise_on_type_mismatch(record)
208
- unless record.is_a?(reflection.klass) || record.is_a?(reflection.class_name.constantize)
209
- message = "#{reflection.class_name}(##{reflection.klass.object_id}) expected, got #{record.class}(##{record.class.object_id})"
210
- raise ActiveRecord::AssociationTypeMismatch, message
216
+ def raise_on_type_mismatch!(record)
217
+ unless record.is_a?(reflection.klass)
218
+ fresh_class = reflection.class_name.safe_constantize
219
+ unless fresh_class && record.is_a?(fresh_class)
220
+ message = "#{reflection.class_name}(##{reflection.klass.object_id}) expected, got #{record.class}(##{record.class.object_id})"
221
+ raise ActiveRecord::AssociationTypeMismatch, message
222
+ end
211
223
  end
212
224
  end
213
225
 
@@ -218,29 +230,37 @@ module ActiveRecord
218
230
  reflection.inverse_of
219
231
  end
220
232
 
221
- # Is this association invertible? Can be redefined by subclasses.
233
+ # Returns true if inverse association on the given record needs to be set.
234
+ # This method is redefined by subclasses.
222
235
  def invertible_for?(record)
223
- inverse_reflection_for(record)
236
+ foreign_key_for?(record) && inverse_reflection_for(record)
237
+ end
238
+
239
+ # Returns true if record contains the foreign_key
240
+ def foreign_key_for?(record)
241
+ record.has_attribute?(reflection.foreign_key)
224
242
  end
225
243
 
226
244
  # This should be implemented to return the values of the relevant key(s) on the owner,
227
- # so that when state_state is different from the value stored on the last find_target,
245
+ # so that when stale_state is different from the value stored on the last find_target,
228
246
  # the target is stale.
229
247
  #
230
248
  # This is only relevant to certain associations, which is why it returns nil by default.
231
249
  def stale_state
232
250
  end
233
251
 
234
- def association_class
235
- @reflection.klass
252
+ def build_record(attributes)
253
+ reflection.build_association(attributes) do |record|
254
+ initialize_attributes(record, attributes)
255
+ end
236
256
  end
237
257
 
238
- def build_record(attributes, options)
239
- reflection.build_association(attributes, options) do |record|
240
- skip_assign = [reflection.foreign_key, reflection.type].compact
241
- attributes = create_scope.except(*(record.changed - skip_assign))
242
- record.assign_attributes(attributes, :without_protection => true)
243
- end
258
+ # Returns true if statement cache should be skipped on the association reader.
259
+ def skip_statement_cache?
260
+ reflection.has_scope? ||
261
+ scope.eager_loading? ||
262
+ klass.scope_attributes? ||
263
+ reflection.source_reflection.active_record.default_scopes.any?
244
264
  end
245
265
  end
246
266
  end
@@ -1,133 +1,165 @@
1
1
  module ActiveRecord
2
2
  module Associations
3
3
  class AssociationScope #:nodoc:
4
- include JoinHelper
5
-
6
- attr_reader :association, :alias_tracker
4
+ def self.scope(association, connection)
5
+ INSTANCE.scope(association, connection)
6
+ end
7
7
 
8
- delegate :klass, :owner, :reflection, :interpolate, :to => :association
9
- delegate :chain, :conditions, :options, :source_options, :active_record, :to => :reflection
8
+ def self.create(&block)
9
+ block ||= lambda { |val| val }
10
+ new(block)
11
+ end
10
12
 
11
- def initialize(association)
12
- @association = association
13
- @alias_tracker = AliasTracker.new klass.connection
13
+ def initialize(value_transformation)
14
+ @value_transformation = value_transformation
14
15
  end
15
16
 
16
- def scope
17
+ INSTANCE = create
18
+
19
+ def scope(association, connection)
20
+ klass = association.klass
21
+ reflection = association.reflection
17
22
  scope = klass.unscoped
18
- scope = scope.extending(*Array.wrap(options[:extend]))
23
+ owner = association.owner
24
+ alias_tracker = AliasTracker.create connection, association.klass.table_name, klass.type_caster
25
+ chain_head, chain_tail = get_chain(reflection, association, alias_tracker)
19
26
 
20
- # It's okay to just apply all these like this. The options will only be present if the
21
- # association supports that option; this is enforced by the association builder.
22
- scope = scope.apply_finder_options(options.slice(
23
- :readonly, :include, :order, :limit, :joins, :group, :having, :offset, :select))
27
+ scope.extending! Array(reflection.options[:extend])
28
+ add_constraints(scope, owner, klass, reflection, chain_head, chain_tail)
29
+ end
24
30
 
25
- if options[:through] && !options[:include]
26
- scope = scope.includes(source_options[:include])
27
- end
31
+ def join_type
32
+ Arel::Nodes::InnerJoin
33
+ end
34
+
35
+ def self.get_bind_values(owner, chain)
36
+ binds = []
37
+ last_reflection = chain.last
28
38
 
29
- scope = scope.uniq if options[:uniq]
39
+ binds << last_reflection.join_id_for(owner)
40
+ if last_reflection.type
41
+ binds << owner.class.base_class.name
42
+ end
30
43
 
31
- add_constraints(scope)
44
+ chain.each_cons(2).each do |reflection, next_reflection|
45
+ if reflection.type
46
+ binds << next_reflection.klass.base_class.name
47
+ end
48
+ end
49
+ binds
32
50
  end
33
51
 
52
+ protected
53
+
54
+ attr_reader :value_transformation
55
+
34
56
  private
57
+ def join(table, constraint)
58
+ table.create_join(table, table.create_on(constraint), join_type)
59
+ end
35
60
 
36
- def add_constraints(scope)
37
- tables = construct_tables
61
+ def last_chain_scope(scope, table, reflection, owner, association_klass)
62
+ join_keys = reflection.join_keys(association_klass)
63
+ key = join_keys.key
64
+ foreign_key = join_keys.foreign_key
38
65
 
39
- chain.each_with_index do |reflection, i|
40
- table, foreign_table = tables.shift, tables.first
66
+ value = transform_value(owner[foreign_key])
67
+ scope = scope.where(table.name => { key => value })
41
68
 
42
- if reflection.source_macro == :has_and_belongs_to_many
43
- join_table = tables.shift
69
+ if reflection.type
70
+ polymorphic_type = transform_value(owner.class.base_class.name)
71
+ scope = scope.where(table.name => { reflection.type => polymorphic_type })
72
+ end
44
73
 
45
- scope = scope.joins(join(
46
- join_table,
47
- table[reflection.association_primary_key].
48
- eq(join_table[reflection.association_foreign_key])
49
- ))
74
+ scope
75
+ end
50
76
 
51
- table, foreign_table = join_table, tables.first
52
- end
77
+ def transform_value(value)
78
+ value_transformation.call(value)
79
+ end
53
80
 
54
- if reflection.source_macro == :belongs_to
55
- if reflection.options[:polymorphic]
56
- key = reflection.association_primary_key(klass)
57
- else
58
- key = reflection.association_primary_key
59
- end
81
+ def next_chain_scope(scope, table, reflection, association_klass, foreign_table, next_reflection)
82
+ join_keys = reflection.join_keys(association_klass)
83
+ key = join_keys.key
84
+ foreign_key = join_keys.foreign_key
60
85
 
61
- foreign_key = reflection.foreign_key
62
- else
63
- key = reflection.foreign_key
64
- foreign_key = reflection.active_record_primary_key
65
- end
86
+ constraint = table[key].eq(foreign_table[foreign_key])
66
87
 
67
- conditions = self.conditions[i]
88
+ if reflection.type
89
+ value = transform_value(next_reflection.klass.base_class.name)
90
+ scope = scope.where(table.name => { reflection.type => value })
91
+ end
68
92
 
69
- if reflection == chain.last
70
- scope = scope.where(table[key].eq(owner[foreign_key]))
93
+ scope = scope.joins(join(foreign_table, constraint))
94
+ end
71
95
 
72
- if reflection.type
73
- scope = scope.where(table[reflection.type].eq(owner.class.base_class.name))
74
- end
96
+ class ReflectionProxy < SimpleDelegator # :nodoc:
97
+ attr_accessor :next
98
+ attr_reader :alias_name
75
99
 
76
- conditions.each do |condition|
77
- if options[:through] && condition.is_a?(Hash)
78
- condition = disambiguate_condition(table, condition)
79
- end
100
+ def initialize(reflection, alias_name)
101
+ super(reflection)
102
+ @alias_name = alias_name
103
+ end
80
104
 
81
- scope = scope.where(interpolate(condition))
82
- end
83
- else
84
- constraint = table[key].eq(foreign_table[foreign_key])
105
+ def all_includes; nil; end
106
+ end
85
107
 
86
- if reflection.type
87
- type = chain[i + 1].klass.base_class.name
88
- constraint = constraint.and(table[reflection.type].eq(type))
89
- end
108
+ def get_chain(reflection, association, tracker)
109
+ name = reflection.name
110
+ runtime_reflection = Reflection::RuntimeReflection.new(reflection, association)
111
+ previous_reflection = runtime_reflection
112
+ reflection.chain.drop(1).each do |refl|
113
+ alias_name = tracker.aliased_table_for(refl.table_name, refl.alias_candidate(name))
114
+ proxy = ReflectionProxy.new(refl, alias_name)
115
+ previous_reflection.next = proxy
116
+ previous_reflection = proxy
117
+ end
118
+ [runtime_reflection, previous_reflection]
119
+ end
90
120
 
91
- scope = scope.joins(join(foreign_table, constraint))
121
+ def add_constraints(scope, owner, association_klass, refl, chain_head, chain_tail)
122
+ owner_reflection = chain_tail
123
+ table = owner_reflection.alias_name
124
+ scope = last_chain_scope(scope, table, owner_reflection, owner, association_klass)
92
125
 
93
- unless conditions.empty?
94
- scope = scope.where(sanitize(conditions, table))
95
- end
126
+ reflection = chain_head
127
+ loop do
128
+ break unless reflection
129
+ table = reflection.alias_name
130
+
131
+ unless reflection == chain_tail
132
+ next_reflection = reflection.next
133
+ foreign_table = next_reflection.alias_name
134
+ scope = next_chain_scope(scope, table, reflection, association_klass, foreign_table, next_reflection)
96
135
  end
97
- end
98
136
 
99
- scope
100
- end
137
+ # Exclude the scope of the association itself, because that
138
+ # was already merged in the #scope method.
139
+ reflection.constraints.each do |scope_chain_item|
140
+ item = eval_scope(reflection.klass, scope_chain_item, owner)
101
141
 
102
- def alias_suffix
103
- reflection.name
104
- end
142
+ if scope_chain_item == refl.scope
143
+ scope.merge! item.except(:where, :includes)
144
+ end
105
145
 
106
- def table_name_for(reflection)
107
- if reflection == self.reflection
108
- # If this is a polymorphic belongs_to, we want to get the klass from the
109
- # association because it depends on the polymorphic_type attribute of
110
- # the owner
111
- klass.table_name
112
- else
113
- reflection.table_name
146
+ reflection.all_includes do
147
+ scope.includes! item.includes_values
148
+ end
149
+
150
+ scope.unscope!(*item.unscope_values)
151
+ scope.where_clause += item.where_clause
152
+ scope.order_values |= item.order_values
153
+ end
154
+
155
+ reflection = reflection.next
114
156
  end
157
+
158
+ scope
115
159
  end
116
160
 
117
- def disambiguate_condition(table, condition)
118
- if condition.is_a?(Hash)
119
- Hash[
120
- condition.map do |k, v|
121
- if v.is_a?(Hash)
122
- [k, v]
123
- else
124
- [table.table_alias || table.name, { k => v }]
125
- end
126
- end
127
- ]
128
- else
129
- condition
130
- end
161
+ def eval_scope(klass, scope, owner)
162
+ klass.unscoped.instance_exec(owner, &scope)
131
163
  end
132
164
  end
133
165
  end