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,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 = 3
13
+ PRE = nil
14
+
15
+ STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
16
+ end
17
+ end
@@ -0,0 +1,283 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/hash/indifferent_access"
4
+
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
+ #
37
+ module Inheritance
38
+ extend ActiveSupport::Concern
39
+
40
+ included do
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
44
+ end
45
+
46
+ module ClassMethods
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.
72
+ def descends_from_active_record?
73
+ if self == Base
74
+ false
75
+ elsif superclass.abstract_class?
76
+ superclass.descends_from_active_record?
77
+ else
78
+ superclass == Base || !columns_hash.include?(inheritance_column)
79
+ end
80
+ end
81
+
82
+ def finder_needs_type_condition? #:nodoc:
83
+ # This is like this because benchmarking justifies the strange :false stuff
84
+ :true == (@finder_needs_type_condition ||= descends_from_active_record? ? :false : :true)
85
+ end
86
+
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
91
+ # through some arbitrarily deep hierarchy, B.base_class will return A.
92
+ #
93
+ # If B < A and C < B and if A is an abstract_class then both B.base_class
94
+ # and C.base_class would return B as the answer since A is an abstract_class.
95
+ def base_class
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
105
+ end
106
+
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.
148
+ attr_accessor :abstract_class
149
+
150
+ # Returns whether this class is an abstract class or not.
151
+ def abstract_class?
152
+ defined?(@abstract_class) && @abstract_class == true
153
+ end
154
+
155
+ def sti_name
156
+ store_full_sti_class ? name : name.demodulize
157
+ end
158
+
159
+ def polymorphic_name
160
+ base_class.name
161
+ end
162
+
163
+ def inherited(subclass)
164
+ subclass.instance_variable_set(:@_type_candidates_cache, Concurrent::Map.new)
165
+ super
166
+ end
167
+
168
+ protected
169
+
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
182
+
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
194
+ end
195
+
196
+ raise NameError.new("uninitialized constant #{candidates.first}", candidates.first)
197
+ end
198
+ end
199
+
200
+ private
201
+
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
211
+ end
212
+
213
+ def using_single_table_inheritance?(record)
214
+ record[inheritance_column].present? && has_attribute?(inheritance_column)
215
+ end
216
+
217
+ def find_sti_class(type_name)
218
+ type_name = base_class.type_for_attribute(inheritance_column).cast(type_name)
219
+ subclass = begin
220
+ if store_full_sti_class
221
+ ActiveSupport::Dependencies.constantize(type_name)
222
+ else
223
+ compute_type(type_name)
224
+ end
225
+ rescue NameError
226
+ raise SubclassNotFound,
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 " \
230
+ "or overwrite #{name}.inheritance_column to use another column for that information."
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
236
+ end
237
+
238
+ def type_condition(table = arel_table)
239
+ sti_column = arel_attribute(inheritance_column, table)
240
+ sti_names = ([self] + descendants).map(&:sti_name)
241
+
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
262
+ end
263
+
264
+ private
265
+
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
281
+ end
282
+ end
283
+ end
@@ -0,0 +1,155 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/string/filters"
4
+
5
+ module ActiveRecord
6
+ module Integration
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ ##
11
+ # :singleton-method:
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
25
+ end
26
+
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.
30
+ #
31
+ # For example, suppose that you have a User model, and that you have a
32
+ # <tt>resources :users</tt> route. Normally, +user_path+ will
33
+ # construct a path with the user object's 'id' in it:
34
+ #
35
+ # user = User.find_by(name: 'Phusion')
36
+ # user_path(user) # => "/users/1"
37
+ #
38
+ # You can override +to_param+ in your model to make +user_path+ construct
39
+ # a path using the user's name instead of the user's id:
40
+ #
41
+ # class User < ActiveRecord::Base
42
+ # def to_param # overridden
43
+ # name
44
+ # end
45
+ # end
46
+ #
47
+ # user = User.find_by(name: 'Phusion')
48
+ # user_path(user) # => "/users/Phusion"
49
+ def to_param
50
+ # We can't use alias_method here, because method 'id' optimizes itself on the fly.
51
+ id && id.to_s # Be sure to stringify the id for routes
52
+ end
53
+
54
+ # Returns a stable cache key that can be used to identify this record.
55
+ #
56
+ # Product.new.cache_key # => "products/new"
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
63
+ # Person.find(5).cache_key # => "people/5-20071224150000" (updated_at available)
64
+ def cache_key(*timestamp_names)
65
+ if new_record?
66
+ "#{model_name.cache_key}/new"
67
+ else
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
152
+ end
153
+ end
154
+ end
155
+ end