activerecord 3.2.22.5 → 5.2.8

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 (275) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +657 -621
  3. data/MIT-LICENSE +2 -2
  4. data/README.rdoc +41 -46
  5. data/examples/performance.rb +55 -42
  6. data/examples/simple.rb +6 -5
  7. data/lib/active_record/aggregations.rb +264 -236
  8. data/lib/active_record/association_relation.rb +40 -0
  9. data/lib/active_record/associations/alias_tracker.rb +47 -42
  10. data/lib/active_record/associations/association.rb +127 -75
  11. data/lib/active_record/associations/association_scope.rb +126 -92
  12. data/lib/active_record/associations/belongs_to_association.rb +78 -27
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +9 -4
  14. data/lib/active_record/associations/builder/association.rb +117 -32
  15. data/lib/active_record/associations/builder/belongs_to.rb +135 -60
  16. data/lib/active_record/associations/builder/collection_association.rb +61 -54
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +120 -42
  18. data/lib/active_record/associations/builder/has_many.rb +10 -64
  19. data/lib/active_record/associations/builder/has_one.rb +19 -51
  20. data/lib/active_record/associations/builder/singular_association.rb +28 -18
  21. data/lib/active_record/associations/collection_association.rb +226 -293
  22. data/lib/active_record/associations/collection_proxy.rb +1067 -69
  23. data/lib/active_record/associations/foreign_association.rb +13 -0
  24. data/lib/active_record/associations/has_many_association.rb +83 -47
  25. data/lib/active_record/associations/has_many_through_association.rb +98 -65
  26. data/lib/active_record/associations/has_one_association.rb +57 -20
  27. data/lib/active_record/associations/has_one_through_association.rb +18 -9
  28. data/lib/active_record/associations/join_dependency/join_association.rb +48 -126
  29. data/lib/active_record/associations/join_dependency/join_base.rb +11 -12
  30. data/lib/active_record/associations/join_dependency/join_part.rb +35 -42
  31. data/lib/active_record/associations/join_dependency.rb +212 -164
  32. data/lib/active_record/associations/preloader/association.rb +95 -89
  33. data/lib/active_record/associations/preloader/through_association.rb +84 -44
  34. data/lib/active_record/associations/preloader.rb +123 -111
  35. data/lib/active_record/associations/singular_association.rb +33 -24
  36. data/lib/active_record/associations/through_association.rb +60 -26
  37. data/lib/active_record/associations.rb +1759 -1506
  38. data/lib/active_record/attribute_assignment.rb +60 -193
  39. data/lib/active_record/attribute_decorators.rb +90 -0
  40. data/lib/active_record/attribute_methods/before_type_cast.rb +55 -8
  41. data/lib/active_record/attribute_methods/dirty.rb +113 -74
  42. data/lib/active_record/attribute_methods/primary_key.rb +106 -77
  43. data/lib/active_record/attribute_methods/query.rb +8 -5
  44. data/lib/active_record/attribute_methods/read.rb +63 -114
  45. data/lib/active_record/attribute_methods/serialization.rb +60 -90
  46. data/lib/active_record/attribute_methods/time_zone_conversion.rb +69 -43
  47. data/lib/active_record/attribute_methods/write.rb +43 -45
  48. data/lib/active_record/attribute_methods.rb +366 -149
  49. data/lib/active_record/attributes.rb +266 -0
  50. data/lib/active_record/autosave_association.rb +312 -225
  51. data/lib/active_record/base.rb +114 -505
  52. data/lib/active_record/callbacks.rb +145 -67
  53. data/lib/active_record/coders/json.rb +15 -0
  54. data/lib/active_record/coders/yaml_column.rb +32 -23
  55. data/lib/active_record/collection_cache_key.rb +53 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +883 -284
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +16 -2
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +350 -200
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +82 -27
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +150 -65
  61. data/lib/active_record/connection_adapters/abstract/savepoints.rb +23 -0
  62. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +146 -0
  63. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +477 -284
  64. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +95 -0
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +1100 -310
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +283 -0
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +450 -118
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +657 -446
  69. data/lib/active_record/connection_adapters/column.rb +50 -255
  70. data/lib/active_record/connection_adapters/connection_specification.rb +287 -0
  71. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +33 -0
  72. data/lib/active_record/connection_adapters/mysql/column.rb +27 -0
  73. data/lib/active_record/connection_adapters/mysql/database_statements.rb +140 -0
  74. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +72 -0
  75. data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -0
  76. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +73 -0
  77. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +87 -0
  78. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +80 -0
  79. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +148 -0
  80. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +35 -0
  81. data/lib/active_record/connection_adapters/mysql2_adapter.rb +59 -210
  82. data/lib/active_record/connection_adapters/postgresql/column.rb +44 -0
  83. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +163 -0
  84. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +92 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +56 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +15 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +17 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +50 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +23 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +23 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +15 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +21 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +71 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +15 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +15 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +45 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +41 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +15 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +65 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +97 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +18 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +111 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +23 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +28 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +30 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid.rb +34 -0
  108. data/lib/active_record/connection_adapters/postgresql/quoting.rb +168 -0
  109. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +43 -0
  110. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +65 -0
  111. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +206 -0
  112. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +50 -0
  113. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +774 -0
  114. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +39 -0
  115. data/lib/active_record/connection_adapters/postgresql/utils.rb +81 -0
  116. data/lib/active_record/connection_adapters/postgresql_adapter.rb +620 -1080
  117. data/lib/active_record/connection_adapters/schema_cache.rb +85 -36
  118. data/lib/active_record/connection_adapters/sql_type_metadata.rb +34 -0
  119. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
  120. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +67 -0
  121. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +17 -0
  122. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +19 -0
  123. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +18 -0
  124. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +106 -0
  125. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +545 -27
  126. data/lib/active_record/connection_adapters/statement_pool.rb +34 -13
  127. data/lib/active_record/connection_handling.rb +145 -0
  128. data/lib/active_record/core.rb +559 -0
  129. data/lib/active_record/counter_cache.rb +200 -105
  130. data/lib/active_record/define_callbacks.rb +22 -0
  131. data/lib/active_record/dynamic_matchers.rb +107 -69
  132. data/lib/active_record/enum.rb +244 -0
  133. data/lib/active_record/errors.rb +245 -60
  134. data/lib/active_record/explain.rb +35 -71
  135. data/lib/active_record/explain_registry.rb +32 -0
  136. data/lib/active_record/explain_subscriber.rb +18 -9
  137. data/lib/active_record/fixture_set/file.rb +82 -0
  138. data/lib/active_record/fixtures.rb +418 -275
  139. data/lib/active_record/gem_version.rb +17 -0
  140. data/lib/active_record/inheritance.rb +209 -100
  141. data/lib/active_record/integration.rb +116 -21
  142. data/lib/active_record/internal_metadata.rb +45 -0
  143. data/lib/active_record/legacy_yaml_adapter.rb +48 -0
  144. data/lib/active_record/locale/en.yml +9 -1
  145. data/lib/active_record/locking/optimistic.rb +107 -94
  146. data/lib/active_record/locking/pessimistic.rb +20 -8
  147. data/lib/active_record/log_subscriber.rb +99 -34
  148. data/lib/active_record/migration/command_recorder.rb +199 -64
  149. data/lib/active_record/migration/compatibility.rb +217 -0
  150. data/lib/active_record/migration/join_table.rb +17 -0
  151. data/lib/active_record/migration.rb +893 -296
  152. data/lib/active_record/model_schema.rb +328 -175
  153. data/lib/active_record/nested_attributes.rb +338 -242
  154. data/lib/active_record/no_touching.rb +58 -0
  155. data/lib/active_record/null_relation.rb +68 -0
  156. data/lib/active_record/persistence.rb +557 -170
  157. data/lib/active_record/query_cache.rb +14 -43
  158. data/lib/active_record/querying.rb +36 -24
  159. data/lib/active_record/railtie.rb +147 -52
  160. data/lib/active_record/railties/console_sandbox.rb +5 -4
  161. data/lib/active_record/railties/controller_runtime.rb +13 -6
  162. data/lib/active_record/railties/databases.rake +206 -488
  163. data/lib/active_record/readonly_attributes.rb +4 -6
  164. data/lib/active_record/reflection.rb +734 -228
  165. data/lib/active_record/relation/batches/batch_enumerator.rb +69 -0
  166. data/lib/active_record/relation/batches.rb +249 -52
  167. data/lib/active_record/relation/calculations.rb +330 -284
  168. data/lib/active_record/relation/delegation.rb +135 -37
  169. data/lib/active_record/relation/finder_methods.rb +450 -287
  170. data/lib/active_record/relation/from_clause.rb +26 -0
  171. data/lib/active_record/relation/merger.rb +193 -0
  172. data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
  173. data/lib/active_record/relation/predicate_builder/association_query_value.rb +46 -0
  174. data/lib/active_record/relation/predicate_builder/base_handler.rb +19 -0
  175. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +20 -0
  176. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +56 -0
  177. data/lib/active_record/relation/predicate_builder/range_handler.rb +42 -0
  178. data/lib/active_record/relation/predicate_builder/relation_handler.rb +19 -0
  179. data/lib/active_record/relation/predicate_builder.rb +132 -43
  180. data/lib/active_record/relation/query_attribute.rb +45 -0
  181. data/lib/active_record/relation/query_methods.rb +1037 -221
  182. data/lib/active_record/relation/record_fetch_warning.rb +51 -0
  183. data/lib/active_record/relation/spawn_methods.rb +48 -151
  184. data/lib/active_record/relation/where_clause.rb +186 -0
  185. data/lib/active_record/relation/where_clause_factory.rb +34 -0
  186. data/lib/active_record/relation.rb +451 -359
  187. data/lib/active_record/result.rb +129 -20
  188. data/lib/active_record/runtime_registry.rb +24 -0
  189. data/lib/active_record/sanitization.rb +164 -136
  190. data/lib/active_record/schema.rb +31 -19
  191. data/lib/active_record/schema_dumper.rb +154 -107
  192. data/lib/active_record/schema_migration.rb +56 -0
  193. data/lib/active_record/scoping/default.rb +108 -98
  194. data/lib/active_record/scoping/named.rb +125 -112
  195. data/lib/active_record/scoping.rb +77 -123
  196. data/lib/active_record/secure_token.rb +40 -0
  197. data/lib/active_record/serialization.rb +10 -6
  198. data/lib/active_record/statement_cache.rb +121 -0
  199. data/lib/active_record/store.rb +175 -16
  200. data/lib/active_record/suppressor.rb +61 -0
  201. data/lib/active_record/table_metadata.rb +82 -0
  202. data/lib/active_record/tasks/database_tasks.rb +337 -0
  203. data/lib/active_record/tasks/mysql_database_tasks.rb +115 -0
  204. data/lib/active_record/tasks/postgresql_database_tasks.rb +143 -0
  205. data/lib/active_record/tasks/sqlite_database_tasks.rb +83 -0
  206. data/lib/active_record/timestamp.rb +80 -41
  207. data/lib/active_record/touch_later.rb +64 -0
  208. data/lib/active_record/transactions.rb +240 -119
  209. data/lib/active_record/translation.rb +2 -0
  210. data/lib/active_record/type/adapter_specific_registry.rb +136 -0
  211. data/lib/active_record/type/date.rb +9 -0
  212. data/lib/active_record/type/date_time.rb +9 -0
  213. data/lib/active_record/type/decimal_without_scale.rb +15 -0
  214. data/lib/active_record/type/hash_lookup_type_map.rb +25 -0
  215. data/lib/active_record/type/internal/timezone.rb +17 -0
  216. data/lib/active_record/type/json.rb +30 -0
  217. data/lib/active_record/type/serialized.rb +71 -0
  218. data/lib/active_record/type/text.rb +11 -0
  219. data/lib/active_record/type/time.rb +21 -0
  220. data/lib/active_record/type/type_map.rb +62 -0
  221. data/lib/active_record/type/unsigned_integer.rb +17 -0
  222. data/lib/active_record/type.rb +79 -0
  223. data/lib/active_record/type_caster/connection.rb +33 -0
  224. data/lib/active_record/type_caster/map.rb +23 -0
  225. data/lib/active_record/type_caster.rb +9 -0
  226. data/lib/active_record/validations/absence.rb +25 -0
  227. data/lib/active_record/validations/associated.rb +35 -18
  228. data/lib/active_record/validations/length.rb +26 -0
  229. data/lib/active_record/validations/presence.rb +68 -0
  230. data/lib/active_record/validations/uniqueness.rb +133 -75
  231. data/lib/active_record/validations.rb +53 -43
  232. data/lib/active_record/version.rb +7 -7
  233. data/lib/active_record.rb +89 -57
  234. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +27 -0
  235. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +5 -0
  236. data/lib/rails/generators/active_record/migration/migration_generator.rb +61 -8
  237. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +24 -0
  238. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +46 -0
  239. data/lib/rails/generators/active_record/migration.rb +28 -8
  240. data/lib/rails/generators/active_record/model/model_generator.rb +23 -22
  241. data/lib/rails/generators/active_record/model/templates/model.rb.tt +13 -0
  242. data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +1 -1
  243. data/lib/rails/generators/active_record.rb +10 -16
  244. metadata +141 -62
  245. data/examples/associations.png +0 -0
  246. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -63
  247. data/lib/active_record/associations/join_helper.rb +0 -55
  248. data/lib/active_record/associations/preloader/belongs_to.rb +0 -17
  249. data/lib/active_record/associations/preloader/collection_association.rb +0 -24
  250. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  251. data/lib/active_record/associations/preloader/has_many.rb +0 -17
  252. data/lib/active_record/associations/preloader/has_many_through.rb +0 -15
  253. data/lib/active_record/associations/preloader/has_one.rb +0 -23
  254. data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
  255. data/lib/active_record/associations/preloader/singular_association.rb +0 -21
  256. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
  257. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
  258. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -441
  259. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
  260. data/lib/active_record/dynamic_finder_match.rb +0 -68
  261. data/lib/active_record/dynamic_scope_match.rb +0 -23
  262. data/lib/active_record/fixtures/file.rb +0 -65
  263. data/lib/active_record/identity_map.rb +0 -162
  264. data/lib/active_record/observer.rb +0 -121
  265. data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
  266. data/lib/active_record/serializers/xml_serializer.rb +0 -203
  267. data/lib/active_record/session_store.rb +0 -360
  268. data/lib/active_record/test_case.rb +0 -73
  269. data/lib/rails/generators/active_record/migration/templates/migration.rb +0 -34
  270. data/lib/rails/generators/active_record/model/templates/migration.rb +0 -15
  271. data/lib/rails/generators/active_record/model/templates/model.rb +0 -12
  272. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  273. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  274. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  275. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ # Returns the version of the currently loaded Active Record as a <tt>Gem::Version</tt>
5
+ def self.gem_version
6
+ Gem::Version.new VERSION::STRING
7
+ end
8
+
9
+ module VERSION
10
+ MAJOR = 5
11
+ MINOR = 2
12
+ TINY = 8
13
+ PRE = nil
14
+
15
+ STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
16
+ end
17
+ end
@@ -1,19 +1,78 @@
1
- require 'active_support/concern'
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/hash/indifferent_access"
2
4
 
3
5
  module ActiveRecord
6
+ # == Single table inheritance
7
+ #
8
+ # Active Record allows inheritance by storing the name of the class in a column that by
9
+ # default is named "type" (can be changed by overwriting <tt>Base.inheritance_column</tt>).
10
+ # This means that an inheritance looking like this:
11
+ #
12
+ # class Company < ActiveRecord::Base; end
13
+ # class Firm < Company; end
14
+ # class Client < Company; end
15
+ # class PriorityClient < Client; end
16
+ #
17
+ # When you do <tt>Firm.create(name: "37signals")</tt>, this record will be saved in
18
+ # the companies table with type = "Firm". You can then fetch this row again using
19
+ # <tt>Company.where(name: '37signals').first</tt> and it will return a Firm object.
20
+ #
21
+ # Be aware that because the type column is an attribute on the record every new
22
+ # subclass will instantly be marked as dirty and the type column will be included
23
+ # in the list of changed attributes on the record. This is different from non
24
+ # Single Table Inheritance(STI) classes:
25
+ #
26
+ # Company.new.changed? # => false
27
+ # Firm.new.changed? # => true
28
+ # Firm.new.changes # => {"type"=>["","Firm"]}
29
+ #
30
+ # If you don't have a type column defined in your table, single-table inheritance won't
31
+ # be triggered. In that case, it'll work just like normal subclasses with no special magic
32
+ # for differentiating between them or reloading the right type with find.
33
+ #
34
+ # Note, all the attributes for all the cases are kept in the same table. Read more:
35
+ # https://www.martinfowler.com/eaaCatalog/singleTableInheritance.html
36
+ #
4
37
  module Inheritance
5
38
  extend ActiveSupport::Concern
6
39
 
7
40
  included do
8
- # Determine whether to store the full constant name including namespace when using STI
9
- class_attribute :store_full_sti_class
10
- self.store_full_sti_class = true
41
+ # Determines whether to store the full constant name including namespace when using STI.
42
+ # This is true, by default.
43
+ class_attribute :store_full_sti_class, instance_writer: false, default: true
11
44
  end
12
45
 
13
46
  module ClassMethods
14
- # True if this isn't a concrete subclass needing a STI type condition.
47
+ # Determines if one of the attributes passed in is the inheritance column,
48
+ # and if the inheritance column is attr accessible, it initializes an
49
+ # instance of the given subclass instead of the base class.
50
+ def new(attributes = nil, &block)
51
+ if abstract_class? || self == Base
52
+ raise NotImplementedError, "#{self} is an abstract class and cannot be instantiated."
53
+ end
54
+
55
+ if has_attribute?(inheritance_column)
56
+ subclass = subclass_from_attributes(attributes)
57
+
58
+ if subclass.nil? && base_class == self
59
+ subclass = subclass_from_attributes(column_defaults)
60
+ end
61
+ end
62
+
63
+ if subclass && subclass != self
64
+ subclass.new(attributes, &block)
65
+ else
66
+ super
67
+ end
68
+ end
69
+
70
+ # Returns +true+ if this does not need STI type condition. Returns
71
+ # +false+ if STI type condition needs to be applied.
15
72
  def descends_from_active_record?
16
- if superclass.abstract_class?
73
+ if self == Base
74
+ false
75
+ elsif superclass.abstract_class?
17
76
  superclass.descends_from_active_record?
18
77
  else
19
78
  superclass == Base || !columns_hash.include?(inheritance_column)
@@ -25,25 +84,67 @@ module ActiveRecord
25
84
  :true == (@finder_needs_type_condition ||= descends_from_active_record? ? :false : :true)
26
85
  end
27
86
 
28
- def symbolized_base_class
29
- @symbolized_base_class ||= base_class.to_s.to_sym
30
- end
31
-
32
- def symbolized_sti_name
33
- @symbolized_sti_name ||= sti_name.present? ? sti_name.to_sym : symbolized_base_class
34
- end
35
-
36
- # Returns the base AR subclass that this class descends from. If A
37
- # extends AR::Base, A.base_class will return A. If B descends from A
87
+ # Returns the class descending directly from ActiveRecord::Base, or
88
+ # an abstract class, if any, in the inheritance hierarchy.
89
+ #
90
+ # If A extends ActiveRecord::Base, A.base_class will return A. If B descends from A
38
91
  # through some arbitrarily deep hierarchy, B.base_class will return A.
39
92
  #
40
93
  # If B < A and C < B and if A is an abstract_class then both B.base_class
41
94
  # and C.base_class would return B as the answer since A is an abstract_class.
42
95
  def base_class
43
- class_of_active_record_descendant(self)
96
+ unless self < Base
97
+ raise ActiveRecordError, "#{name} doesn't belong in a hierarchy descending from ActiveRecord"
98
+ end
99
+
100
+ if superclass == Base || superclass.abstract_class?
101
+ self
102
+ else
103
+ superclass.base_class
104
+ end
44
105
  end
45
106
 
46
- # Set this to true if this is an abstract class (see <tt>abstract_class?</tt>).
107
+ # Set this to +true+ if this is an abstract class (see
108
+ # <tt>abstract_class?</tt>).
109
+ # If you are using inheritance with Active Record and don't want a class
110
+ # to be considered as part of the STI hierarchy, you must set this to
111
+ # true.
112
+ # +ApplicationRecord+, for example, is generated as an abstract class.
113
+ #
114
+ # Consider the following default behaviour:
115
+ #
116
+ # Shape = Class.new(ActiveRecord::Base)
117
+ # Polygon = Class.new(Shape)
118
+ # Square = Class.new(Polygon)
119
+ #
120
+ # Shape.table_name # => "shapes"
121
+ # Polygon.table_name # => "shapes"
122
+ # Square.table_name # => "shapes"
123
+ # Shape.create! # => #<Shape id: 1, type: nil>
124
+ # Polygon.create! # => #<Polygon id: 2, type: "Polygon">
125
+ # Square.create! # => #<Square id: 3, type: "Square">
126
+ #
127
+ # However, when using <tt>abstract_class</tt>, +Shape+ is omitted from
128
+ # the hierarchy:
129
+ #
130
+ # class Shape < ActiveRecord::Base
131
+ # self.abstract_class = true
132
+ # end
133
+ # Polygon = Class.new(Shape)
134
+ # Square = Class.new(Polygon)
135
+ #
136
+ # Shape.table_name # => nil
137
+ # Polygon.table_name # => "polygons"
138
+ # Square.table_name # => "polygons"
139
+ # Shape.create! # => NotImplementedError: Shape is an abstract class and cannot be instantiated.
140
+ # Polygon.create! # => #<Polygon id: 1, type: nil>
141
+ # Square.create! # => #<Square id: 2, type: "Square">
142
+ #
143
+ # Note that in the above example, to disallow the creation of a plain
144
+ # +Polygon+, you should use <tt>validates :type, presence: true</tt>,
145
+ # instead of setting it as an abstract class. This way, +Polygon+ will
146
+ # stay in the hierarchy, and Active Record will continue to correctly
147
+ # derive the table name.
47
148
  attr_accessor :abstract_class
48
149
 
49
150
  # Returns whether this class is an abstract class or not.
@@ -55,85 +156,67 @@ module ActiveRecord
55
156
  store_full_sti_class ? name : name.demodulize
56
157
  end
57
158
 
58
- # Finder methods must instantiate through this method to work with the
59
- # single-table inheritance model that makes it possible to create
60
- # objects of different types from the same table.
61
- def instantiate(record)
62
- sti_class = find_sti_class(record[inheritance_column])
63
- record_id = sti_class.primary_key && record[sti_class.primary_key]
64
-
65
- if ActiveRecord::IdentityMap.enabled? && record_id
66
- instance = use_identity_map(sti_class, record_id, record)
67
- else
68
- instance = sti_class.allocate.init_with('attributes' => record)
69
- end
159
+ def polymorphic_name
160
+ base_class.name
161
+ end
70
162
 
71
- instance
163
+ def inherited(subclass)
164
+ subclass.instance_variable_set(:@_type_candidates_cache, Concurrent::Map.new)
165
+ super
72
166
  end
73
167
 
74
168
  protected
75
169
 
76
- # Returns the class descending directly from ActiveRecord::Base or an
77
- # abstract class, if any, in the inheritance hierarchy.
78
- def class_of_active_record_descendant(klass)
79
- if klass == Base || klass.superclass == Base || klass.superclass.abstract_class?
80
- klass
81
- elsif klass.superclass.nil?
82
- raise ActiveRecordError, "#{name} doesn't belong in a hierarchy descending from ActiveRecord"
83
- else
84
- class_of_active_record_descendant(klass.superclass)
85
- end
86
- end
170
+ # Returns the class type of the record using the current module as a prefix. So descendants of
171
+ # MyApp::Business::Account would appear as MyApp::Business::AccountSubclass.
172
+ def compute_type(type_name)
173
+ if type_name.start_with?("::".freeze)
174
+ # If the type is prefixed with a scope operator then we assume that
175
+ # the type_name is an absolute reference.
176
+ ActiveSupport::Dependencies.constantize(type_name)
177
+ else
178
+ type_candidate = @_type_candidates_cache[type_name]
179
+ if type_candidate && type_constant = ActiveSupport::Dependencies.safe_constantize(type_candidate)
180
+ return type_constant
181
+ end
87
182
 
88
- # Returns the class type of the record using the current module as a prefix. So descendants of
89
- # MyApp::Business::Account would appear as MyApp::Business::AccountSubclass.
90
- def compute_type(type_name)
91
- if type_name.match(/^::/)
92
- # If the type is prefixed with a scope operator then we assume that
93
- # the type_name is an absolute reference.
94
- ActiveSupport::Dependencies.constantize(type_name)
95
- else
96
- # Build a list of candidates to search for
97
- candidates = []
98
- name.scan(/::|$/) { candidates.unshift "#{$`}::#{type_name}" }
99
- candidates << type_name
100
-
101
- candidates.each do |candidate|
102
- begin
103
- constant = ActiveSupport::Dependencies.constantize(candidate)
104
- return constant if candidate == constant.to_s
105
- rescue NameError => e
106
- # We don't want to swallow NoMethodError < NameError errors
107
- raise e unless e.instance_of?(NameError)
183
+ # Build a list of candidates to search for
184
+ candidates = []
185
+ name.scan(/::|$/) { candidates.unshift "#{$`}::#{type_name}" }
186
+ candidates << type_name
187
+
188
+ candidates.each do |candidate|
189
+ constant = ActiveSupport::Dependencies.safe_constantize(candidate)
190
+ if candidate == constant.to_s
191
+ @_type_candidates_cache[type_name] = candidate
192
+ return constant
193
+ end
108
194
  end
109
- end
110
195
 
111
- raise NameError, "uninitialized constant #{candidates.first}"
196
+ raise NameError.new("uninitialized constant #{candidates.first}", candidates.first)
197
+ end
112
198
  end
113
- end
114
199
 
115
200
  private
116
201
 
117
- def use_identity_map(sti_class, record_id, record)
118
- if (column = sti_class.columns_hash[sti_class.primary_key]) && column.number?
119
- record_id = record_id.to_i
202
+ # Called by +instantiate+ to decide which class to use for a new
203
+ # record instance. For single-table inheritance, we check the record
204
+ # for a +type+ column and return the corresponding class.
205
+ def discriminate_class_for_record(record)
206
+ if using_single_table_inheritance?(record)
207
+ find_sti_class(record[inheritance_column])
208
+ else
209
+ super
210
+ end
120
211
  end
121
212
 
122
- if instance = IdentityMap.get(sti_class, record_id)
123
- instance.reinit_with('attributes' => record)
124
- else
125
- instance = sti_class.allocate.init_with('attributes' => record)
126
- IdentityMap.add(instance)
213
+ def using_single_table_inheritance?(record)
214
+ record[inheritance_column].present? && has_attribute?(inheritance_column)
127
215
  end
128
216
 
129
- instance
130
- end
131
-
132
- def find_sti_class(type_name)
133
- if type_name.blank? || !columns_hash.include?(inheritance_column)
134
- self
135
- else
136
- begin
217
+ def find_sti_class(type_name)
218
+ type_name = base_class.type_for_attribute(inheritance_column).cast(type_name)
219
+ subclass = begin
137
220
  if store_full_sti_class
138
221
  ActiveSupport::Dependencies.constantize(type_name)
139
222
  else
@@ -141,34 +224,60 @@ module ActiveRecord
141
224
  end
142
225
  rescue NameError
143
226
  raise SubclassNotFound,
144
- "The single-table inheritance mechanism failed to locate the subclass: '#{type_name}'. " +
145
- "This error is raised because the column '#{inheritance_column}' is reserved for storing the class in case of inheritance. " +
146
- "Please rename this column if you didn't intend it to be used for storing the inheritance class " +
227
+ "The single-table inheritance mechanism failed to locate the subclass: '#{type_name}'. " \
228
+ "This error is raised because the column '#{inheritance_column}' is reserved for storing the class in case of inheritance. " \
229
+ "Please rename this column if you didn't intend it to be used for storing the inheritance class " \
147
230
  "or overwrite #{name}.inheritance_column to use another column for that information."
148
231
  end
232
+ unless subclass == self || descendants.include?(subclass)
233
+ raise SubclassNotFound, "Invalid single-table inheritance type: #{subclass.name} is not a subclass of #{name}"
234
+ end
235
+ subclass
149
236
  end
150
- end
151
237
 
152
- def type_condition(table = arel_table)
153
- sti_column = table[inheritance_column.to_sym]
154
- sti_names = ([self] + descendants).map { |model| model.sti_name }
238
+ def type_condition(table = arel_table)
239
+ sti_column = arel_attribute(inheritance_column, table)
240
+ sti_names = ([self] + descendants).map(&:sti_name)
155
241
 
156
- sti_column.in(sti_names)
157
- end
242
+ sti_column.in(sti_names)
243
+ end
244
+
245
+ # Detect the subclass from the inheritance column of attrs. If the inheritance column value
246
+ # is not self or a valid subclass, raises ActiveRecord::SubclassNotFound
247
+ def subclass_from_attributes(attrs)
248
+ attrs = attrs.to_h if attrs.respond_to?(:permitted?)
249
+ if attrs.is_a?(Hash)
250
+ subclass_name = attrs[inheritance_column] || attrs[inheritance_column.to_sym]
251
+
252
+ if subclass_name.present?
253
+ find_sti_class(subclass_name)
254
+ end
255
+ end
256
+ end
257
+ end
258
+
259
+ def initialize_dup(other)
260
+ super
261
+ ensure_proper_type
158
262
  end
159
263
 
160
264
  private
161
265
 
162
- # Sets the attribute used for single table inheritance to this class name if this is not the
163
- # ActiveRecord::Base descendant.
164
- # Considering the hierarchy Reply < Message < ActiveRecord::Base, this makes it possible to
165
- # do Reply.new without having to set <tt>Reply[Reply.inheritance_column] = "Reply"</tt> yourself.
166
- # No such attribute would be set for objects of the Message class in that example.
167
- def ensure_proper_type
168
- klass = self.class
169
- if klass.finder_needs_type_condition?
170
- write_attribute(klass.inheritance_column, klass.sti_name)
266
+ def initialize_internals_callback
267
+ super
268
+ ensure_proper_type
269
+ end
270
+
271
+ # Sets the attribute used for single table inheritance to this class name if this is not the
272
+ # ActiveRecord::Base descendant.
273
+ # Considering the hierarchy Reply < Message < ActiveRecord::Base, this makes it possible to
274
+ # do Reply.new without having to set <tt>Reply[Reply.inheritance_column] = "Reply"</tt> yourself.
275
+ # No such attribute would be set for objects of the Message class in that example.
276
+ def ensure_proper_type
277
+ klass = self.class
278
+ if klass.finder_needs_type_condition?
279
+ _write_attribute(klass.inheritance_column, klass.sti_name)
280
+ end
171
281
  end
172
- end
173
282
  end
174
283
  end
@@ -1,3 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/string/filters"
4
+
1
5
  module ActiveRecord
2
6
  module Integration
3
7
  extend ActiveSupport::Concern
@@ -5,21 +9,30 @@ module ActiveRecord
5
9
  included do
6
10
  ##
7
11
  # :singleton-method:
8
- # Indicates the format used to generate the timestamp format in the cache key.
9
- # This is +:number+, by default.
10
- class_attribute :cache_timestamp_format, :instance_writer => false
11
- self.cache_timestamp_format = :number
12
+ # Indicates the format used to generate the timestamp in the cache key, if
13
+ # versioning is off. Accepts any of the symbols in <tt>Time::DATE_FORMATS</tt>.
14
+ #
15
+ # This is +:usec+, by default.
16
+ class_attribute :cache_timestamp_format, instance_writer: false, default: :usec
17
+
18
+ ##
19
+ # :singleton-method:
20
+ # Indicates whether to use a stable #cache_key method that is accompanied
21
+ # by a changing version in the #cache_version method.
22
+ #
23
+ # This is +false+, by default until Rails 6.0.
24
+ class_attribute :cache_versioning, instance_writer: false, default: false
12
25
  end
13
26
 
14
- # Returns a String, which Action Pack uses for constructing an URL to this
15
- # object. The default implementation returns this record's id as a String,
16
- # or nil if this record's unsaved.
27
+ # Returns a +String+, which Action Pack uses for constructing a URL to this
28
+ # object. The default implementation returns this record's id as a +String+,
29
+ # or +nil+ if this record's unsaved.
17
30
  #
18
31
  # For example, suppose that you have a User model, and that you have a
19
32
  # <tt>resources :users</tt> route. Normally, +user_path+ will
20
33
  # construct a path with the user object's 'id' in it:
21
34
  #
22
- # user = User.find_by_name('Phusion')
35
+ # user = User.find_by(name: 'Phusion')
23
36
  # user_path(user) # => "/users/1"
24
37
  #
25
38
  # You can override +to_param+ in your model to make +user_path+ construct
@@ -31,29 +44,111 @@ module ActiveRecord
31
44
  # end
32
45
  # end
33
46
  #
34
- # user = User.find_by_name('Phusion')
47
+ # user = User.find_by(name: 'Phusion')
35
48
  # user_path(user) # => "/users/Phusion"
36
49
  def to_param
37
50
  # We can't use alias_method here, because method 'id' optimizes itself on the fly.
38
51
  id && id.to_s # Be sure to stringify the id for routes
39
52
  end
40
53
 
41
- # Returns a cache key that can be used to identify this record.
42
- #
43
- # ==== Examples
54
+ # Returns a stable cache key that can be used to identify this record.
44
55
  #
45
56
  # Product.new.cache_key # => "products/new"
46
- # Product.find(5).cache_key # => "products/5" (updated_at not available)
57
+ # Product.find(5).cache_key # => "products/5"
58
+ #
59
+ # If ActiveRecord::Base.cache_versioning is turned off, as it was in Rails 5.1 and earlier,
60
+ # the cache key will also include a version.
61
+ #
62
+ # Product.cache_versioning = false
47
63
  # Person.find(5).cache_key # => "people/5-20071224150000" (updated_at available)
48
- def cache_key
49
- case
50
- when new_record?
51
- "#{self.class.model_name.cache_key}/new"
52
- when timestamp = self[:updated_at]
53
- timestamp = timestamp.utc.to_s(cache_timestamp_format)
54
- "#{self.class.model_name.cache_key}/#{id}-#{timestamp}"
64
+ def cache_key(*timestamp_names)
65
+ if new_record?
66
+ "#{model_name.cache_key}/new"
55
67
  else
56
- "#{self.class.model_name.cache_key}/#{id}"
68
+ if cache_version && timestamp_names.none?
69
+ "#{model_name.cache_key}/#{id}"
70
+ else
71
+ timestamp = if timestamp_names.any?
72
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
73
+ Specifying a timestamp name for #cache_key has been deprecated in favor of
74
+ the explicit #cache_version method that can be overwritten.
75
+ MSG
76
+
77
+ max_updated_column_timestamp(timestamp_names)
78
+ else
79
+ max_updated_column_timestamp
80
+ end
81
+
82
+ if timestamp
83
+ timestamp = timestamp.utc.to_s(cache_timestamp_format)
84
+ "#{model_name.cache_key}/#{id}-#{timestamp}"
85
+ else
86
+ "#{model_name.cache_key}/#{id}"
87
+ end
88
+ end
89
+ end
90
+ end
91
+
92
+ # Returns a cache version that can be used together with the cache key to form
93
+ # a recyclable caching scheme. By default, the #updated_at column is used for the
94
+ # cache_version, but this method can be overwritten to return something else.
95
+ #
96
+ # Note, this method will return nil if ActiveRecord::Base.cache_versioning is set to
97
+ # +false+ (which it is by default until Rails 6.0).
98
+ def cache_version
99
+ if cache_versioning && timestamp = try(:updated_at)
100
+ timestamp.utc.to_s(:usec)
101
+ end
102
+ end
103
+
104
+ # Returns a cache key along with the version.
105
+ def cache_key_with_version
106
+ if version = cache_version
107
+ "#{cache_key}-#{version}"
108
+ else
109
+ cache_key
110
+ end
111
+ end
112
+
113
+ module ClassMethods
114
+ # Defines your model's +to_param+ method to generate "pretty" URLs
115
+ # using +method_name+, which can be any attribute or method that
116
+ # responds to +to_s+.
117
+ #
118
+ # class User < ActiveRecord::Base
119
+ # to_param :name
120
+ # end
121
+ #
122
+ # user = User.find_by(name: 'Fancy Pants')
123
+ # user.id # => 123
124
+ # user_path(user) # => "/users/123-fancy-pants"
125
+ #
126
+ # Values longer than 20 characters will be truncated. The value
127
+ # is truncated word by word.
128
+ #
129
+ # user = User.find_by(name: 'David Heinemeier Hansson')
130
+ # user.id # => 125
131
+ # user_path(user) # => "/users/125-david-heinemeier"
132
+ #
133
+ # Because the generated param begins with the record's +id+, it is
134
+ # suitable for passing to +find+. In a controller, for example:
135
+ #
136
+ # params[:id] # => "123-fancy-pants"
137
+ # User.find(params[:id]).id # => 123
138
+ def to_param(method_name = nil)
139
+ if method_name.nil?
140
+ super()
141
+ else
142
+ define_method :to_param do
143
+ if (default = super()) &&
144
+ (result = send(method_name).to_s).present? &&
145
+ (param = result.squish.parameterize.truncate(20, separator: /-/, omission: "")).present?
146
+ "#{default}-#{param}"
147
+ else
148
+ default
149
+ end
150
+ end
151
+ end
57
152
  end
58
153
  end
59
154
  end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_record/scoping/default"
4
+ require "active_record/scoping/named"
5
+
6
+ module ActiveRecord
7
+ # This class is used to create a table that keeps track of values and keys such
8
+ # as which environment migrations were run in.
9
+ class InternalMetadata < ActiveRecord::Base # :nodoc:
10
+ class << self
11
+ def primary_key
12
+ "key"
13
+ end
14
+
15
+ def table_name
16
+ "#{table_name_prefix}#{ActiveRecord::Base.internal_metadata_table_name}#{table_name_suffix}"
17
+ end
18
+
19
+ def []=(key, value)
20
+ find_or_initialize_by(key: key).update_attributes!(value: value)
21
+ end
22
+
23
+ def [](key)
24
+ where(key: key).pluck(:value).first
25
+ end
26
+
27
+ def table_exists?
28
+ connection.table_exists?(table_name)
29
+ end
30
+
31
+ # Creates an internal metadata table with columns +key+ and +value+
32
+ def create_table
33
+ unless table_exists?
34
+ key_options = connection.internal_string_options_for_primary_key
35
+
36
+ connection.create_table(table_name, id: false) do |t|
37
+ t.string :key, key_options
38
+ t.string :value
39
+ t.timestamps
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module LegacyYamlAdapter
5
+ def self.convert(klass, coder)
6
+ return coder unless coder.is_a?(Psych::Coder)
7
+
8
+ case coder["active_record_yaml_version"]
9
+ when 1, 2 then coder
10
+ else
11
+ if coder["attributes"].is_a?(ActiveModel::AttributeSet)
12
+ Rails420.convert(klass, coder)
13
+ else
14
+ Rails41.convert(klass, coder)
15
+ end
16
+ end
17
+ end
18
+
19
+ module Rails420
20
+ def self.convert(klass, coder)
21
+ attribute_set = coder["attributes"]
22
+
23
+ klass.attribute_names.each do |attr_name|
24
+ attribute = attribute_set[attr_name]
25
+ if attribute.type.is_a?(Delegator)
26
+ type_from_klass = klass.type_for_attribute(attr_name)
27
+ attribute_set[attr_name] = attribute.with_type(type_from_klass)
28
+ end
29
+ end
30
+
31
+ coder
32
+ end
33
+ end
34
+
35
+ module Rails41
36
+ def self.convert(klass, coder)
37
+ attributes = klass.attributes_builder
38
+ .build_from_database(coder["attributes"])
39
+ new_record = coder["attributes"][klass.primary_key].blank?
40
+
41
+ {
42
+ "attributes" => attributes,
43
+ "new_record" => new_record,
44
+ }
45
+ end
46
+ end
47
+ end
48
+ end