activerecord 5.2.3

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 (244) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +937 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +217 -0
  5. data/examples/performance.rb +185 -0
  6. data/examples/simple.rb +15 -0
  7. data/lib/active_record.rb +188 -0
  8. data/lib/active_record/aggregations.rb +283 -0
  9. data/lib/active_record/association_relation.rb +40 -0
  10. data/lib/active_record/associations.rb +1860 -0
  11. data/lib/active_record/associations/alias_tracker.rb +81 -0
  12. data/lib/active_record/associations/association.rb +299 -0
  13. data/lib/active_record/associations/association_scope.rb +168 -0
  14. data/lib/active_record/associations/belongs_to_association.rb +130 -0
  15. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +40 -0
  16. data/lib/active_record/associations/builder/association.rb +140 -0
  17. data/lib/active_record/associations/builder/belongs_to.rb +163 -0
  18. data/lib/active_record/associations/builder/collection_association.rb +82 -0
  19. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +135 -0
  20. data/lib/active_record/associations/builder/has_many.rb +17 -0
  21. data/lib/active_record/associations/builder/has_one.rb +30 -0
  22. data/lib/active_record/associations/builder/singular_association.rb +42 -0
  23. data/lib/active_record/associations/collection_association.rb +513 -0
  24. data/lib/active_record/associations/collection_proxy.rb +1131 -0
  25. data/lib/active_record/associations/foreign_association.rb +13 -0
  26. data/lib/active_record/associations/has_many_association.rb +144 -0
  27. data/lib/active_record/associations/has_many_through_association.rb +227 -0
  28. data/lib/active_record/associations/has_one_association.rb +120 -0
  29. data/lib/active_record/associations/has_one_through_association.rb +45 -0
  30. data/lib/active_record/associations/join_dependency.rb +262 -0
  31. data/lib/active_record/associations/join_dependency/join_association.rb +60 -0
  32. data/lib/active_record/associations/join_dependency/join_base.rb +23 -0
  33. data/lib/active_record/associations/join_dependency/join_part.rb +71 -0
  34. data/lib/active_record/associations/preloader.rb +193 -0
  35. data/lib/active_record/associations/preloader/association.rb +131 -0
  36. data/lib/active_record/associations/preloader/through_association.rb +107 -0
  37. data/lib/active_record/associations/singular_association.rb +73 -0
  38. data/lib/active_record/associations/through_association.rb +121 -0
  39. data/lib/active_record/attribute_assignment.rb +88 -0
  40. data/lib/active_record/attribute_decorators.rb +90 -0
  41. data/lib/active_record/attribute_methods.rb +492 -0
  42. data/lib/active_record/attribute_methods/before_type_cast.rb +78 -0
  43. data/lib/active_record/attribute_methods/dirty.rb +150 -0
  44. data/lib/active_record/attribute_methods/primary_key.rb +143 -0
  45. data/lib/active_record/attribute_methods/query.rb +42 -0
  46. data/lib/active_record/attribute_methods/read.rb +85 -0
  47. data/lib/active_record/attribute_methods/serialization.rb +90 -0
  48. data/lib/active_record/attribute_methods/time_zone_conversion.rb +91 -0
  49. data/lib/active_record/attribute_methods/write.rb +68 -0
  50. data/lib/active_record/attributes.rb +266 -0
  51. data/lib/active_record/autosave_association.rb +498 -0
  52. data/lib/active_record/base.rb +329 -0
  53. data/lib/active_record/callbacks.rb +353 -0
  54. data/lib/active_record/coders/json.rb +15 -0
  55. data/lib/active_record/coders/yaml_column.rb +50 -0
  56. data/lib/active_record/collection_cache_key.rb +53 -0
  57. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +1068 -0
  58. data/lib/active_record/connection_adapters/abstract/database_limits.rb +72 -0
  59. data/lib/active_record/connection_adapters/abstract/database_statements.rb +540 -0
  60. data/lib/active_record/connection_adapters/abstract/query_cache.rb +145 -0
  61. data/lib/active_record/connection_adapters/abstract/quoting.rb +200 -0
  62. data/lib/active_record/connection_adapters/abstract/savepoints.rb +23 -0
  63. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +146 -0
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +685 -0
  65. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +95 -0
  66. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +1396 -0
  67. data/lib/active_record/connection_adapters/abstract/transaction.rb +283 -0
  68. data/lib/active_record/connection_adapters/abstract_adapter.rb +628 -0
  69. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +887 -0
  70. data/lib/active_record/connection_adapters/column.rb +91 -0
  71. data/lib/active_record/connection_adapters/connection_specification.rb +287 -0
  72. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +33 -0
  73. data/lib/active_record/connection_adapters/mysql/column.rb +27 -0
  74. data/lib/active_record/connection_adapters/mysql/database_statements.rb +140 -0
  75. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +72 -0
  76. data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -0
  77. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +73 -0
  78. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +87 -0
  79. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +80 -0
  80. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +148 -0
  81. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +35 -0
  82. data/lib/active_record/connection_adapters/mysql2_adapter.rb +129 -0
  83. data/lib/active_record/connection_adapters/postgresql/column.rb +44 -0
  84. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +163 -0
  85. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid.rb +34 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +92 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +56 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +15 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +17 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +50 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +23 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +23 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +15 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +21 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +71 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +15 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +15 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +45 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +41 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +15 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +65 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +97 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +18 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +111 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +23 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +28 -0
  108. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +30 -0
  109. data/lib/active_record/connection_adapters/postgresql/quoting.rb +168 -0
  110. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +43 -0
  111. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +65 -0
  112. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +206 -0
  113. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +50 -0
  114. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +774 -0
  115. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +39 -0
  116. data/lib/active_record/connection_adapters/postgresql/utils.rb +81 -0
  117. data/lib/active_record/connection_adapters/postgresql_adapter.rb +863 -0
  118. data/lib/active_record/connection_adapters/schema_cache.rb +118 -0
  119. data/lib/active_record/connection_adapters/sql_type_metadata.rb +34 -0
  120. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
  121. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +67 -0
  122. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +17 -0
  123. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +19 -0
  124. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +18 -0
  125. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +106 -0
  126. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +573 -0
  127. data/lib/active_record/connection_adapters/statement_pool.rb +61 -0
  128. data/lib/active_record/connection_handling.rb +145 -0
  129. data/lib/active_record/core.rb +559 -0
  130. data/lib/active_record/counter_cache.rb +218 -0
  131. data/lib/active_record/define_callbacks.rb +22 -0
  132. data/lib/active_record/dynamic_matchers.rb +122 -0
  133. data/lib/active_record/enum.rb +244 -0
  134. data/lib/active_record/errors.rb +380 -0
  135. data/lib/active_record/explain.rb +50 -0
  136. data/lib/active_record/explain_registry.rb +32 -0
  137. data/lib/active_record/explain_subscriber.rb +34 -0
  138. data/lib/active_record/fixture_set/file.rb +82 -0
  139. data/lib/active_record/fixtures.rb +1065 -0
  140. data/lib/active_record/gem_version.rb +17 -0
  141. data/lib/active_record/inheritance.rb +283 -0
  142. data/lib/active_record/integration.rb +155 -0
  143. data/lib/active_record/internal_metadata.rb +45 -0
  144. data/lib/active_record/legacy_yaml_adapter.rb +48 -0
  145. data/lib/active_record/locale/en.yml +48 -0
  146. data/lib/active_record/locking/optimistic.rb +198 -0
  147. data/lib/active_record/locking/pessimistic.rb +89 -0
  148. data/lib/active_record/log_subscriber.rb +137 -0
  149. data/lib/active_record/migration.rb +1378 -0
  150. data/lib/active_record/migration/command_recorder.rb +240 -0
  151. data/lib/active_record/migration/compatibility.rb +217 -0
  152. data/lib/active_record/migration/join_table.rb +17 -0
  153. data/lib/active_record/model_schema.rb +521 -0
  154. data/lib/active_record/nested_attributes.rb +600 -0
  155. data/lib/active_record/no_touching.rb +58 -0
  156. data/lib/active_record/null_relation.rb +68 -0
  157. data/lib/active_record/persistence.rb +763 -0
  158. data/lib/active_record/query_cache.rb +45 -0
  159. data/lib/active_record/querying.rb +70 -0
  160. data/lib/active_record/railtie.rb +226 -0
  161. data/lib/active_record/railties/console_sandbox.rb +7 -0
  162. data/lib/active_record/railties/controller_runtime.rb +56 -0
  163. data/lib/active_record/railties/databases.rake +377 -0
  164. data/lib/active_record/readonly_attributes.rb +24 -0
  165. data/lib/active_record/reflection.rb +1044 -0
  166. data/lib/active_record/relation.rb +629 -0
  167. data/lib/active_record/relation/batches.rb +287 -0
  168. data/lib/active_record/relation/batches/batch_enumerator.rb +69 -0
  169. data/lib/active_record/relation/calculations.rb +417 -0
  170. data/lib/active_record/relation/delegation.rb +147 -0
  171. data/lib/active_record/relation/finder_methods.rb +565 -0
  172. data/lib/active_record/relation/from_clause.rb +26 -0
  173. data/lib/active_record/relation/merger.rb +193 -0
  174. data/lib/active_record/relation/predicate_builder.rb +152 -0
  175. data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
  176. data/lib/active_record/relation/predicate_builder/association_query_value.rb +46 -0
  177. data/lib/active_record/relation/predicate_builder/base_handler.rb +19 -0
  178. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +20 -0
  179. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +56 -0
  180. data/lib/active_record/relation/predicate_builder/range_handler.rb +42 -0
  181. data/lib/active_record/relation/predicate_builder/relation_handler.rb +19 -0
  182. data/lib/active_record/relation/query_attribute.rb +45 -0
  183. data/lib/active_record/relation/query_methods.rb +1231 -0
  184. data/lib/active_record/relation/record_fetch_warning.rb +51 -0
  185. data/lib/active_record/relation/spawn_methods.rb +77 -0
  186. data/lib/active_record/relation/where_clause.rb +186 -0
  187. data/lib/active_record/relation/where_clause_factory.rb +34 -0
  188. data/lib/active_record/result.rb +149 -0
  189. data/lib/active_record/runtime_registry.rb +24 -0
  190. data/lib/active_record/sanitization.rb +222 -0
  191. data/lib/active_record/schema.rb +70 -0
  192. data/lib/active_record/schema_dumper.rb +255 -0
  193. data/lib/active_record/schema_migration.rb +56 -0
  194. data/lib/active_record/scoping.rb +106 -0
  195. data/lib/active_record/scoping/default.rb +152 -0
  196. data/lib/active_record/scoping/named.rb +213 -0
  197. data/lib/active_record/secure_token.rb +40 -0
  198. data/lib/active_record/serialization.rb +22 -0
  199. data/lib/active_record/statement_cache.rb +121 -0
  200. data/lib/active_record/store.rb +211 -0
  201. data/lib/active_record/suppressor.rb +61 -0
  202. data/lib/active_record/table_metadata.rb +82 -0
  203. data/lib/active_record/tasks/database_tasks.rb +337 -0
  204. data/lib/active_record/tasks/mysql_database_tasks.rb +115 -0
  205. data/lib/active_record/tasks/postgresql_database_tasks.rb +143 -0
  206. data/lib/active_record/tasks/sqlite_database_tasks.rb +83 -0
  207. data/lib/active_record/timestamp.rb +153 -0
  208. data/lib/active_record/touch_later.rb +64 -0
  209. data/lib/active_record/transactions.rb +502 -0
  210. data/lib/active_record/translation.rb +24 -0
  211. data/lib/active_record/type.rb +79 -0
  212. data/lib/active_record/type/adapter_specific_registry.rb +136 -0
  213. data/lib/active_record/type/date.rb +9 -0
  214. data/lib/active_record/type/date_time.rb +9 -0
  215. data/lib/active_record/type/decimal_without_scale.rb +15 -0
  216. data/lib/active_record/type/hash_lookup_type_map.rb +25 -0
  217. data/lib/active_record/type/internal/timezone.rb +17 -0
  218. data/lib/active_record/type/json.rb +30 -0
  219. data/lib/active_record/type/serialized.rb +71 -0
  220. data/lib/active_record/type/text.rb +11 -0
  221. data/lib/active_record/type/time.rb +21 -0
  222. data/lib/active_record/type/type_map.rb +62 -0
  223. data/lib/active_record/type/unsigned_integer.rb +17 -0
  224. data/lib/active_record/type_caster.rb +9 -0
  225. data/lib/active_record/type_caster/connection.rb +33 -0
  226. data/lib/active_record/type_caster/map.rb +23 -0
  227. data/lib/active_record/validations.rb +93 -0
  228. data/lib/active_record/validations/absence.rb +25 -0
  229. data/lib/active_record/validations/associated.rb +60 -0
  230. data/lib/active_record/validations/length.rb +26 -0
  231. data/lib/active_record/validations/presence.rb +68 -0
  232. data/lib/active_record/validations/uniqueness.rb +238 -0
  233. data/lib/active_record/version.rb +10 -0
  234. data/lib/rails/generators/active_record.rb +19 -0
  235. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +27 -0
  236. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +5 -0
  237. data/lib/rails/generators/active_record/migration.rb +35 -0
  238. data/lib/rails/generators/active_record/migration/migration_generator.rb +78 -0
  239. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +24 -0
  240. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +46 -0
  241. data/lib/rails/generators/active_record/model/model_generator.rb +48 -0
  242. data/lib/rails/generators/active_record/model/templates/model.rb.tt +13 -0
  243. data/lib/rails/generators/active_record/model/templates/module.rb.tt +7 -0
  244. metadata +333 -0
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/string/conversions"
4
+
5
+ module ActiveRecord
6
+ module Associations
7
+ # Keeps track of table aliases for ActiveRecord::Associations::JoinDependency
8
+ class AliasTracker # :nodoc:
9
+ def self.create(connection, initial_table, joins)
10
+ if joins.empty?
11
+ aliases = Hash.new(0)
12
+ else
13
+ aliases = Hash.new { |h, k|
14
+ h[k] = initial_count_for(connection, k, joins)
15
+ }
16
+ end
17
+ aliases[initial_table] = 1
18
+ new(connection, aliases)
19
+ end
20
+
21
+ def self.initial_count_for(connection, name, table_joins)
22
+ quoted_name = nil
23
+
24
+ counts = table_joins.map do |join|
25
+ if join.is_a?(Arel::Nodes::StringJoin)
26
+ # quoted_name should be case ignored as some database adapters (Oracle) return quoted name in uppercase
27
+ quoted_name ||= connection.quote_table_name(name)
28
+
29
+ # Table names + table aliases
30
+ join.left.scan(
31
+ /JOIN(?:\s+\w+)?\s+(?:\S+\s+)?(?:#{quoted_name}|#{name})\sON/i
32
+ ).size
33
+ elsif join.is_a?(Arel::Nodes::Join)
34
+ join.left.name == name ? 1 : 0
35
+ elsif join.is_a?(Hash)
36
+ join[name]
37
+ else
38
+ raise ArgumentError, "joins list should be initialized by list of Arel::Nodes::Join"
39
+ end
40
+ end
41
+
42
+ counts.sum
43
+ end
44
+
45
+ # table_joins is an array of arel joins which might conflict with the aliases we assign here
46
+ def initialize(connection, aliases)
47
+ @aliases = aliases
48
+ @connection = connection
49
+ end
50
+
51
+ def aliased_table_for(table_name, aliased_name, type_caster)
52
+ if aliases[table_name].zero?
53
+ # If it's zero, we can have our table_name
54
+ aliases[table_name] = 1
55
+ Arel::Table.new(table_name, type_caster: type_caster)
56
+ else
57
+ # Otherwise, we need to use an alias
58
+ aliased_name = @connection.table_alias_for(aliased_name)
59
+
60
+ # Update the count
61
+ aliases[aliased_name] += 1
62
+
63
+ table_alias = if aliases[aliased_name] > 1
64
+ "#{truncate(aliased_name)}_#{aliases[aliased_name]}"
65
+ else
66
+ aliased_name
67
+ end
68
+ Arel::Table.new(table_name, type_caster: type_caster).alias(table_alias)
69
+ end
70
+ end
71
+
72
+ attr_reader :aliases
73
+
74
+ private
75
+
76
+ def truncate(name)
77
+ name.slice(0, @connection.table_alias_length - 2)
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,299 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/array/wrap"
4
+
5
+ module ActiveRecord
6
+ module Associations
7
+ # = Active Record Associations
8
+ #
9
+ # This is the root class of all associations ('+ Foo' signifies an included module Foo):
10
+ #
11
+ # Association
12
+ # SingularAssociation
13
+ # HasOneAssociation + ForeignAssociation
14
+ # HasOneThroughAssociation + ThroughAssociation
15
+ # BelongsToAssociation
16
+ # BelongsToPolymorphicAssociation
17
+ # CollectionAssociation
18
+ # HasManyAssociation + ForeignAssociation
19
+ # HasManyThroughAssociation + ThroughAssociation
20
+ class Association #:nodoc:
21
+ attr_reader :owner, :target, :reflection
22
+
23
+ delegate :options, to: :reflection
24
+
25
+ def initialize(owner, reflection)
26
+ reflection.check_validity!
27
+
28
+ @owner, @reflection = owner, reflection
29
+
30
+ reset
31
+ reset_scope
32
+ end
33
+
34
+ # Resets the \loaded flag to +false+ and sets the \target to +nil+.
35
+ def reset
36
+ @loaded = false
37
+ @target = nil
38
+ @stale_state = nil
39
+ @inversed = false
40
+ end
41
+
42
+ # Reloads the \target and returns +self+ on success.
43
+ def reload
44
+ reset
45
+ reset_scope
46
+ load_target
47
+ self unless target.nil?
48
+ end
49
+
50
+ # Has the \target been already \loaded?
51
+ def loaded?
52
+ @loaded
53
+ end
54
+
55
+ # Asserts the \target has been loaded setting the \loaded flag to +true+.
56
+ def loaded!
57
+ @loaded = true
58
+ @stale_state = stale_state
59
+ @inversed = false
60
+ end
61
+
62
+ # The target is stale if the target no longer points to the record(s) that the
63
+ # relevant foreign_key(s) refers to. If stale, the association accessor method
64
+ # on the owner will reload the target. It's up to subclasses to implement the
65
+ # stale_state method if relevant.
66
+ #
67
+ # Note that if the target has not been loaded, it is not considered stale.
68
+ def stale_target?
69
+ !@inversed && loaded? && @stale_state != stale_state
70
+ end
71
+
72
+ # Sets the target of this association to <tt>\target</tt>, and the \loaded flag to +true+.
73
+ def target=(target)
74
+ @target = target
75
+ loaded!
76
+ end
77
+
78
+ def scope
79
+ target_scope.merge!(association_scope)
80
+ end
81
+
82
+ # The scope for this association.
83
+ #
84
+ # Note that the association_scope is merged into the target_scope only when the
85
+ # scope method is called. This is because at that point the call may be surrounded
86
+ # by scope.scoping { ... } or with_scope { ... } etc, which affects the scope which
87
+ # actually gets built.
88
+ def association_scope
89
+ if klass
90
+ @association_scope ||= AssociationScope.scope(self)
91
+ end
92
+ end
93
+
94
+ def reset_scope
95
+ @association_scope = nil
96
+ end
97
+
98
+ # Set the inverse association, if possible
99
+ def set_inverse_instance(record)
100
+ if inverse = inverse_association_for(record)
101
+ inverse.inversed_from(owner)
102
+ end
103
+ record
104
+ end
105
+
106
+ def set_inverse_instance_from_queries(record)
107
+ if inverse = inverse_association_for(record)
108
+ inverse.inversed_from_queries(owner)
109
+ end
110
+ record
111
+ end
112
+
113
+ # Remove the inverse association, if possible
114
+ def remove_inverse_instance(record)
115
+ if inverse = inverse_association_for(record)
116
+ inverse.inversed_from(nil)
117
+ end
118
+ end
119
+
120
+ def inversed_from(record)
121
+ self.target = record
122
+ @inversed = !!record
123
+ end
124
+ alias :inversed_from_queries :inversed_from
125
+
126
+ # Returns the class of the target. belongs_to polymorphic overrides this to look at the
127
+ # polymorphic_type field on the owner.
128
+ def klass
129
+ reflection.klass
130
+ end
131
+
132
+ # Can be overridden (i.e. in ThroughAssociation) to merge in other scopes (i.e. the
133
+ # through association's scope)
134
+ def target_scope
135
+ AssociationRelation.create(klass, self).merge!(klass.all)
136
+ end
137
+
138
+ def extensions
139
+ extensions = klass.default_extensions | reflection.extensions
140
+
141
+ if reflection.scope
142
+ extensions |= reflection.scope_for(klass.unscoped, owner).extensions
143
+ end
144
+
145
+ extensions
146
+ end
147
+
148
+ # Loads the \target if needed and returns it.
149
+ #
150
+ # This method is abstract in the sense that it relies on +find_target+,
151
+ # which is expected to be provided by descendants.
152
+ #
153
+ # If the \target is already \loaded it is just returned. Thus, you can call
154
+ # +load_target+ unconditionally to get the \target.
155
+ #
156
+ # ActiveRecord::RecordNotFound is rescued within the method, and it is
157
+ # not reraised. The proxy is \reset and +nil+ is the return value.
158
+ def load_target
159
+ @target = find_target if (@stale_state && stale_target?) || find_target?
160
+
161
+ loaded! unless loaded?
162
+ target
163
+ rescue ActiveRecord::RecordNotFound
164
+ reset
165
+ end
166
+
167
+ # We can't dump @reflection and @through_reflection since it contains the scope proc
168
+ def marshal_dump
169
+ ivars = (instance_variables - [:@reflection, :@through_reflection]).map { |name| [name, instance_variable_get(name)] }
170
+ [@reflection.name, ivars]
171
+ end
172
+
173
+ def marshal_load(data)
174
+ reflection_name, ivars = data
175
+ ivars.each { |name, val| instance_variable_set(name, val) }
176
+ @reflection = @owner.class._reflect_on_association(reflection_name)
177
+ end
178
+
179
+ def initialize_attributes(record, except_from_scope_attributes = nil) #:nodoc:
180
+ except_from_scope_attributes ||= {}
181
+ skip_assign = [reflection.foreign_key, reflection.type].compact
182
+ assigned_keys = record.changed_attribute_names_to_save
183
+ assigned_keys += except_from_scope_attributes.keys.map(&:to_s)
184
+ attributes = scope_for_create.except!(*(assigned_keys - skip_assign))
185
+ record.send(:_assign_attributes, attributes) if attributes.any?
186
+ set_inverse_instance(record)
187
+ end
188
+
189
+ def create(attributes = {}, &block)
190
+ _create_record(attributes, &block)
191
+ end
192
+
193
+ def create!(attributes = {}, &block)
194
+ _create_record(attributes, true, &block)
195
+ end
196
+
197
+ private
198
+ def scope_for_create
199
+ scope.scope_for_create
200
+ end
201
+
202
+ def find_target?
203
+ !loaded? && (!owner.new_record? || foreign_key_present?) && klass
204
+ end
205
+
206
+ def creation_attributes
207
+ attributes = {}
208
+
209
+ if (reflection.has_one? || reflection.collection?) && !options[:through]
210
+ attributes[reflection.foreign_key] = owner[reflection.active_record_primary_key]
211
+
212
+ if reflection.type
213
+ attributes[reflection.type] = owner.class.polymorphic_name
214
+ end
215
+ end
216
+
217
+ attributes
218
+ end
219
+
220
+ # Sets the owner attributes on the given record
221
+ def set_owner_attributes(record)
222
+ creation_attributes.each { |key, value| record[key] = value }
223
+ end
224
+
225
+ # Returns true if there is a foreign key present on the owner which
226
+ # references the target. This is used to determine whether we can load
227
+ # the target if the owner is currently a new record (and therefore
228
+ # without a key). If the owner is a new record then foreign_key must
229
+ # be present in order to load target.
230
+ #
231
+ # Currently implemented by belongs_to (vanilla and polymorphic) and
232
+ # has_one/has_many :through associations which go through a belongs_to.
233
+ def foreign_key_present?
234
+ false
235
+ end
236
+
237
+ # Raises ActiveRecord::AssociationTypeMismatch unless +record+ is of
238
+ # the kind of the class of the associated objects. Meant to be used as
239
+ # a sanity check when you are about to assign an associated record.
240
+ def raise_on_type_mismatch!(record)
241
+ unless record.is_a?(reflection.klass)
242
+ fresh_class = reflection.class_name.safe_constantize
243
+ unless fresh_class && record.is_a?(fresh_class)
244
+ message = "#{reflection.class_name}(##{reflection.klass.object_id}) expected, "\
245
+ "got #{record.inspect} which is an instance of #{record.class}(##{record.class.object_id})"
246
+ raise ActiveRecord::AssociationTypeMismatch, message
247
+ end
248
+ end
249
+ end
250
+
251
+ def inverse_association_for(record)
252
+ if invertible_for?(record)
253
+ record.association(inverse_reflection_for(record).name)
254
+ end
255
+ end
256
+
257
+ # Can be redefined by subclasses, notably polymorphic belongs_to
258
+ # The record parameter is necessary to support polymorphic inverses as we must check for
259
+ # the association in the specific class of the record.
260
+ def inverse_reflection_for(record)
261
+ reflection.inverse_of
262
+ end
263
+
264
+ # Returns true if inverse association on the given record needs to be set.
265
+ # This method is redefined by subclasses.
266
+ def invertible_for?(record)
267
+ foreign_key_for?(record) && inverse_reflection_for(record)
268
+ end
269
+
270
+ # Returns true if record contains the foreign_key
271
+ def foreign_key_for?(record)
272
+ record.has_attribute?(reflection.foreign_key)
273
+ end
274
+
275
+ # This should be implemented to return the values of the relevant key(s) on the owner,
276
+ # so that when stale_state is different from the value stored on the last find_target,
277
+ # the target is stale.
278
+ #
279
+ # This is only relevant to certain associations, which is why it returns +nil+ by default.
280
+ def stale_state
281
+ end
282
+
283
+ def build_record(attributes)
284
+ reflection.build_association(attributes) do |record|
285
+ initialize_attributes(record, attributes)
286
+ yield(record) if block_given?
287
+ end
288
+ end
289
+
290
+ # Returns true if statement cache should be skipped on the association reader.
291
+ def skip_statement_cache?(scope)
292
+ reflection.has_scope? ||
293
+ scope.eager_loading? ||
294
+ klass.scope_attributes? ||
295
+ reflection.source_reflection.active_record.default_scopes.any?
296
+ end
297
+ end
298
+ end
299
+ end
@@ -0,0 +1,168 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Associations
5
+ class AssociationScope #:nodoc:
6
+ def self.scope(association)
7
+ INSTANCE.scope(association)
8
+ end
9
+
10
+ def self.create(&block)
11
+ block ||= lambda { |val| val }
12
+ new(block)
13
+ end
14
+
15
+ def initialize(value_transformation)
16
+ @value_transformation = value_transformation
17
+ end
18
+
19
+ INSTANCE = create
20
+
21
+ def scope(association)
22
+ klass = association.klass
23
+ reflection = association.reflection
24
+ scope = klass.unscoped
25
+ owner = association.owner
26
+ chain = get_chain(reflection, association, scope.alias_tracker)
27
+
28
+ scope.extending! reflection.extensions
29
+ add_constraints(scope, owner, chain)
30
+ end
31
+
32
+ def self.get_bind_values(owner, chain)
33
+ binds = []
34
+ last_reflection = chain.last
35
+
36
+ binds << last_reflection.join_id_for(owner)
37
+ if last_reflection.type
38
+ binds << owner.class.polymorphic_name
39
+ end
40
+
41
+ chain.each_cons(2).each do |reflection, next_reflection|
42
+ if reflection.type
43
+ binds << next_reflection.klass.polymorphic_name
44
+ end
45
+ end
46
+ binds
47
+ end
48
+
49
+ # TODO Change this to private once we've dropped Ruby 2.2 support.
50
+ # Workaround for Ruby 2.2 "private attribute?" warning.
51
+ protected
52
+
53
+ attr_reader :value_transformation
54
+
55
+ private
56
+ def join(table, constraint)
57
+ table.create_join(table, table.create_on(constraint))
58
+ end
59
+
60
+ def last_chain_scope(scope, reflection, owner)
61
+ join_keys = reflection.join_keys
62
+ key = join_keys.key
63
+ foreign_key = join_keys.foreign_key
64
+
65
+ table = reflection.aliased_table
66
+ value = transform_value(owner[foreign_key])
67
+ scope = apply_scope(scope, table, key, value)
68
+
69
+ if reflection.type
70
+ polymorphic_type = transform_value(owner.class.polymorphic_name)
71
+ scope = apply_scope(scope, table, reflection.type, polymorphic_type)
72
+ end
73
+
74
+ scope
75
+ end
76
+
77
+ def transform_value(value)
78
+ value_transformation.call(value)
79
+ end
80
+
81
+ def next_chain_scope(scope, reflection, next_reflection)
82
+ join_keys = reflection.join_keys
83
+ key = join_keys.key
84
+ foreign_key = join_keys.foreign_key
85
+
86
+ table = reflection.aliased_table
87
+ foreign_table = next_reflection.aliased_table
88
+ constraint = table[key].eq(foreign_table[foreign_key])
89
+
90
+ if reflection.type
91
+ value = transform_value(next_reflection.klass.polymorphic_name)
92
+ scope = apply_scope(scope, table, reflection.type, value)
93
+ end
94
+
95
+ scope.joins!(join(foreign_table, constraint))
96
+ end
97
+
98
+ class ReflectionProxy < SimpleDelegator # :nodoc:
99
+ attr_reader :aliased_table
100
+
101
+ def initialize(reflection, aliased_table)
102
+ super(reflection)
103
+ @aliased_table = aliased_table
104
+ end
105
+
106
+ def all_includes; nil; end
107
+ end
108
+
109
+ def get_chain(reflection, association, tracker)
110
+ name = reflection.name
111
+ chain = [Reflection::RuntimeReflection.new(reflection, association)]
112
+ reflection.chain.drop(1).each do |refl|
113
+ aliased_table = tracker.aliased_table_for(
114
+ refl.table_name,
115
+ refl.alias_candidate(name),
116
+ refl.klass.type_caster
117
+ )
118
+ chain << ReflectionProxy.new(refl, aliased_table)
119
+ end
120
+ chain
121
+ end
122
+
123
+ def add_constraints(scope, owner, chain)
124
+ scope = last_chain_scope(scope, chain.last, owner)
125
+
126
+ chain.each_cons(2) do |reflection, next_reflection|
127
+ scope = next_chain_scope(scope, reflection, next_reflection)
128
+ end
129
+
130
+ chain_head = chain.first
131
+ chain.reverse_each do |reflection|
132
+ # Exclude the scope of the association itself, because that
133
+ # was already merged in the #scope method.
134
+ reflection.constraints.each do |scope_chain_item|
135
+ item = eval_scope(reflection, scope_chain_item, owner)
136
+
137
+ if scope_chain_item == chain_head.scope
138
+ scope.merge! item.except(:where, :includes, :unscope, :order)
139
+ end
140
+
141
+ reflection.all_includes do
142
+ scope.includes! item.includes_values
143
+ end
144
+
145
+ scope.unscope!(*item.unscope_values)
146
+ scope.where_clause += item.where_clause
147
+ scope.order_values = item.order_values | scope.order_values
148
+ end
149
+ end
150
+
151
+ scope
152
+ end
153
+
154
+ def apply_scope(scope, table, key, value)
155
+ if scope.table == table
156
+ scope.where!(key => value)
157
+ else
158
+ scope.where!(table.name => { key => value })
159
+ end
160
+ end
161
+
162
+ def eval_scope(reflection, scope, owner)
163
+ relation = reflection.build_scope(reflection.aliased_table)
164
+ relation.instance_exec(owner, &scope) || relation
165
+ end
166
+ end
167
+ end
168
+ end