activerecord 4.2.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 (221) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +1372 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +218 -0
  5. data/examples/performance.rb +184 -0
  6. data/examples/simple.rb +14 -0
  7. data/lib/active_record.rb +173 -0
  8. data/lib/active_record/aggregations.rb +266 -0
  9. data/lib/active_record/association_relation.rb +22 -0
  10. data/lib/active_record/associations.rb +1724 -0
  11. data/lib/active_record/associations/alias_tracker.rb +87 -0
  12. data/lib/active_record/associations/association.rb +253 -0
  13. data/lib/active_record/associations/association_scope.rb +194 -0
  14. data/lib/active_record/associations/belongs_to_association.rb +111 -0
  15. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +40 -0
  16. data/lib/active_record/associations/builder/association.rb +149 -0
  17. data/lib/active_record/associations/builder/belongs_to.rb +116 -0
  18. data/lib/active_record/associations/builder/collection_association.rb +91 -0
  19. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +124 -0
  20. data/lib/active_record/associations/builder/has_many.rb +15 -0
  21. data/lib/active_record/associations/builder/has_one.rb +23 -0
  22. data/lib/active_record/associations/builder/singular_association.rb +38 -0
  23. data/lib/active_record/associations/collection_association.rb +634 -0
  24. data/lib/active_record/associations/collection_proxy.rb +1027 -0
  25. data/lib/active_record/associations/has_many_association.rb +184 -0
  26. data/lib/active_record/associations/has_many_through_association.rb +238 -0
  27. data/lib/active_record/associations/has_one_association.rb +105 -0
  28. data/lib/active_record/associations/has_one_through_association.rb +36 -0
  29. data/lib/active_record/associations/join_dependency.rb +282 -0
  30. data/lib/active_record/associations/join_dependency/join_association.rb +122 -0
  31. data/lib/active_record/associations/join_dependency/join_base.rb +22 -0
  32. data/lib/active_record/associations/join_dependency/join_part.rb +71 -0
  33. data/lib/active_record/associations/preloader.rb +203 -0
  34. data/lib/active_record/associations/preloader/association.rb +162 -0
  35. data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
  36. data/lib/active_record/associations/preloader/collection_association.rb +24 -0
  37. data/lib/active_record/associations/preloader/has_many.rb +17 -0
  38. data/lib/active_record/associations/preloader/has_many_through.rb +19 -0
  39. data/lib/active_record/associations/preloader/has_one.rb +23 -0
  40. data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
  41. data/lib/active_record/associations/preloader/singular_association.rb +21 -0
  42. data/lib/active_record/associations/preloader/through_association.rb +96 -0
  43. data/lib/active_record/associations/singular_association.rb +86 -0
  44. data/lib/active_record/associations/through_association.rb +96 -0
  45. data/lib/active_record/attribute.rb +149 -0
  46. data/lib/active_record/attribute_assignment.rb +212 -0
  47. data/lib/active_record/attribute_decorators.rb +66 -0
  48. data/lib/active_record/attribute_methods.rb +439 -0
  49. data/lib/active_record/attribute_methods/before_type_cast.rb +71 -0
  50. data/lib/active_record/attribute_methods/dirty.rb +181 -0
  51. data/lib/active_record/attribute_methods/primary_key.rb +128 -0
  52. data/lib/active_record/attribute_methods/query.rb +40 -0
  53. data/lib/active_record/attribute_methods/read.rb +103 -0
  54. data/lib/active_record/attribute_methods/serialization.rb +70 -0
  55. data/lib/active_record/attribute_methods/time_zone_conversion.rb +65 -0
  56. data/lib/active_record/attribute_methods/write.rb +83 -0
  57. data/lib/active_record/attribute_set.rb +77 -0
  58. data/lib/active_record/attribute_set/builder.rb +86 -0
  59. data/lib/active_record/attributes.rb +139 -0
  60. data/lib/active_record/autosave_association.rb +439 -0
  61. data/lib/active_record/base.rb +317 -0
  62. data/lib/active_record/callbacks.rb +313 -0
  63. data/lib/active_record/coders/json.rb +13 -0
  64. data/lib/active_record/coders/yaml_column.rb +38 -0
  65. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +659 -0
  66. data/lib/active_record/connection_adapters/abstract/database_limits.rb +67 -0
  67. data/lib/active_record/connection_adapters/abstract/database_statements.rb +373 -0
  68. data/lib/active_record/connection_adapters/abstract/query_cache.rb +95 -0
  69. data/lib/active_record/connection_adapters/abstract/quoting.rb +133 -0
  70. data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
  71. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +125 -0
  72. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +574 -0
  73. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +50 -0
  74. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +991 -0
  75. data/lib/active_record/connection_adapters/abstract/transaction.rb +219 -0
  76. data/lib/active_record/connection_adapters/abstract_adapter.rb +487 -0
  77. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +883 -0
  78. data/lib/active_record/connection_adapters/column.rb +82 -0
  79. data/lib/active_record/connection_adapters/connection_specification.rb +275 -0
  80. data/lib/active_record/connection_adapters/mysql2_adapter.rb +282 -0
  81. data/lib/active_record/connection_adapters/mysql_adapter.rb +491 -0
  82. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +93 -0
  83. data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
  84. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +232 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid.rb +36 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +99 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +14 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +27 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +17 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +79 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +15 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +97 -0
  108. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
  109. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  110. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  111. data/lib/active_record/connection_adapters/postgresql/quoting.rb +108 -0
  112. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  113. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +152 -0
  114. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +588 -0
  115. data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
  116. data/lib/active_record/connection_adapters/postgresql_adapter.rb +754 -0
  117. data/lib/active_record/connection_adapters/schema_cache.rb +94 -0
  118. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +628 -0
  119. data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
  120. data/lib/active_record/connection_handling.rb +132 -0
  121. data/lib/active_record/core.rb +566 -0
  122. data/lib/active_record/counter_cache.rb +175 -0
  123. data/lib/active_record/dynamic_matchers.rb +140 -0
  124. data/lib/active_record/enum.rb +198 -0
  125. data/lib/active_record/errors.rb +252 -0
  126. data/lib/active_record/explain.rb +38 -0
  127. data/lib/active_record/explain_registry.rb +30 -0
  128. data/lib/active_record/explain_subscriber.rb +29 -0
  129. data/lib/active_record/fixture_set/file.rb +56 -0
  130. data/lib/active_record/fixtures.rb +1007 -0
  131. data/lib/active_record/gem_version.rb +15 -0
  132. data/lib/active_record/inheritance.rb +247 -0
  133. data/lib/active_record/integration.rb +113 -0
  134. data/lib/active_record/locale/en.yml +47 -0
  135. data/lib/active_record/locking/optimistic.rb +204 -0
  136. data/lib/active_record/locking/pessimistic.rb +77 -0
  137. data/lib/active_record/log_subscriber.rb +75 -0
  138. data/lib/active_record/migration.rb +1051 -0
  139. data/lib/active_record/migration/command_recorder.rb +197 -0
  140. data/lib/active_record/migration/join_table.rb +15 -0
  141. data/lib/active_record/model_schema.rb +340 -0
  142. data/lib/active_record/nested_attributes.rb +548 -0
  143. data/lib/active_record/no_touching.rb +52 -0
  144. data/lib/active_record/null_relation.rb +81 -0
  145. data/lib/active_record/persistence.rb +532 -0
  146. data/lib/active_record/query_cache.rb +56 -0
  147. data/lib/active_record/querying.rb +68 -0
  148. data/lib/active_record/railtie.rb +162 -0
  149. data/lib/active_record/railties/console_sandbox.rb +5 -0
  150. data/lib/active_record/railties/controller_runtime.rb +50 -0
  151. data/lib/active_record/railties/databases.rake +391 -0
  152. data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
  153. data/lib/active_record/readonly_attributes.rb +23 -0
  154. data/lib/active_record/reflection.rb +881 -0
  155. data/lib/active_record/relation.rb +681 -0
  156. data/lib/active_record/relation/batches.rb +138 -0
  157. data/lib/active_record/relation/calculations.rb +403 -0
  158. data/lib/active_record/relation/delegation.rb +140 -0
  159. data/lib/active_record/relation/finder_methods.rb +528 -0
  160. data/lib/active_record/relation/merger.rb +170 -0
  161. data/lib/active_record/relation/predicate_builder.rb +126 -0
  162. data/lib/active_record/relation/predicate_builder/array_handler.rb +47 -0
  163. data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
  164. data/lib/active_record/relation/query_methods.rb +1176 -0
  165. data/lib/active_record/relation/spawn_methods.rb +75 -0
  166. data/lib/active_record/result.rb +131 -0
  167. data/lib/active_record/runtime_registry.rb +22 -0
  168. data/lib/active_record/sanitization.rb +191 -0
  169. data/lib/active_record/schema.rb +64 -0
  170. data/lib/active_record/schema_dumper.rb +251 -0
  171. data/lib/active_record/schema_migration.rb +56 -0
  172. data/lib/active_record/scoping.rb +87 -0
  173. data/lib/active_record/scoping/default.rb +134 -0
  174. data/lib/active_record/scoping/named.rb +164 -0
  175. data/lib/active_record/serialization.rb +22 -0
  176. data/lib/active_record/serializers/xml_serializer.rb +193 -0
  177. data/lib/active_record/statement_cache.rb +111 -0
  178. data/lib/active_record/store.rb +205 -0
  179. data/lib/active_record/tasks/database_tasks.rb +296 -0
  180. data/lib/active_record/tasks/mysql_database_tasks.rb +145 -0
  181. data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
  182. data/lib/active_record/tasks/sqlite_database_tasks.rb +55 -0
  183. data/lib/active_record/timestamp.rb +121 -0
  184. data/lib/active_record/transactions.rb +417 -0
  185. data/lib/active_record/translation.rb +22 -0
  186. data/lib/active_record/type.rb +23 -0
  187. data/lib/active_record/type/big_integer.rb +13 -0
  188. data/lib/active_record/type/binary.rb +50 -0
  189. data/lib/active_record/type/boolean.rb +30 -0
  190. data/lib/active_record/type/date.rb +46 -0
  191. data/lib/active_record/type/date_time.rb +43 -0
  192. data/lib/active_record/type/decimal.rb +40 -0
  193. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  194. data/lib/active_record/type/decorator.rb +14 -0
  195. data/lib/active_record/type/float.rb +19 -0
  196. data/lib/active_record/type/hash_lookup_type_map.rb +17 -0
  197. data/lib/active_record/type/integer.rb +55 -0
  198. data/lib/active_record/type/mutable.rb +16 -0
  199. data/lib/active_record/type/numeric.rb +36 -0
  200. data/lib/active_record/type/serialized.rb +56 -0
  201. data/lib/active_record/type/string.rb +36 -0
  202. data/lib/active_record/type/text.rb +11 -0
  203. data/lib/active_record/type/time.rb +26 -0
  204. data/lib/active_record/type/time_value.rb +38 -0
  205. data/lib/active_record/type/type_map.rb +64 -0
  206. data/lib/active_record/type/unsigned_integer.rb +15 -0
  207. data/lib/active_record/type/value.rb +101 -0
  208. data/lib/active_record/validations.rb +90 -0
  209. data/lib/active_record/validations/associated.rb +51 -0
  210. data/lib/active_record/validations/presence.rb +67 -0
  211. data/lib/active_record/validations/uniqueness.rb +229 -0
  212. data/lib/active_record/version.rb +8 -0
  213. data/lib/rails/generators/active_record.rb +17 -0
  214. data/lib/rails/generators/active_record/migration.rb +18 -0
  215. data/lib/rails/generators/active_record/migration/migration_generator.rb +70 -0
  216. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +22 -0
  217. data/lib/rails/generators/active_record/migration/templates/migration.rb +45 -0
  218. data/lib/rails/generators/active_record/model/model_generator.rb +52 -0
  219. data/lib/rails/generators/active_record/model/templates/model.rb +10 -0
  220. data/lib/rails/generators/active_record/model/templates/module.rb +7 -0
  221. metadata +309 -0
@@ -0,0 +1,87 @@
1
+ require 'active_support/core_ext/string/conversions'
2
+
3
+ module ActiveRecord
4
+ module Associations
5
+ # Keeps track of table aliases for ActiveRecord::Associations::ClassMethods::JoinDependency and
6
+ # ActiveRecord::Associations::ThroughAssociationScope
7
+ class AliasTracker # :nodoc:
8
+ attr_reader :aliases, :connection
9
+
10
+ def self.empty(connection)
11
+ new connection, Hash.new(0)
12
+ end
13
+
14
+ def self.create(connection, table_joins)
15
+ if table_joins.empty?
16
+ empty connection
17
+ else
18
+ aliases = Hash.new { |h,k|
19
+ h[k] = initial_count_for(connection, k, table_joins)
20
+ }
21
+ new connection, aliases
22
+ end
23
+ end
24
+
25
+ def self.initial_count_for(connection, name, table_joins)
26
+ # quoted_name should be downcased as some database adapters (Oracle) return quoted name in uppercase
27
+ quoted_name = connection.quote_table_name(name).downcase
28
+
29
+ counts = table_joins.map do |join|
30
+ if join.is_a?(Arel::Nodes::StringJoin)
31
+ # Table names + table aliases
32
+ join.left.downcase.scan(
33
+ /join(?:\s+\w+)?\s+(\S+\s+)?#{quoted_name}\son/
34
+ ).size
35
+ elsif join.respond_to? :left
36
+ join.left.table_name == name ? 1 : 0
37
+ else
38
+ # this branch is reached by two tests:
39
+ #
40
+ # activerecord/test/cases/associations/cascaded_eager_loading_test.rb:37
41
+ # with :posts
42
+ #
43
+ # activerecord/test/cases/associations/eager_test.rb:1133
44
+ # with :comments
45
+ #
46
+ 0
47
+ end
48
+ end
49
+
50
+ counts.sum
51
+ end
52
+
53
+ # table_joins is an array of arel joins which might conflict with the aliases we assign here
54
+ def initialize(connection, aliases)
55
+ @aliases = aliases
56
+ @connection = connection
57
+ end
58
+
59
+ def aliased_table_for(table_name, aliased_name)
60
+ if aliases[table_name].zero?
61
+ # If it's zero, we can have our table_name
62
+ aliases[table_name] = 1
63
+ Arel::Table.new(table_name)
64
+ else
65
+ # Otherwise, we need to use an alias
66
+ aliased_name = connection.table_alias_for(aliased_name)
67
+
68
+ # Update the count
69
+ aliases[aliased_name] += 1
70
+
71
+ table_alias = if aliases[aliased_name] > 1
72
+ "#{truncate(aliased_name)}_#{aliases[aliased_name]}"
73
+ else
74
+ aliased_name
75
+ end
76
+ Arel::Table.new(table_name).alias(table_alias)
77
+ end
78
+ end
79
+
80
+ private
81
+
82
+ def truncate(name)
83
+ name.slice(0, connection.table_alias_length - 2)
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,253 @@
1
+ require 'active_support/core_ext/array/wrap'
2
+
3
+ module ActiveRecord
4
+ module Associations
5
+ # = Active Record Associations
6
+ #
7
+ # This is the root class of all associations ('+ Foo' signifies an included module Foo):
8
+ #
9
+ # Association
10
+ # SingularAssociation
11
+ # HasOneAssociation
12
+ # HasOneThroughAssociation + ThroughAssociation
13
+ # BelongsToAssociation
14
+ # BelongsToPolymorphicAssociation
15
+ # CollectionAssociation
16
+ # HasManyAssociation
17
+ # HasManyThroughAssociation + ThroughAssociation
18
+ class Association #:nodoc:
19
+ attr_reader :owner, :target, :reflection
20
+ attr_accessor :inversed
21
+
22
+ delegate :options, :to => :reflection
23
+
24
+ def initialize(owner, reflection)
25
+ reflection.check_validity!
26
+
27
+ @owner, @reflection = owner, reflection
28
+
29
+ reset
30
+ reset_scope
31
+ end
32
+
33
+ # Returns the name of the table of the associated class:
34
+ #
35
+ # post.comments.aliased_table_name # => "comments"
36
+ #
37
+ def aliased_table_name
38
+ klass.table_name
39
+ end
40
+
41
+ # Resets the \loaded flag to +false+ and sets the \target to +nil+.
42
+ def reset
43
+ @loaded = false
44
+ @target = nil
45
+ @stale_state = nil
46
+ @inversed = false
47
+ end
48
+
49
+ # Reloads the \target and returns +self+ on success.
50
+ def reload
51
+ reset
52
+ reset_scope
53
+ load_target
54
+ self unless target.nil?
55
+ end
56
+
57
+ # Has the \target been already \loaded?
58
+ def loaded?
59
+ @loaded
60
+ end
61
+
62
+ # Asserts the \target has been loaded setting the \loaded flag to +true+.
63
+ def loaded!
64
+ @loaded = true
65
+ @stale_state = stale_state
66
+ @inversed = false
67
+ end
68
+
69
+ # The target is stale if the target no longer points to the record(s) that the
70
+ # relevant foreign_key(s) refers to. If stale, the association accessor method
71
+ # on the owner will reload the target. It's up to subclasses to implement the
72
+ # stale_state method if relevant.
73
+ #
74
+ # Note that if the target has not been loaded, it is not considered stale.
75
+ def stale_target?
76
+ !inversed && loaded? && @stale_state != stale_state
77
+ end
78
+
79
+ # Sets the target of this association to <tt>\target</tt>, and the \loaded flag to +true+.
80
+ def target=(target)
81
+ @target = target
82
+ loaded!
83
+ end
84
+
85
+ def scope
86
+ target_scope.merge(association_scope)
87
+ end
88
+
89
+ # The scope for this association.
90
+ #
91
+ # Note that the association_scope is merged into the target_scope only when the
92
+ # scope method is called. This is because at that point the call may be surrounded
93
+ # by scope.scoping { ... } or with_scope { ... } etc, which affects the scope which
94
+ # actually gets built.
95
+ def association_scope
96
+ if klass
97
+ @association_scope ||= AssociationScope.scope(self, klass.connection)
98
+ end
99
+ end
100
+
101
+ def reset_scope
102
+ @association_scope = nil
103
+ end
104
+
105
+ # Set the inverse association, if possible
106
+ def set_inverse_instance(record)
107
+ if invertible_for?(record)
108
+ inverse = record.association(inverse_reflection_for(record).name)
109
+ inverse.target = owner
110
+ inverse.inversed = true
111
+ end
112
+ record
113
+ end
114
+
115
+ # Returns the class of the target. belongs_to polymorphic overrides this to look at the
116
+ # polymorphic_type field on the owner.
117
+ def klass
118
+ reflection.klass
119
+ end
120
+
121
+ # Can be overridden (i.e. in ThroughAssociation) to merge in other scopes (i.e. the
122
+ # through association's scope)
123
+ def target_scope
124
+ AssociationRelation.create(klass, klass.arel_table, self).merge!(klass.all)
125
+ end
126
+
127
+ # Loads the \target if needed and returns it.
128
+ #
129
+ # This method is abstract in the sense that it relies on +find_target+,
130
+ # which is expected to be provided by descendants.
131
+ #
132
+ # If the \target is already \loaded it is just returned. Thus, you can call
133
+ # +load_target+ unconditionally to get the \target.
134
+ #
135
+ # ActiveRecord::RecordNotFound is rescued within the method, and it is
136
+ # not reraised. The proxy is \reset and +nil+ is the return value.
137
+ def load_target
138
+ @target = find_target if (@stale_state && stale_target?) || find_target?
139
+
140
+ loaded! unless loaded?
141
+ target
142
+ rescue ActiveRecord::RecordNotFound
143
+ reset
144
+ end
145
+
146
+ def interpolate(sql, record = nil)
147
+ if sql.respond_to?(:to_proc)
148
+ owner.instance_exec(record, &sql)
149
+ else
150
+ sql
151
+ end
152
+ end
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) #:nodoc:
167
+ skip_assign = [reflection.foreign_key, reflection.type].compact
168
+ attributes = create_scope.except(*(record.changed - skip_assign))
169
+ record.assign_attributes(attributes)
170
+ set_inverse_instance(record)
171
+ end
172
+
173
+ private
174
+
175
+ def find_target?
176
+ !loaded? && (!owner.new_record? || foreign_key_present?) && klass
177
+ end
178
+
179
+ def creation_attributes
180
+ attributes = {}
181
+
182
+ if (reflection.has_one? || reflection.collection?) && !options[:through]
183
+ attributes[reflection.foreign_key] = owner[reflection.active_record_primary_key]
184
+
185
+ if reflection.options[:as]
186
+ attributes[reflection.type] = owner.class.base_class.name
187
+ end
188
+ end
189
+
190
+ attributes
191
+ end
192
+
193
+ # Sets the owner attributes on the given record
194
+ def set_owner_attributes(record)
195
+ creation_attributes.each { |key, value| record[key] = value }
196
+ end
197
+
198
+ # Returns true if there is a foreign key present on the owner which
199
+ # references the target. This is used to determine whether we can load
200
+ # the target if the owner is currently a new record (and therefore
201
+ # without a key). If the owner is a new record then foreign_key must
202
+ # be present in order to load target.
203
+ #
204
+ # Currently implemented by belongs_to (vanilla and polymorphic) and
205
+ # has_one/has_many :through associations which go through a belongs_to.
206
+ def foreign_key_present?
207
+ false
208
+ end
209
+
210
+ # Raises ActiveRecord::AssociationTypeMismatch unless +record+ is of
211
+ # the kind of the class of the associated objects. Meant to be used as
212
+ # a sanity check when you are about to assign an associated record.
213
+ def raise_on_type_mismatch!(record)
214
+ unless record.is_a?(reflection.klass) || record.is_a?(reflection.class_name.constantize)
215
+ message = "#{reflection.class_name}(##{reflection.klass.object_id}) expected, got #{record.class}(##{record.class.object_id})"
216
+ raise ActiveRecord::AssociationTypeMismatch, message
217
+ end
218
+ end
219
+
220
+ # Can be redefined by subclasses, notably polymorphic belongs_to
221
+ # The record parameter is necessary to support polymorphic inverses as we must check for
222
+ # the association in the specific class of the record.
223
+ def inverse_reflection_for(record)
224
+ reflection.inverse_of
225
+ end
226
+
227
+ # Returns true if inverse association on the given record needs to be set.
228
+ # This method is redefined by subclasses.
229
+ def invertible_for?(record)
230
+ foreign_key_for?(record) && inverse_reflection_for(record)
231
+ end
232
+
233
+ # Returns true if record contains the foreign_key
234
+ def foreign_key_for?(record)
235
+ record.has_attribute?(reflection.foreign_key)
236
+ end
237
+
238
+ # This should be implemented to return the values of the relevant key(s) on the owner,
239
+ # so that when stale_state is different from the value stored on the last find_target,
240
+ # the target is stale.
241
+ #
242
+ # This is only relevant to certain associations, which is why it returns nil by default.
243
+ def stale_state
244
+ end
245
+
246
+ def build_record(attributes)
247
+ reflection.build_association(attributes) do |record|
248
+ initialize_attributes(record)
249
+ end
250
+ end
251
+ end
252
+ end
253
+ end
@@ -0,0 +1,194 @@
1
+ module ActiveRecord
2
+ module Associations
3
+ class AssociationScope #:nodoc:
4
+ def self.scope(association, connection)
5
+ INSTANCE.scope association, connection
6
+ end
7
+
8
+ class BindSubstitution
9
+ def initialize(block)
10
+ @block = block
11
+ end
12
+
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
19
+
20
+ def self.create(&block)
21
+ block = block ? block : lambda { |val| val }
22
+ new BindSubstitution.new(block)
23
+ end
24
+
25
+ def initialize(bind_substitution)
26
+ @bind_substitution = bind_substitution
27
+ end
28
+
29
+ INSTANCE = create
30
+
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
37
+
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
53
+ end
54
+
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
61
+ end
62
+
63
+ private
64
+
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
+ )
71
+ end
72
+ end
73
+
74
+ def table_alias_for(reflection, refl, join = false)
75
+ name = "#{reflection.plural_name}_#{alias_suffix(refl)}"
76
+ name << "_join" if join
77
+ name
78
+ end
79
+
80
+ def join(table, constraint)
81
+ table.create_join(table, table.create_on(constraint), join_type)
82
+ end
83
+
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
88
+
89
+ def bind_value(scope, column, value, alias_tracker)
90
+ @bind_substitution.bind_value scope, column, value, alias_tracker
91
+ end
92
+
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
97
+
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
102
+
103
+ bind_val = bind scope, table.table_name, key.to_s, owner[foreign_key], tracker
104
+ scope = scope.where(table[key].eq(bind_val))
105
+
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
114
+
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
119
+
120
+ constraint = table[key].eq(foreign_table[foreign_key])
121
+
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
127
+
128
+ scope = scope.joins(join(foreign_table, constraint))
129
+ end
130
+
131
+ def add_constraints(scope, owner, assoc_klass, refl, tracker)
132
+ chain = refl.chain
133
+ scope_chain = refl.scope_chain
134
+
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
151
+
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)
156
+
157
+ if scope_chain_item == refl.scope
158
+ scope.merge! item.except(:where, :includes, :bind)
159
+ end
160
+
161
+ if is_first_chain
162
+ scope.includes! item.includes_values
163
+ end
164
+
165
+ scope.where_values += item.where_values
166
+ scope.bind_values += item.bind_values
167
+ scope.order_values |= item.order_values
168
+ end
169
+ end
170
+
171
+ scope
172
+ end
173
+
174
+ def alias_suffix(refl)
175
+ refl.name
176
+ end
177
+
178
+ def table_name_for(reflection, klass, refl)
179
+ if reflection == refl
180
+ # If this is a polymorphic belongs_to, we want to get the klass from the
181
+ # association because it depends on the polymorphic_type attribute of
182
+ # the owner
183
+ klass.table_name
184
+ else
185
+ reflection.table_name
186
+ end
187
+ end
188
+
189
+ def eval_scope(klass, scope, owner)
190
+ klass.unscoped.instance_exec(owner, &scope)
191
+ end
192
+ end
193
+ end
194
+ end