activerecord 3.1.10 → 4.2.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (237) hide show
  1. checksums.yaml +6 -6
  2. data/CHANGELOG.md +1837 -338
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +39 -43
  5. data/examples/performance.rb +51 -20
  6. data/examples/simple.rb +4 -4
  7. data/lib/active_record/aggregations.rb +57 -43
  8. data/lib/active_record/association_relation.rb +35 -0
  9. data/lib/active_record/associations/alias_tracker.rb +47 -39
  10. data/lib/active_record/associations/association.rb +71 -85
  11. data/lib/active_record/associations/association_scope.rb +138 -89
  12. data/lib/active_record/associations/belongs_to_association.rb +65 -25
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +9 -3
  14. data/lib/active_record/associations/builder/association.rb +125 -29
  15. data/lib/active_record/associations/builder/belongs_to.rb +91 -60
  16. data/lib/active_record/associations/builder/collection_association.rb +69 -49
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +113 -42
  18. data/lib/active_record/associations/builder/has_many.rb +8 -64
  19. data/lib/active_record/associations/builder/has_one.rb +12 -52
  20. data/lib/active_record/associations/builder/singular_association.rb +22 -29
  21. data/lib/active_record/associations/collection_association.rb +294 -187
  22. data/lib/active_record/associations/collection_proxy.rb +961 -94
  23. data/lib/active_record/associations/foreign_association.rb +11 -0
  24. data/lib/active_record/associations/has_many_association.rb +118 -23
  25. data/lib/active_record/associations/has_many_through_association.rb +115 -45
  26. data/lib/active_record/associations/has_one_association.rb +57 -24
  27. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  28. data/lib/active_record/associations/join_dependency/join_association.rb +76 -102
  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 +230 -156
  32. data/lib/active_record/associations/preloader/association.rb +96 -55
  33. data/lib/active_record/associations/preloader/collection_association.rb +3 -3
  34. data/lib/active_record/associations/preloader/has_many_through.rb +7 -3
  35. data/lib/active_record/associations/preloader/has_one.rb +1 -1
  36. data/lib/active_record/associations/preloader/singular_association.rb +3 -3
  37. data/lib/active_record/associations/preloader/through_association.rb +61 -32
  38. data/lib/active_record/associations/preloader.rb +113 -87
  39. data/lib/active_record/associations/singular_association.rb +29 -13
  40. data/lib/active_record/associations/through_association.rb +37 -19
  41. data/lib/active_record/associations.rb +505 -371
  42. data/lib/active_record/attribute.rb +163 -0
  43. data/lib/active_record/attribute_assignment.rb +212 -0
  44. data/lib/active_record/attribute_decorators.rb +66 -0
  45. data/lib/active_record/attribute_methods/before_type_cast.rb +52 -7
  46. data/lib/active_record/attribute_methods/dirty.rb +141 -51
  47. data/lib/active_record/attribute_methods/primary_key.rb +87 -36
  48. data/lib/active_record/attribute_methods/query.rb +5 -4
  49. data/lib/active_record/attribute_methods/read.rb +74 -117
  50. data/lib/active_record/attribute_methods/serialization.rb +70 -0
  51. data/lib/active_record/attribute_methods/time_zone_conversion.rb +49 -47
  52. data/lib/active_record/attribute_methods/write.rb +60 -21
  53. data/lib/active_record/attribute_methods.rb +409 -48
  54. data/lib/active_record/attribute_set/builder.rb +106 -0
  55. data/lib/active_record/attribute_set.rb +81 -0
  56. data/lib/active_record/attributes.rb +147 -0
  57. data/lib/active_record/autosave_association.rb +279 -232
  58. data/lib/active_record/base.rb +84 -1969
  59. data/lib/active_record/callbacks.rb +66 -28
  60. data/lib/active_record/coders/json.rb +13 -0
  61. data/lib/active_record/coders/yaml_column.rb +18 -21
  62. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +422 -243
  63. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  64. data/lib/active_record/connection_adapters/abstract/database_statements.rb +170 -194
  65. data/lib/active_record/connection_adapters/abstract/query_cache.rb +32 -19
  66. data/lib/active_record/connection_adapters/abstract/quoting.rb +79 -57
  67. data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
  68. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +125 -0
  69. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +273 -170
  70. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +50 -0
  71. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +731 -254
  72. data/lib/active_record/connection_adapters/abstract/transaction.rb +215 -0
  73. data/lib/active_record/connection_adapters/abstract_adapter.rb +339 -95
  74. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +946 -0
  75. data/lib/active_record/connection_adapters/column.rb +33 -221
  76. data/lib/active_record/connection_adapters/connection_specification.rb +275 -0
  77. data/lib/active_record/connection_adapters/mysql2_adapter.rb +140 -602
  78. data/lib/active_record/connection_adapters/mysql_adapter.rb +254 -756
  79. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +93 -0
  80. data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
  81. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +232 -0
  82. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +100 -0
  83. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +15 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +36 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +19 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +79 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +19 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +109 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid.rb +36 -0
  108. data/lib/active_record/connection_adapters/postgresql/quoting.rb +108 -0
  109. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  110. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +152 -0
  111. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +596 -0
  112. data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
  113. data/lib/active_record/connection_adapters/postgresql_adapter.rb +445 -902
  114. data/lib/active_record/connection_adapters/schema_cache.rb +94 -0
  115. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +578 -25
  116. data/lib/active_record/connection_handling.rb +132 -0
  117. data/lib/active_record/core.rb +579 -0
  118. data/lib/active_record/counter_cache.rb +159 -102
  119. data/lib/active_record/dynamic_matchers.rb +140 -0
  120. data/lib/active_record/enum.rb +197 -0
  121. data/lib/active_record/errors.rb +102 -34
  122. data/lib/active_record/explain.rb +38 -0
  123. data/lib/active_record/explain_registry.rb +30 -0
  124. data/lib/active_record/explain_subscriber.rb +29 -0
  125. data/lib/active_record/fixture_set/file.rb +56 -0
  126. data/lib/active_record/fixtures.rb +318 -260
  127. data/lib/active_record/gem_version.rb +15 -0
  128. data/lib/active_record/inheritance.rb +247 -0
  129. data/lib/active_record/integration.rb +113 -0
  130. data/lib/active_record/legacy_yaml_adapter.rb +30 -0
  131. data/lib/active_record/locale/en.yml +8 -1
  132. data/lib/active_record/locking/optimistic.rb +80 -52
  133. data/lib/active_record/locking/pessimistic.rb +27 -5
  134. data/lib/active_record/log_subscriber.rb +25 -18
  135. data/lib/active_record/migration/command_recorder.rb +130 -38
  136. data/lib/active_record/migration/join_table.rb +15 -0
  137. data/lib/active_record/migration.rb +532 -201
  138. data/lib/active_record/model_schema.rb +342 -0
  139. data/lib/active_record/nested_attributes.rb +229 -139
  140. data/lib/active_record/no_touching.rb +52 -0
  141. data/lib/active_record/null_relation.rb +81 -0
  142. data/lib/active_record/persistence.rb +304 -99
  143. data/lib/active_record/query_cache.rb +25 -43
  144. data/lib/active_record/querying.rb +68 -0
  145. data/lib/active_record/railtie.rb +86 -45
  146. data/lib/active_record/railties/console_sandbox.rb +3 -4
  147. data/lib/active_record/railties/controller_runtime.rb +7 -4
  148. data/lib/active_record/railties/databases.rake +198 -377
  149. data/lib/active_record/railties/jdbcmysql_error.rb +2 -2
  150. data/lib/active_record/readonly_attributes.rb +23 -0
  151. data/lib/active_record/reflection.rb +516 -165
  152. data/lib/active_record/relation/batches.rb +96 -45
  153. data/lib/active_record/relation/calculations.rb +221 -144
  154. data/lib/active_record/relation/delegation.rb +140 -0
  155. data/lib/active_record/relation/finder_methods.rb +362 -243
  156. data/lib/active_record/relation/merger.rb +193 -0
  157. data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
  158. data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
  159. data/lib/active_record/relation/predicate_builder.rb +135 -41
  160. data/lib/active_record/relation/query_methods.rb +982 -155
  161. data/lib/active_record/relation/spawn_methods.rb +50 -110
  162. data/lib/active_record/relation.rb +371 -180
  163. data/lib/active_record/result.rb +109 -12
  164. data/lib/active_record/runtime_registry.rb +22 -0
  165. data/lib/active_record/sanitization.rb +191 -0
  166. data/lib/active_record/schema.rb +19 -13
  167. data/lib/active_record/schema_dumper.rb +111 -61
  168. data/lib/active_record/schema_migration.rb +53 -0
  169. data/lib/active_record/scoping/default.rb +135 -0
  170. data/lib/active_record/scoping/named.rb +164 -0
  171. data/lib/active_record/scoping.rb +87 -0
  172. data/lib/active_record/serialization.rb +7 -45
  173. data/lib/active_record/serializers/xml_serializer.rb +14 -65
  174. data/lib/active_record/statement_cache.rb +111 -0
  175. data/lib/active_record/store.rb +205 -0
  176. data/lib/active_record/tasks/database_tasks.rb +299 -0
  177. data/lib/active_record/tasks/mysql_database_tasks.rb +159 -0
  178. data/lib/active_record/tasks/postgresql_database_tasks.rb +101 -0
  179. data/lib/active_record/tasks/sqlite_database_tasks.rb +55 -0
  180. data/lib/active_record/timestamp.rb +35 -14
  181. data/lib/active_record/transactions.rb +141 -74
  182. data/lib/active_record/translation.rb +22 -0
  183. data/lib/active_record/type/big_integer.rb +13 -0
  184. data/lib/active_record/type/binary.rb +50 -0
  185. data/lib/active_record/type/boolean.rb +31 -0
  186. data/lib/active_record/type/date.rb +50 -0
  187. data/lib/active_record/type/date_time.rb +54 -0
  188. data/lib/active_record/type/decimal.rb +64 -0
  189. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  190. data/lib/active_record/type/decorator.rb +14 -0
  191. data/lib/active_record/type/float.rb +19 -0
  192. data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
  193. data/lib/active_record/type/integer.rb +59 -0
  194. data/lib/active_record/type/mutable.rb +16 -0
  195. data/lib/active_record/type/numeric.rb +36 -0
  196. data/lib/active_record/type/serialized.rb +62 -0
  197. data/lib/active_record/type/string.rb +40 -0
  198. data/lib/active_record/type/text.rb +11 -0
  199. data/lib/active_record/type/time.rb +26 -0
  200. data/lib/active_record/type/time_value.rb +38 -0
  201. data/lib/active_record/type/type_map.rb +64 -0
  202. data/lib/active_record/type/unsigned_integer.rb +15 -0
  203. data/lib/active_record/type/value.rb +110 -0
  204. data/lib/active_record/type.rb +23 -0
  205. data/lib/active_record/validations/associated.rb +27 -18
  206. data/lib/active_record/validations/presence.rb +67 -0
  207. data/lib/active_record/validations/uniqueness.rb +125 -66
  208. data/lib/active_record/validations.rb +37 -30
  209. data/lib/active_record/version.rb +5 -7
  210. data/lib/active_record.rb +80 -25
  211. data/lib/rails/generators/active_record/migration/migration_generator.rb +54 -9
  212. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +19 -0
  213. data/lib/rails/generators/active_record/migration/templates/migration.rb +25 -11
  214. data/lib/rails/generators/active_record/migration.rb +11 -8
  215. data/lib/rails/generators/active_record/model/model_generator.rb +17 -4
  216. data/lib/rails/generators/active_record/model/templates/model.rb +5 -2
  217. data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
  218. data/lib/rails/generators/active_record.rb +3 -11
  219. metadata +132 -53
  220. data/examples/associations.png +0 -0
  221. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -62
  222. data/lib/active_record/associations/join_helper.rb +0 -55
  223. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  224. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -135
  225. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -556
  226. data/lib/active_record/dynamic_finder_match.rb +0 -56
  227. data/lib/active_record/dynamic_scope_match.rb +0 -23
  228. data/lib/active_record/identity_map.rb +0 -163
  229. data/lib/active_record/named_scope.rb +0 -200
  230. data/lib/active_record/observer.rb +0 -121
  231. data/lib/active_record/session_store.rb +0 -358
  232. data/lib/active_record/test_case.rb +0 -69
  233. data/lib/rails/generators/active_record/model/templates/migration.rb +0 -17
  234. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  235. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  236. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  237. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -16
@@ -1,122 +1,183 @@
1
1
  module ActiveRecord
2
2
  module Associations
3
3
  class AssociationScope #:nodoc:
4
- include JoinHelper
4
+ def self.scope(association, connection)
5
+ INSTANCE.scope association, connection
6
+ end
5
7
 
6
- attr_reader :association, :alias_tracker
8
+ class BindSubstitution
9
+ def initialize(block)
10
+ @block = block
11
+ end
7
12
 
8
- delegate :klass, :owner, :reflection, :interpolate, :to => :association
9
- delegate :chain, :conditions, :options, :source_options, :active_record, :to => :reflection
13
+ def bind_value(scope, column, value, alias_tracker)
14
+ substitute = alias_tracker.connection.substitute_at(column)
15
+ scope.bind_values += [[column, @block.call(value)]]
16
+ substitute
17
+ end
18
+ end
10
19
 
11
- def initialize(association)
12
- @association = association
13
- @alias_tracker = AliasTracker.new
20
+ def self.create(&block)
21
+ block = block ? block : lambda { |val| val }
22
+ new BindSubstitution.new(block)
14
23
  end
15
24
 
16
- def scope
17
- scope = klass.unscoped
18
- scope = scope.extending(*Array.wrap(options[:extend]))
25
+ def initialize(bind_substitution)
26
+ @bind_substitution = bind_substitution
27
+ end
19
28
 
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))
29
+ INSTANCE = create
24
30
 
25
- if options[:through] && !options[:include]
26
- scope = scope.includes(source_options[:include])
27
- end
31
+ def scope(association, connection)
32
+ klass = association.klass
33
+ reflection = association.reflection
34
+ scope = klass.unscoped
35
+ owner = association.owner
36
+ alias_tracker = AliasTracker.empty connection
28
37
 
29
- if select = select_value
30
- scope = scope.select(select)
38
+ scope.extending! Array(reflection.options[:extend])
39
+ add_constraints(scope, owner, klass, reflection, alias_tracker)
40
+ end
41
+
42
+ def join_type
43
+ Arel::Nodes::InnerJoin
44
+ end
45
+
46
+ def self.get_bind_values(owner, chain)
47
+ binds = []
48
+ last_reflection = chain.last
49
+
50
+ binds << last_reflection.join_id_for(owner)
51
+ if last_reflection.type
52
+ binds << owner.class.base_class.name
31
53
  end
32
54
 
33
- add_constraints(scope)
55
+ chain.each_cons(2).each do |reflection, next_reflection|
56
+ if reflection.type
57
+ binds << next_reflection.klass.base_class.name
58
+ end
59
+ end
60
+ binds
34
61
  end
35
62
 
36
63
  private
37
64
 
38
- def select_value
39
- select_value = options[:select]
40
-
41
- if reflection.collection?
42
- select_value ||= options[:uniq] && "DISTINCT #{reflection.quoted_table_name}.*"
65
+ def construct_tables(chain, klass, refl, alias_tracker)
66
+ chain.map do |reflection|
67
+ alias_tracker.aliased_table_for(
68
+ table_name_for(reflection, klass, refl),
69
+ table_alias_for(reflection, refl, reflection != refl)
70
+ )
43
71
  end
72
+ end
44
73
 
45
- select_value
74
+ def table_alias_for(reflection, refl, join = false)
75
+ name = "#{reflection.plural_name}_#{alias_suffix(refl)}"
76
+ name << "_join" if join
77
+ name
46
78
  end
47
79
 
48
- def add_constraints(scope)
49
- tables = construct_tables
80
+ def join(table, constraint)
81
+ table.create_join(table, table.create_on(constraint), join_type)
82
+ end
50
83
 
51
- chain.each_with_index do |reflection, i|
52
- table, foreign_table = tables.shift, tables.first
84
+ def column_for(table_name, column_name, alias_tracker)
85
+ columns = alias_tracker.connection.schema_cache.columns_hash(table_name)
86
+ columns[column_name]
87
+ end
53
88
 
54
- if reflection.source_macro == :has_and_belongs_to_many
55
- join_table = tables.shift
89
+ def bind_value(scope, column, value, alias_tracker)
90
+ @bind_substitution.bind_value scope, column, value, alias_tracker
91
+ end
56
92
 
57
- scope = scope.joins(join(
58
- join_table,
59
- table[reflection.association_primary_key].
60
- eq(join_table[reflection.association_foreign_key])
61
- ))
93
+ def bind(scope, table_name, column_name, value, tracker)
94
+ column = column_for table_name, column_name, tracker
95
+ bind_value scope, column, value, tracker
96
+ end
62
97
 
63
- table, foreign_table = join_table, tables.first
64
- end
98
+ def last_chain_scope(scope, table, reflection, owner, tracker, assoc_klass)
99
+ join_keys = reflection.join_keys(assoc_klass)
100
+ key = join_keys.key
101
+ foreign_key = join_keys.foreign_key
65
102
 
66
- if reflection.source_macro == :belongs_to
67
- if reflection.options[:polymorphic]
68
- key = reflection.association_primary_key(klass)
69
- else
70
- key = reflection.association_primary_key
71
- end
103
+ bind_val = bind scope, table.table_name, key.to_s, owner[foreign_key], tracker
104
+ scope = scope.where(table[key].eq(bind_val))
72
105
 
73
- foreign_key = reflection.foreign_key
74
- else
75
- key = reflection.foreign_key
76
- foreign_key = reflection.active_record_primary_key
77
- end
106
+ if reflection.type
107
+ value = owner.class.base_class.name
108
+ bind_val = bind scope, table.table_name, reflection.type, value, tracker
109
+ scope = scope.where(table[reflection.type].eq(bind_val))
110
+ else
111
+ scope
112
+ end
113
+ end
78
114
 
79
- conditions = self.conditions[i]
115
+ def next_chain_scope(scope, table, reflection, tracker, assoc_klass, foreign_table, next_reflection)
116
+ join_keys = reflection.join_keys(assoc_klass)
117
+ key = join_keys.key
118
+ foreign_key = join_keys.foreign_key
80
119
 
81
- if reflection == chain.last
82
- scope = scope.where(table[key].eq(owner[foreign_key]))
120
+ constraint = table[key].eq(foreign_table[foreign_key])
83
121
 
84
- if reflection.type
85
- scope = scope.where(table[reflection.type].eq(owner.class.base_class.name))
86
- end
122
+ if reflection.type
123
+ value = next_reflection.klass.base_class.name
124
+ bind_val = bind scope, table.table_name, reflection.type, value, tracker
125
+ scope = scope.where(table[reflection.type].eq(bind_val))
126
+ end
87
127
 
88
- conditions.each do |condition|
89
- if options[:through] && condition.is_a?(Hash)
90
- condition = disambiguate_condition(table, condition)
91
- end
128
+ scope = scope.joins(join(foreign_table, constraint))
129
+ end
92
130
 
93
- scope = scope.where(interpolate(condition))
94
- end
95
- else
96
- constraint = table[key].eq(foreign_table[foreign_key])
131
+ def add_constraints(scope, owner, assoc_klass, refl, tracker)
132
+ chain = refl.chain
133
+ scope_chain = refl.scope_chain
97
134
 
98
- if reflection.type
99
- type = chain[i + 1].klass.base_class.name
100
- constraint = constraint.and(table[reflection.type].eq(type))
101
- end
135
+ tables = construct_tables(chain, assoc_klass, refl, tracker)
136
+
137
+ owner_reflection = chain.last
138
+ table = tables.last
139
+ scope = last_chain_scope(scope, table, owner_reflection, owner, tracker, assoc_klass)
140
+
141
+ chain.each_with_index do |reflection, i|
142
+ table, foreign_table = tables.shift, tables.first
143
+
144
+ unless reflection == chain.last
145
+ next_reflection = chain[i + 1]
146
+ scope = next_chain_scope(scope, table, reflection, tracker, assoc_klass, foreign_table, next_reflection)
147
+ end
148
+
149
+ is_first_chain = i == 0
150
+ klass = is_first_chain ? assoc_klass : reflection.klass
102
151
 
103
- scope = scope.joins(join(foreign_table, constraint))
152
+ # Exclude the scope of the association itself, because that
153
+ # was already merged in the #scope method.
154
+ scope_chain[i].each do |scope_chain_item|
155
+ item = eval_scope(klass, scope_chain_item, owner)
104
156
 
105
- unless conditions.empty?
106
- scope = scope.where(sanitize(conditions, table))
157
+ if scope_chain_item == refl.scope
158
+ scope.merge! item.except(:where, :includes, :bind)
107
159
  end
160
+
161
+ if is_first_chain
162
+ scope.includes! item.includes_values
163
+ end
164
+
165
+ scope.unscope!(*item.unscope_values)
166
+ scope.where_values += item.where_values
167
+ scope.bind_values += item.bind_values
168
+ scope.order_values |= item.order_values
108
169
  end
109
170
  end
110
171
 
111
172
  scope
112
173
  end
113
174
 
114
- def alias_suffix
115
- reflection.name
175
+ def alias_suffix(refl)
176
+ refl.name
116
177
  end
117
178
 
118
- def table_name_for(reflection)
119
- if reflection == self.reflection
179
+ def table_name_for(reflection, klass, refl)
180
+ if reflection == refl
120
181
  # If this is a polymorphic belongs_to, we want to get the klass from the
121
182
  # association because it depends on the polymorphic_type attribute of
122
183
  # the owner
@@ -126,20 +187,8 @@ module ActiveRecord
126
187
  end
127
188
  end
128
189
 
129
- def disambiguate_condition(table, condition)
130
- if condition.is_a?(Hash)
131
- Hash[
132
- condition.map do |k, v|
133
- if v.is_a?(Hash)
134
- [k, v]
135
- else
136
- [table.table_alias || table.name, { k => v }]
137
- end
138
- end
139
- ]
140
- else
141
- condition
142
- end
190
+ def eval_scope(klass, scope, owner)
191
+ klass.unscoped.instance_exec(owner, &scope)
143
192
  end
144
193
  end
145
194
  end
@@ -1,78 +1,118 @@
1
1
  module ActiveRecord
2
- # = Active Record Belongs To Associations
2
+ # = Active Record Belongs To Association
3
3
  module Associations
4
4
  class BelongsToAssociation < SingularAssociation #:nodoc:
5
- def replace(record)
6
- raise_on_type_mismatch(record) if record
7
5
 
8
- update_counters(record)
9
- replace_keys(record)
10
- set_inverse_instance(record)
6
+ def handle_dependency
7
+ target.send(options[:dependent]) if load_target
8
+ end
11
9
 
12
- @updated = true if record
10
+ def replace(record)
11
+ if record
12
+ raise_on_type_mismatch!(record)
13
+ update_counters(record)
14
+ replace_keys(record)
15
+ set_inverse_instance(record)
16
+ @updated = true
17
+ else
18
+ decrement_counters
19
+ remove_keys
20
+ end
13
21
 
14
22
  self.target = record
15
23
  end
16
24
 
25
+ def reset
26
+ super
27
+ @updated = false
28
+ end
29
+
17
30
  def updated?
18
31
  @updated
19
32
  end
20
33
 
34
+ def decrement_counters # :nodoc:
35
+ with_cache_name { |name| decrement_counter name }
36
+ end
37
+
38
+ def increment_counters # :nodoc:
39
+ with_cache_name { |name| increment_counter name }
40
+ end
41
+
21
42
  private
22
43
 
23
44
  def find_target?
24
45
  !loaded? && foreign_key_present? && klass
25
46
  end
26
47
 
27
- def update_counters(record)
48
+ def with_cache_name
28
49
  counter_cache_name = reflection.counter_cache_column
50
+ return unless counter_cache_name && owner.persisted?
51
+ yield counter_cache_name
52
+ end
29
53
 
30
- if counter_cache_name && owner.persisted? && different_target?(record)
31
- if record
32
- record.class.increment_counter(counter_cache_name, record.id)
33
- end
54
+ def update_counters(record)
55
+ with_cache_name do |name|
56
+ return unless different_target? record
57
+ record.class.increment_counter(name, record.id)
58
+ decrement_counter name
59
+ end
60
+ end
61
+
62
+ def decrement_counter(counter_cache_name)
63
+ if foreign_key_present?
64
+ klass.decrement_counter(counter_cache_name, target_id)
65
+ end
66
+ end
34
67
 
35
- if foreign_key_present?
36
- klass.decrement_counter(counter_cache_name, target_id)
68
+ def increment_counter(counter_cache_name)
69
+ if foreign_key_present?
70
+ klass.increment_counter(counter_cache_name, target_id)
71
+ if target && !stale_target? && counter_cache_available_in_memory?(counter_cache_name)
72
+ target.increment(counter_cache_name)
37
73
  end
38
74
  end
39
75
  end
40
76
 
41
77
  # Checks whether record is different to the current target, without loading it
42
78
  def different_target?(record)
43
- record.nil? && owner[reflection.foreign_key] ||
44
- record && record.id != owner[reflection.foreign_key]
79
+ record.id != owner._read_attribute(reflection.foreign_key)
45
80
  end
46
81
 
47
82
  def replace_keys(record)
48
- if record
49
- owner[reflection.foreign_key] = record[reflection.association_primary_key(record.class)]
50
- else
51
- owner[reflection.foreign_key] = nil
52
- end
83
+ owner[reflection.foreign_key] = record._read_attribute(reflection.association_primary_key(record.class))
84
+ end
85
+
86
+ def remove_keys
87
+ owner[reflection.foreign_key] = nil
53
88
  end
54
89
 
55
90
  def foreign_key_present?
56
- owner[reflection.foreign_key]
91
+ owner._read_attribute(reflection.foreign_key)
57
92
  end
58
93
 
59
94
  # NOTE - for now, we're only supporting inverse setting from belongs_to back onto
60
95
  # has_one associations.
61
96
  def invertible_for?(record)
62
97
  inverse = inverse_reflection_for(record)
63
- inverse && inverse.macro == :has_one
98
+ inverse && inverse.has_one?
64
99
  end
65
100
 
66
101
  def target_id
67
102
  if options[:primary_key]
68
103
  owner.send(reflection.name).try(:id)
69
104
  else
70
- owner[reflection.foreign_key]
105
+ owner._read_attribute(reflection.foreign_key)
71
106
  end
72
107
  end
73
108
 
74
109
  def stale_state
75
- owner[reflection.foreign_key].to_s
110
+ result = owner._read_attribute(reflection.foreign_key)
111
+ result && result.to_s
112
+ end
113
+
114
+ def counter_cache_available_in_memory?(counter_cache_name)
115
+ target.respond_to?(counter_cache_name)
76
116
  end
77
117
  end
78
118
  end
@@ -11,7 +11,12 @@ module ActiveRecord
11
11
 
12
12
  def replace_keys(record)
13
13
  super
14
- owner[reflection.foreign_type] = record && record.class.base_class.name
14
+ owner[reflection.foreign_type] = record.class.base_class.name
15
+ end
16
+
17
+ def remove_keys
18
+ super
19
+ owner[reflection.foreign_type] = nil
15
20
  end
16
21
 
17
22
  def different_target?(record)
@@ -22,12 +27,13 @@ module ActiveRecord
22
27
  reflection.polymorphic_inverse_of(record.class)
23
28
  end
24
29
 
25
- def raise_on_type_mismatch(record)
30
+ def raise_on_type_mismatch!(record)
26
31
  # A polymorphic association cannot have a type mismatch, by definition
27
32
  end
28
33
 
29
34
  def stale_state
30
- [super, owner[reflection.foreign_type].to_s]
35
+ foreign_key = super
36
+ foreign_key && [foreign_key.to_s, owner[reflection.foreign_type].to_s]
31
37
  end
32
38
  end
33
39
  end
@@ -1,53 +1,149 @@
1
+ require 'active_support/core_ext/module/attribute_accessors'
2
+
3
+ # This is the parent Association class which defines the variables
4
+ # used by all associations.
5
+ #
6
+ # The hierarchy is defined as follows:
7
+ # Association
8
+ # - SingularAssociation
9
+ # - BelongsToAssociation
10
+ # - HasOneAssociation
11
+ # - CollectionAssociation
12
+ # - HasManyAssociation
13
+
1
14
  module ActiveRecord::Associations::Builder
2
15
  class Association #:nodoc:
3
- class_attribute :valid_options
4
- self.valid_options = [:class_name, :foreign_key, :select, :conditions, :include, :extend, :readonly, :validate]
16
+ class << self
17
+ attr_accessor :extensions
18
+ # TODO: This class accessor is needed to make activerecord-deprecated_finders work.
19
+ # We can move it to a constant in 5.0.
20
+ attr_accessor :valid_options
21
+ end
22
+ self.extensions = []
5
23
 
6
- # Set by subclasses
7
- class_attribute :macro
24
+ self.valid_options = [:class_name, :anonymous_class, :foreign_key, :validate]
8
25
 
9
- attr_reader :model, :name, :options, :reflection
26
+ attr_reader :name, :scope, :options
27
+
28
+ def self.build(model, name, scope, options, &block)
29
+ if model.dangerous_attribute_method?(name)
30
+ raise ArgumentError, "You tried to define an association named #{name} on the model #{model.name}, but " \
31
+ "this will conflict with a method #{name} already defined by Active Record. " \
32
+ "Please choose a different association name."
33
+ end
10
34
 
11
- def self.build(model, name, options)
12
- new(model, name, options).build
35
+ builder = create_builder model, name, scope, options, &block
36
+ reflection = builder.build(model)
37
+ define_accessors model, reflection
38
+ define_callbacks model, reflection
39
+ define_validations model, reflection
40
+ builder.define_extensions model
41
+ reflection
13
42
  end
14
43
 
15
- def initialize(model, name, options)
16
- @model, @name, @options = model, name, options
44
+ def self.create_builder(model, name, scope, options, &block)
45
+ raise ArgumentError, "association names must be a Symbol" unless name.kind_of?(Symbol)
46
+
47
+ new(model, name, scope, options, &block)
17
48
  end
18
49
 
19
- def build
50
+ def initialize(model, name, scope, options)
51
+ # TODO: Move this to create_builder as soon we drop support to activerecord-deprecated_finders.
52
+ if scope.is_a?(Hash)
53
+ options = scope
54
+ scope = nil
55
+ end
56
+
57
+ # TODO: Remove this model argument as soon we drop support to activerecord-deprecated_finders.
58
+ @name = name
59
+ @scope = scope
60
+ @options = options
61
+
20
62
  validate_options
21
- reflection = model.create_reflection(self.class.macro, name, options, model)
22
- define_accessors
23
- reflection
63
+
64
+ if scope && scope.arity == 0
65
+ @scope = proc { instance_exec(&scope) }
66
+ end
24
67
  end
25
68
 
26
- private
69
+ def build(model)
70
+ ActiveRecord::Reflection.create(macro, name, scope, options, model)
71
+ end
27
72
 
28
- def validate_options
29
- options.assert_valid_keys(self.class.valid_options)
73
+ def macro
74
+ raise NotImplementedError
75
+ end
76
+
77
+ def valid_options
78
+ Association.valid_options + Association.extensions.flat_map(&:valid_options)
79
+ end
80
+
81
+ def validate_options
82
+ options.assert_valid_keys(valid_options)
83
+ end
84
+
85
+ def define_extensions(model)
86
+ end
87
+
88
+ def self.define_callbacks(model, reflection)
89
+ if dependent = reflection.options[:dependent]
90
+ check_dependent_options(dependent)
91
+ add_destroy_callbacks(model, reflection)
30
92
  end
31
93
 
32
- def define_accessors
33
- define_readers
34
- define_writers
94
+ Association.extensions.each do |extension|
95
+ extension.build model, reflection
35
96
  end
97
+ end
36
98
 
37
- def define_readers
38
- name = self.name
99
+ # Defines the setter and getter methods for the association
100
+ # class Post < ActiveRecord::Base
101
+ # has_many :comments
102
+ # end
103
+ #
104
+ # Post.first.comments and Post.first.comments= methods are defined by this method...
105
+ def self.define_accessors(model, reflection)
106
+ mixin = model.generated_association_methods
107
+ name = reflection.name
108
+ define_readers(mixin, name)
109
+ define_writers(mixin, name)
110
+ end
39
111
 
40
- model.redefine_method(name) do |*params|
41
- association(name).reader(*params)
112
+ def self.define_readers(mixin, name)
113
+ mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
114
+ def #{name}(*args)
115
+ association(:#{name}).reader(*args)
42
116
  end
43
- end
44
-
45
- def define_writers
46
- name = self.name
117
+ CODE
118
+ end
47
119
 
48
- model.redefine_method("#{name}=") do |value|
49
- association(name).writer(value)
120
+ def self.define_writers(mixin, name)
121
+ mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
122
+ def #{name}=(value)
123
+ association(:#{name}).writer(value)
50
124
  end
125
+ CODE
126
+ end
127
+
128
+ def self.define_validations(model, reflection)
129
+ # noop
130
+ end
131
+
132
+ def self.valid_dependent_options
133
+ raise NotImplementedError
134
+ end
135
+
136
+ private
137
+
138
+ def self.check_dependent_options(dependent)
139
+ unless valid_dependent_options.include? dependent
140
+ raise ArgumentError, "The :dependent option must be one of #{valid_dependent_options}, but is :#{dependent}"
51
141
  end
142
+ end
143
+
144
+ def self.add_destroy_callbacks(model, reflection)
145
+ name = reflection.name
146
+ model.before_destroy lambda { |o| o.association(name).handle_dependency }
147
+ end
52
148
  end
53
149
  end