activerecord 3.2.22.5 → 4.2.11.3

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

Potentially problematic release.


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

Files changed (236) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +1632 -609
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +37 -41
  5. data/examples/performance.rb +31 -19
  6. data/examples/simple.rb +4 -4
  7. data/lib/active_record/aggregations.rb +56 -42
  8. data/lib/active_record/association_relation.rb +35 -0
  9. data/lib/active_record/associations/alias_tracker.rb +47 -36
  10. data/lib/active_record/associations/association.rb +73 -55
  11. data/lib/active_record/associations/association_scope.rb +143 -82
  12. data/lib/active_record/associations/belongs_to_association.rb +65 -25
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +7 -2
  14. data/lib/active_record/associations/builder/association.rb +125 -31
  15. data/lib/active_record/associations/builder/belongs_to.rb +89 -61
  16. data/lib/active_record/associations/builder/collection_association.rb +69 -49
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +113 -42
  18. data/lib/active_record/associations/builder/has_many.rb +8 -64
  19. data/lib/active_record/associations/builder/has_one.rb +12 -51
  20. data/lib/active_record/associations/builder/singular_association.rb +23 -17
  21. data/lib/active_record/associations/collection_association.rb +251 -177
  22. data/lib/active_record/associations/collection_proxy.rb +963 -63
  23. data/lib/active_record/associations/foreign_association.rb +11 -0
  24. data/lib/active_record/associations/has_many_association.rb +113 -22
  25. data/lib/active_record/associations/has_many_through_association.rb +99 -39
  26. data/lib/active_record/associations/has_one_association.rb +43 -20
  27. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  28. data/lib/active_record/associations/join_dependency/join_association.rb +76 -107
  29. data/lib/active_record/associations/join_dependency/join_base.rb +7 -9
  30. data/lib/active_record/associations/join_dependency/join_part.rb +30 -37
  31. data/lib/active_record/associations/join_dependency.rb +230 -156
  32. data/lib/active_record/associations/preloader/association.rb +96 -55
  33. data/lib/active_record/associations/preloader/collection_association.rb +3 -3
  34. data/lib/active_record/associations/preloader/has_many_through.rb +7 -3
  35. data/lib/active_record/associations/preloader/has_one.rb +1 -1
  36. data/lib/active_record/associations/preloader/singular_association.rb +3 -3
  37. data/lib/active_record/associations/preloader/through_association.rb +62 -33
  38. data/lib/active_record/associations/preloader.rb +101 -79
  39. data/lib/active_record/associations/singular_association.rb +29 -13
  40. data/lib/active_record/associations/through_association.rb +30 -16
  41. data/lib/active_record/associations.rb +463 -345
  42. data/lib/active_record/attribute.rb +163 -0
  43. data/lib/active_record/attribute_assignment.rb +142 -151
  44. data/lib/active_record/attribute_decorators.rb +66 -0
  45. data/lib/active_record/attribute_methods/before_type_cast.rb +52 -7
  46. data/lib/active_record/attribute_methods/dirty.rb +137 -57
  47. data/lib/active_record/attribute_methods/primary_key.rb +50 -36
  48. data/lib/active_record/attribute_methods/query.rb +5 -4
  49. data/lib/active_record/attribute_methods/read.rb +73 -106
  50. data/lib/active_record/attribute_methods/serialization.rb +44 -94
  51. data/lib/active_record/attribute_methods/time_zone_conversion.rb +49 -45
  52. data/lib/active_record/attribute_methods/write.rb +57 -44
  53. data/lib/active_record/attribute_methods.rb +301 -141
  54. data/lib/active_record/attribute_set/builder.rb +106 -0
  55. data/lib/active_record/attribute_set.rb +81 -0
  56. data/lib/active_record/attributes.rb +147 -0
  57. data/lib/active_record/autosave_association.rb +246 -217
  58. data/lib/active_record/base.rb +70 -474
  59. data/lib/active_record/callbacks.rb +66 -28
  60. data/lib/active_record/coders/json.rb +13 -0
  61. data/lib/active_record/coders/yaml_column.rb +18 -21
  62. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +396 -219
  63. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  64. data/lib/active_record/connection_adapters/abstract/database_statements.rb +167 -164
  65. data/lib/active_record/connection_adapters/abstract/query_cache.rb +29 -24
  66. data/lib/active_record/connection_adapters/abstract/quoting.rb +74 -55
  67. data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
  68. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +125 -0
  69. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +261 -169
  70. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +50 -0
  71. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +707 -259
  72. data/lib/active_record/connection_adapters/abstract/transaction.rb +215 -0
  73. data/lib/active_record/connection_adapters/abstract_adapter.rb +298 -89
  74. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +466 -196
  75. data/lib/active_record/connection_adapters/column.rb +31 -245
  76. data/lib/active_record/connection_adapters/connection_specification.rb +275 -0
  77. data/lib/active_record/connection_adapters/mysql2_adapter.rb +45 -57
  78. data/lib/active_record/connection_adapters/mysql_adapter.rb +180 -123
  79. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +93 -0
  80. data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
  81. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +232 -0
  82. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +100 -0
  83. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +15 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +36 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +19 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +79 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +19 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +109 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid.rb +36 -0
  108. data/lib/active_record/connection_adapters/postgresql/quoting.rb +108 -0
  109. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  110. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +152 -0
  111. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +596 -0
  112. data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
  113. data/lib/active_record/connection_adapters/postgresql_adapter.rb +430 -999
  114. data/lib/active_record/connection_adapters/schema_cache.rb +52 -27
  115. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +579 -22
  116. data/lib/active_record/connection_handling.rb +132 -0
  117. data/lib/active_record/core.rb +579 -0
  118. data/lib/active_record/counter_cache.rb +157 -105
  119. data/lib/active_record/dynamic_matchers.rb +119 -63
  120. data/lib/active_record/enum.rb +197 -0
  121. data/lib/active_record/errors.rb +94 -36
  122. data/lib/active_record/explain.rb +15 -63
  123. data/lib/active_record/explain_registry.rb +30 -0
  124. data/lib/active_record/explain_subscriber.rb +9 -5
  125. data/lib/active_record/fixture_set/file.rb +56 -0
  126. data/lib/active_record/fixtures.rb +302 -215
  127. data/lib/active_record/gem_version.rb +15 -0
  128. data/lib/active_record/inheritance.rb +143 -70
  129. data/lib/active_record/integration.rb +65 -12
  130. data/lib/active_record/legacy_yaml_adapter.rb +30 -0
  131. data/lib/active_record/locale/en.yml +8 -1
  132. data/lib/active_record/locking/optimistic.rb +73 -52
  133. data/lib/active_record/locking/pessimistic.rb +5 -5
  134. data/lib/active_record/log_subscriber.rb +24 -21
  135. data/lib/active_record/migration/command_recorder.rb +124 -32
  136. data/lib/active_record/migration/join_table.rb +15 -0
  137. data/lib/active_record/migration.rb +511 -213
  138. data/lib/active_record/model_schema.rb +91 -117
  139. data/lib/active_record/nested_attributes.rb +184 -130
  140. data/lib/active_record/no_touching.rb +52 -0
  141. data/lib/active_record/null_relation.rb +81 -0
  142. data/lib/active_record/persistence.rb +276 -117
  143. data/lib/active_record/query_cache.rb +19 -37
  144. data/lib/active_record/querying.rb +28 -18
  145. data/lib/active_record/railtie.rb +73 -40
  146. data/lib/active_record/railties/console_sandbox.rb +3 -4
  147. data/lib/active_record/railties/controller_runtime.rb +4 -3
  148. data/lib/active_record/railties/databases.rake +141 -416
  149. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  150. data/lib/active_record/readonly_attributes.rb +1 -4
  151. data/lib/active_record/reflection.rb +513 -154
  152. data/lib/active_record/relation/batches.rb +91 -43
  153. data/lib/active_record/relation/calculations.rb +199 -161
  154. data/lib/active_record/relation/delegation.rb +116 -25
  155. data/lib/active_record/relation/finder_methods.rb +362 -248
  156. data/lib/active_record/relation/merger.rb +193 -0
  157. data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
  158. data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
  159. data/lib/active_record/relation/predicate_builder.rb +135 -43
  160. data/lib/active_record/relation/query_methods.rb +928 -167
  161. data/lib/active_record/relation/spawn_methods.rb +48 -149
  162. data/lib/active_record/relation.rb +352 -207
  163. data/lib/active_record/result.rb +101 -10
  164. data/lib/active_record/runtime_registry.rb +22 -0
  165. data/lib/active_record/sanitization.rb +56 -59
  166. data/lib/active_record/schema.rb +19 -13
  167. data/lib/active_record/schema_dumper.rb +106 -63
  168. data/lib/active_record/schema_migration.rb +53 -0
  169. data/lib/active_record/scoping/default.rb +50 -57
  170. data/lib/active_record/scoping/named.rb +73 -109
  171. data/lib/active_record/scoping.rb +58 -123
  172. data/lib/active_record/serialization.rb +6 -2
  173. data/lib/active_record/serializers/xml_serializer.rb +12 -22
  174. data/lib/active_record/statement_cache.rb +111 -0
  175. data/lib/active_record/store.rb +168 -15
  176. data/lib/active_record/tasks/database_tasks.rb +299 -0
  177. data/lib/active_record/tasks/mysql_database_tasks.rb +159 -0
  178. data/lib/active_record/tasks/postgresql_database_tasks.rb +101 -0
  179. data/lib/active_record/tasks/sqlite_database_tasks.rb +55 -0
  180. data/lib/active_record/timestamp.rb +23 -16
  181. data/lib/active_record/transactions.rb +125 -79
  182. data/lib/active_record/type/big_integer.rb +13 -0
  183. data/lib/active_record/type/binary.rb +50 -0
  184. data/lib/active_record/type/boolean.rb +31 -0
  185. data/lib/active_record/type/date.rb +50 -0
  186. data/lib/active_record/type/date_time.rb +54 -0
  187. data/lib/active_record/type/decimal.rb +64 -0
  188. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  189. data/lib/active_record/type/decorator.rb +14 -0
  190. data/lib/active_record/type/float.rb +19 -0
  191. data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
  192. data/lib/active_record/type/integer.rb +59 -0
  193. data/lib/active_record/type/mutable.rb +16 -0
  194. data/lib/active_record/type/numeric.rb +36 -0
  195. data/lib/active_record/type/serialized.rb +62 -0
  196. data/lib/active_record/type/string.rb +40 -0
  197. data/lib/active_record/type/text.rb +11 -0
  198. data/lib/active_record/type/time.rb +26 -0
  199. data/lib/active_record/type/time_value.rb +38 -0
  200. data/lib/active_record/type/type_map.rb +64 -0
  201. data/lib/active_record/type/unsigned_integer.rb +15 -0
  202. data/lib/active_record/type/value.rb +110 -0
  203. data/lib/active_record/type.rb +23 -0
  204. data/lib/active_record/validations/associated.rb +24 -16
  205. data/lib/active_record/validations/presence.rb +67 -0
  206. data/lib/active_record/validations/uniqueness.rb +123 -64
  207. data/lib/active_record/validations.rb +36 -29
  208. data/lib/active_record/version.rb +5 -7
  209. data/lib/active_record.rb +66 -46
  210. data/lib/rails/generators/active_record/migration/migration_generator.rb +53 -8
  211. data/lib/rails/generators/active_record/{model/templates/migration.rb → migration/templates/create_table_migration.rb} +5 -1
  212. data/lib/rails/generators/active_record/migration/templates/migration.rb +20 -15
  213. data/lib/rails/generators/active_record/migration.rb +11 -8
  214. data/lib/rails/generators/active_record/model/model_generator.rb +9 -4
  215. data/lib/rails/generators/active_record/model/templates/model.rb +4 -6
  216. data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
  217. data/lib/rails/generators/active_record.rb +3 -11
  218. metadata +101 -45
  219. data/examples/associations.png +0 -0
  220. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -63
  221. data/lib/active_record/associations/join_helper.rb +0 -55
  222. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  223. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
  224. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
  225. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
  226. data/lib/active_record/dynamic_finder_match.rb +0 -68
  227. data/lib/active_record/dynamic_scope_match.rb +0 -23
  228. data/lib/active_record/fixtures/file.rb +0 -65
  229. data/lib/active_record/identity_map.rb +0 -162
  230. data/lib/active_record/observer.rb +0 -121
  231. data/lib/active_record/session_store.rb +0 -360
  232. data/lib/active_record/test_case.rb +0 -73
  233. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  234. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  235. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  236. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -0,0 +1,15 @@
1
+ module ActiveRecord
2
+ # Returns the version of the currently loaded Active Record as a <tt>Gem::Version</tt>
3
+ def self.gem_version
4
+ Gem::Version.new VERSION::STRING
5
+ end
6
+
7
+ module VERSION
8
+ MAJOR = 4
9
+ MINOR = 2
10
+ TINY = 11
11
+ PRE = "3"
12
+
13
+ STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
14
+ end
15
+ end
@@ -1,19 +1,73 @@
1
- require 'active_support/concern'
1
+ require 'active_support/core_ext/hash/indifferent_access'
2
2
 
3
3
  module ActiveRecord
4
+ # == Single table inheritance
5
+ #
6
+ # Active Record allows inheritance by storing the name of the class in a column that by
7
+ # default is named "type" (can be changed by overwriting <tt>Base.inheritance_column</tt>).
8
+ # This means that an inheritance looking like this:
9
+ #
10
+ # class Company < ActiveRecord::Base; end
11
+ # class Firm < Company; end
12
+ # class Client < Company; end
13
+ # class PriorityClient < Client; end
14
+ #
15
+ # When you do <tt>Firm.create(name: "37signals")</tt>, this record will be saved in
16
+ # the companies table with type = "Firm". You can then fetch this row again using
17
+ # <tt>Company.where(name: '37signals').first</tt> and it will return a Firm object.
18
+ #
19
+ # Be aware that because the type column is an attribute on the record every new
20
+ # subclass will instantly be marked as dirty and the type column will be included
21
+ # in the list of changed attributes on the record. This is different from non
22
+ # STI classes:
23
+ #
24
+ # Company.new.changed? # => false
25
+ # Firm.new.changed? # => true
26
+ # Firm.new.changes # => {"type"=>["","Firm"]}
27
+ #
28
+ # If you don't have a type column defined in your table, single-table inheritance won't
29
+ # be triggered. In that case, it'll work just like normal subclasses with no special magic
30
+ # for differentiating between them or reloading the right type with find.
31
+ #
32
+ # Note, all the attributes for all the cases are kept in the same table. Read more:
33
+ # http://www.martinfowler.com/eaaCatalog/singleTableInheritance.html
34
+ #
4
35
  module Inheritance
5
36
  extend ActiveSupport::Concern
6
37
 
7
38
  included do
8
- # Determine whether to store the full constant name including namespace when using STI
9
- class_attribute :store_full_sti_class
39
+ # Determines whether to store the full constant name including namespace when using STI.
40
+ class_attribute :store_full_sti_class, instance_writer: false
10
41
  self.store_full_sti_class = true
11
42
  end
12
43
 
13
44
  module ClassMethods
14
- # True if this isn't a concrete subclass needing a STI type condition.
45
+ # Determines if one of the attributes passed in is the inheritance column,
46
+ # and if the inheritance column is attr accessible, it initializes an
47
+ # instance of the given subclass instead of the base class.
48
+ def new(*args, &block)
49
+ if abstract_class? || self == Base
50
+ raise NotImplementedError, "#{self} is an abstract class and cannot be instantiated."
51
+ end
52
+
53
+ attrs = args.first
54
+ if subclass_from_attributes?(attrs)
55
+ subclass = subclass_from_attributes(attrs)
56
+ end
57
+
58
+ if subclass
59
+ subclass.new(*args, &block)
60
+ else
61
+ super
62
+ end
63
+ end
64
+
65
+ # Returns +true+ if this does not need STI type condition. Returns
66
+ # +false+ if STI type condition needs to be applied.
15
67
  def descends_from_active_record?
16
- if superclass.abstract_class?
68
+ if self == Base
69
+ false
70
+ elsif superclass.abstract_class?
17
71
  superclass.descends_from_active_record?
18
72
  else
19
73
  superclass == Base || !columns_hash.include?(inheritance_column)
@@ -26,24 +80,50 @@ module ActiveRecord
26
80
  end
27
81
 
28
82
  def symbolized_base_class
83
+ ActiveSupport::Deprecation.warn('`ActiveRecord::Base.symbolized_base_class` is deprecated and will be removed without replacement.')
29
84
  @symbolized_base_class ||= base_class.to_s.to_sym
30
85
  end
31
86
 
32
87
  def symbolized_sti_name
88
+ ActiveSupport::Deprecation.warn('`ActiveRecord::Base.symbolized_sti_name` is deprecated and will be removed without replacement.')
33
89
  @symbolized_sti_name ||= sti_name.present? ? sti_name.to_sym : symbolized_base_class
34
90
  end
35
91
 
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
92
+ # Returns the class descending directly from ActiveRecord::Base, or
93
+ # an abstract class, if any, in the inheritance hierarchy.
94
+ #
95
+ # If A extends AR::Base, A.base_class will return A. If B descends from A
38
96
  # through some arbitrarily deep hierarchy, B.base_class will return A.
39
97
  #
40
98
  # If B < A and C < B and if A is an abstract_class then both B.base_class
41
99
  # and C.base_class would return B as the answer since A is an abstract_class.
42
100
  def base_class
43
- class_of_active_record_descendant(self)
101
+ unless self < Base
102
+ raise ActiveRecordError, "#{name} doesn't belong in a hierarchy descending from ActiveRecord"
103
+ end
104
+
105
+ if superclass == Base || superclass.abstract_class?
106
+ self
107
+ else
108
+ superclass.base_class
109
+ end
44
110
  end
45
111
 
46
112
  # Set this to true if this is an abstract class (see <tt>abstract_class?</tt>).
113
+ # If you are using inheritance with ActiveRecord and don't want child classes
114
+ # to utilize the implied STI table name of the parent class, this will need to be true.
115
+ # For example, given the following:
116
+ #
117
+ # class SuperClass < ActiveRecord::Base
118
+ # self.abstract_class = true
119
+ # end
120
+ # class Child < SuperClass
121
+ # self.table_name = 'the_table_i_really_want'
122
+ # end
123
+ #
124
+ #
125
+ # <tt>self.abstract_class = true</tt> is required to make <tt>Child<.find,.create, or any Arel method></tt> use <tt>the_table_i_really_want</tt> instead of a table called <tt>super_classes</tt>
126
+ #
47
127
  attr_accessor :abstract_class
48
128
 
49
129
  # Returns whether this class is an abstract class or not.
@@ -55,36 +135,8 @@ module ActiveRecord
55
135
  store_full_sti_class ? name : name.demodulize
56
136
  end
57
137
 
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
70
-
71
- instance
72
- end
73
-
74
138
  protected
75
139
 
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
87
-
88
140
  # Returns the class type of the record using the current module as a prefix. So descendants of
89
141
  # MyApp::Business::Account would appear as MyApp::Business::AccountSubclass.
90
142
  def compute_type(type_name)
@@ -99,66 +151,87 @@ module ActiveRecord
99
151
  candidates << type_name
100
152
 
101
153
  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)
108
- end
154
+ constant = ActiveSupport::Dependencies.safe_constantize(candidate)
155
+ return constant if candidate == constant.to_s
109
156
  end
110
157
 
111
- raise NameError, "uninitialized constant #{candidates.first}"
158
+ raise NameError.new("uninitialized constant #{candidates.first}", candidates.first)
112
159
  end
113
160
  end
114
161
 
115
162
  private
116
163
 
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
120
- end
121
-
122
- if instance = IdentityMap.get(sti_class, record_id)
123
- instance.reinit_with('attributes' => record)
164
+ # Called by +instantiate+ to decide which class to use for a new
165
+ # record instance. For single-table inheritance, we check the record
166
+ # for a +type+ column and return the corresponding class.
167
+ def discriminate_class_for_record(record)
168
+ if using_single_table_inheritance?(record)
169
+ find_sti_class(record[inheritance_column])
124
170
  else
125
- instance = sti_class.allocate.init_with('attributes' => record)
126
- IdentityMap.add(instance)
171
+ super
127
172
  end
173
+ end
128
174
 
129
- instance
175
+ def using_single_table_inheritance?(record)
176
+ record[inheritance_column].present? && columns_hash.include?(inheritance_column)
130
177
  end
131
178
 
132
179
  def find_sti_class(type_name)
133
- if type_name.blank? || !columns_hash.include?(inheritance_column)
134
- self
180
+ if store_full_sti_class
181
+ ActiveSupport::Dependencies.constantize(type_name)
135
182
  else
136
- begin
137
- if store_full_sti_class
138
- ActiveSupport::Dependencies.constantize(type_name)
139
- else
140
- compute_type(type_name)
141
- end
142
- rescue NameError
143
- 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 " +
147
- "or overwrite #{name}.inheritance_column to use another column for that information."
148
- end
183
+ compute_type(type_name)
149
184
  end
185
+ rescue NameError
186
+ raise SubclassNotFound,
187
+ "The single-table inheritance mechanism failed to locate the subclass: '#{type_name}'. " +
188
+ "This error is raised because the column '#{inheritance_column}' is reserved for storing the class in case of inheritance. " +
189
+ "Please rename this column if you didn't intend it to be used for storing the inheritance class " +
190
+ "or overwrite #{name}.inheritance_column to use another column for that information."
150
191
  end
151
192
 
152
193
  def type_condition(table = arel_table)
153
- sti_column = table[inheritance_column.to_sym]
194
+ sti_column = table[inheritance_column]
154
195
  sti_names = ([self] + descendants).map { |model| model.sti_name }
155
196
 
156
197
  sti_column.in(sti_names)
157
198
  end
199
+
200
+ # Detect the subclass from the inheritance column of attrs. If the inheritance column value
201
+ # is not self or a valid subclass, raises ActiveRecord::SubclassNotFound
202
+ # If this is a StrongParameters hash, and access to inheritance_column is not permitted,
203
+ # this will ignore the inheritance column and return nil
204
+ def subclass_from_attributes?(attrs)
205
+ columns_hash.include?(inheritance_column) && attrs.is_a?(Hash)
206
+ end
207
+
208
+ def subclass_from_attributes(attrs)
209
+ subclass_name = attrs.with_indifferent_access[inheritance_column]
210
+
211
+ if subclass_name.present? && subclass_name != self.name
212
+ subclass = subclass_name.safe_constantize
213
+
214
+ unless descendants.include?(subclass)
215
+ raise ActiveRecord::SubclassNotFound.new("Invalid single-table inheritance type: #{subclass_name} is not a subclass of #{name}")
216
+ end
217
+
218
+ subclass
219
+ end
220
+ end
221
+ end
222
+
223
+ def initialize_dup(other)
224
+ super
225
+ ensure_proper_type
158
226
  end
159
227
 
160
228
  private
161
229
 
230
+ def initialize_internals_callback
231
+ super
232
+ ensure_proper_type
233
+ end
234
+
162
235
  # Sets the attribute used for single table inheritance to this class name if this is not the
163
236
  # ActiveRecord::Base descendant.
164
237
  # Considering the hierarchy Reply < Message < ActiveRecord::Base, this makes it possible to
@@ -1,3 +1,5 @@
1
+ require 'active_support/core_ext/string/filters'
2
+
1
3
  module ActiveRecord
2
4
  module Integration
3
5
  extend ActiveSupport::Concern
@@ -5,10 +7,12 @@ module ActiveRecord
5
7
  included do
6
8
  ##
7
9
  # :singleton-method:
8
- # Indicates the format used to generate the timestamp format in the cache key.
9
- # This is +:number+, by default.
10
+ # Indicates the format used to generate the timestamp in the cache key.
11
+ # Accepts any of the symbols in <tt>Time::DATE_FORMATS</tt>.
12
+ #
13
+ # This is +:nsec+, by default.
10
14
  class_attribute :cache_timestamp_format, :instance_writer => false
11
- self.cache_timestamp_format = :number
15
+ self.cache_timestamp_format = :nsec
12
16
  end
13
17
 
14
18
  # Returns a String, which Action Pack uses for constructing an URL to this
@@ -19,7 +23,7 @@ module ActiveRecord
19
23
  # <tt>resources :users</tt> route. Normally, +user_path+ will
20
24
  # construct a path with the user object's 'id' in it:
21
25
  #
22
- # user = User.find_by_name('Phusion')
26
+ # user = User.find_by(name: 'Phusion')
23
27
  # user_path(user) # => "/users/1"
24
28
  #
25
29
  # You can override +to_param+ in your model to make +user_path+ construct
@@ -31,7 +35,7 @@ module ActiveRecord
31
35
  # end
32
36
  # end
33
37
  #
34
- # user = User.find_by_name('Phusion')
38
+ # user = User.find_by(name: 'Phusion')
35
39
  # user_path(user) # => "/users/Phusion"
36
40
  def to_param
37
41
  # We can't use alias_method here, because method 'id' optimizes itself on the fly.
@@ -40,20 +44,69 @@ module ActiveRecord
40
44
 
41
45
  # Returns a cache key that can be used to identify this record.
42
46
  #
43
- # ==== Examples
44
- #
45
47
  # Product.new.cache_key # => "products/new"
46
48
  # Product.find(5).cache_key # => "products/5" (updated_at not available)
47
49
  # Person.find(5).cache_key # => "people/5-20071224150000" (updated_at available)
48
- def cache_key
50
+ #
51
+ # You can also pass a list of named timestamps, and the newest in the list will be
52
+ # used to generate the key:
53
+ #
54
+ # Person.find(5).cache_key(:updated_at, :last_reviewed_at)
55
+ def cache_key(*timestamp_names)
49
56
  case
50
57
  when new_record?
51
- "#{self.class.model_name.cache_key}/new"
52
- when timestamp = self[:updated_at]
58
+ "#{model_name.cache_key}/new"
59
+ when timestamp_names.any?
60
+ timestamp = max_updated_column_timestamp(timestamp_names)
53
61
  timestamp = timestamp.utc.to_s(cache_timestamp_format)
54
- "#{self.class.model_name.cache_key}/#{id}-#{timestamp}"
62
+ "#{model_name.cache_key}/#{id}-#{timestamp}"
63
+ when timestamp = max_updated_column_timestamp
64
+ timestamp = timestamp.utc.to_s(cache_timestamp_format)
65
+ "#{model_name.cache_key}/#{id}-#{timestamp}"
55
66
  else
56
- "#{self.class.model_name.cache_key}/#{id}"
67
+ "#{model_name.cache_key}/#{id}"
68
+ end
69
+ end
70
+
71
+ module ClassMethods
72
+ # Defines your model's +to_param+ method to generate "pretty" URLs
73
+ # using +method_name+, which can be any attribute or method that
74
+ # responds to +to_s+.
75
+ #
76
+ # class User < ActiveRecord::Base
77
+ # to_param :name
78
+ # end
79
+ #
80
+ # user = User.find_by(name: 'Fancy Pants')
81
+ # user.id # => 123
82
+ # user_path(user) # => "/users/123-fancy-pants"
83
+ #
84
+ # Values longer than 20 characters will be truncated. The value
85
+ # is truncated word by word.
86
+ #
87
+ # user = User.find_by(name: 'David HeinemeierHansson')
88
+ # user.id # => 125
89
+ # user_path(user) # => "/users/125-david"
90
+ #
91
+ # Because the generated param begins with the record's +id+, it is
92
+ # suitable for passing to +find+. In a controller, for example:
93
+ #
94
+ # params[:id] # => "123-fancy-pants"
95
+ # User.find(params[:id]).id # => 123
96
+ def to_param(method_name = nil)
97
+ if method_name.nil?
98
+ super()
99
+ else
100
+ define_method :to_param do
101
+ if (default = super()) &&
102
+ (result = send(method_name).to_s).present? &&
103
+ (param = result.squish.truncate(20, separator: /\s/, omission: nil).parameterize).present?
104
+ "#{default}-#{param}"
105
+ else
106
+ default
107
+ end
108
+ end
109
+ end
57
110
  end
58
111
  end
59
112
  end
@@ -0,0 +1,30 @@
1
+ module ActiveRecord
2
+ module LegacyYamlAdapter
3
+ def self.convert(klass, coder)
4
+ return coder unless coder.is_a?(Psych::Coder)
5
+
6
+ case coder["active_record_yaml_version"]
7
+ when 0 then coder
8
+ else
9
+ if coder["attributes"].is_a?(AttributeSet)
10
+ coder
11
+ else
12
+ Rails41.convert(klass, coder)
13
+ end
14
+ end
15
+ end
16
+
17
+ module Rails41
18
+ def self.convert(klass, coder)
19
+ attributes = klass.attributes_builder
20
+ .build_from_database(coder["attributes"])
21
+ new_record = coder["attributes"][klass.primary_key].blank?
22
+
23
+ {
24
+ "attributes" => attributes,
25
+ "new_record" => new_record,
26
+ }
27
+ end
28
+ end
29
+ end
30
+ end
@@ -4,12 +4,19 @@ en:
4
4
  #created_at: "Created at"
5
5
  #updated_at: "Updated at"
6
6
 
7
+ # Default error messages
8
+ errors:
9
+ messages:
10
+ taken: "has already been taken"
11
+
7
12
  # Active Record models configuration
8
13
  activerecord:
9
14
  errors:
10
15
  messages:
11
- taken: "has already been taken"
12
16
  record_invalid: "Validation failed: %{errors}"
17
+ restrict_dependent_destroy:
18
+ one: "Cannot delete record because a dependent %{record} exists"
19
+ many: "Cannot delete record because dependent %{record} exist"
13
20
  # Append your own errors here or at the model/attributes scope.
14
21
 
15
22
  # You can define own errors for models or model attributes.
@@ -40,16 +40,18 @@ module ActiveRecord
40
40
  # This locking mechanism will function inside a single Ruby process. To make it work across all
41
41
  # web requests, the recommended approach is to add +lock_version+ as a hidden field to your form.
42
42
  #
43
- # You must ensure that your database schema defaults the +lock_version+ column to 0.
44
- #
45
43
  # This behavior can be turned off by setting <tt>ActiveRecord::Base.lock_optimistically = false</tt>.
46
- # To override the name of the +lock_version+ column, invoke the <tt>set_locking_column</tt> method.
47
- # This method uses the same syntax as <tt>set_table_name</tt>
44
+ # To override the name of the +lock_version+ column, set the <tt>locking_column</tt> class attribute:
45
+ #
46
+ # class Person < ActiveRecord::Base
47
+ # self.locking_column = :lock_person
48
+ # end
49
+ #
48
50
  module Optimistic
49
51
  extend ActiveSupport::Concern
50
52
 
51
53
  included do
52
- cattr_accessor :lock_optimistically, :instance_writer => false
54
+ class_attribute :lock_optimistically, instance_writer: false
53
55
  self.lock_optimistically = true
54
56
  end
55
57
 
@@ -64,7 +66,16 @@ module ActiveRecord
64
66
  send(lock_col + '=', previous_lock_value + 1)
65
67
  end
66
68
 
67
- def update(attribute_names = @attributes.keys) #:nodoc:
69
+ def _create_record(attribute_names = self.attribute_names, *) # :nodoc:
70
+ if locking_enabled?
71
+ # We always want to persist the locking version, even if we don't detect
72
+ # a change from the default, since the database might have no default
73
+ attribute_names |= [self.class.locking_column]
74
+ end
75
+ super
76
+ end
77
+
78
+ def _update_record(attribute_names = self.attribute_names) #:nodoc:
68
79
  return super unless locking_enabled?
69
80
  return 0 if attribute_names.empty?
70
81
 
@@ -78,13 +89,14 @@ module ActiveRecord
78
89
  begin
79
90
  relation = self.class.unscoped
80
91
 
81
- stmt = relation.where(
82
- relation.table[self.class.primary_key].eq(id).and(
83
- relation.table[lock_col].eq(quote_value(previous_lock_value, self.class.columns_hash[lock_col]))
84
- )
85
- ).arel.compile_update(arel_attributes_values(false, false, attribute_names))
86
-
87
- affected_rows = connection.update stmt
92
+ affected_rows = relation.where(
93
+ self.class.primary_key => id,
94
+ lock_col => previous_lock_value,
95
+ ).update_all(
96
+ Hash[attributes_for_update(attribute_names).map do |name|
97
+ [name, _read_attribute(name)]
98
+ end]
99
+ )
88
100
 
89
101
  unless affected_rows == 1
90
102
  raise ActiveRecord::StaleObjectError.new(self, "update")
@@ -99,26 +111,29 @@ module ActiveRecord
99
111
  end
100
112
  end
101
113
 
102
- def destroy #:nodoc:
103
- return super unless locking_enabled?
114
+ def destroy_row
115
+ affected_rows = super
104
116
 
105
- destroy_associations
117
+ if locking_enabled? && affected_rows != 1
118
+ raise ActiveRecord::StaleObjectError.new(self, "destroy")
119
+ end
106
120
 
107
- if persisted?
108
- table = self.class.arel_table
109
- lock_col = self.class.locking_column
110
- predicate = table[self.class.primary_key].eq(id).
111
- and(table[lock_col].eq(send(lock_col).to_i))
121
+ affected_rows
122
+ end
112
123
 
113
- affected_rows = self.class.unscoped.where(predicate).delete_all
124
+ def relation_for_destroy
125
+ relation = super
114
126
 
115
- unless affected_rows == 1
116
- raise ActiveRecord::StaleObjectError.new(self, "destroy")
117
- end
127
+ if locking_enabled?
128
+ column_name = self.class.locking_column
129
+ column = self.class.columns_hash[column_name]
130
+ substitute = self.class.connection.substitute_at(column)
131
+
132
+ relation = relation.where(self.class.arel_table[column_name].eq(substitute))
133
+ relation.bind_values << [column, self[column_name].to_i]
118
134
  end
119
135
 
120
- @destroyed = true
121
- freeze
136
+ relation
122
137
  end
123
138
 
124
139
  module ClassMethods
@@ -131,14 +146,10 @@ module ActiveRecord
131
146
  lock_optimistically && columns_hash[locking_column]
132
147
  end
133
148
 
134
- def locking_column=(value)
135
- @original_locking_column = @locking_column if defined?(@locking_column)
136
- @locking_column = value.to_s
137
- end
138
-
139
149
  # Set the column to use for optimistic locking. Defaults to +lock_version+.
140
- def set_locking_column(value = nil, &block)
141
- deprecated_property_setter :locking_column, value, block
150
+ def locking_column=(value)
151
+ clear_caches_calculated_from_columns
152
+ @locking_column = value.to_s
142
153
  end
143
154
 
144
155
  # The version column used for optimistic locking. Defaults to +lock_version+.
@@ -147,15 +158,6 @@ module ActiveRecord
147
158
  @locking_column
148
159
  end
149
160
 
150
- def original_locking_column #:nodoc:
151
- deprecated_original_property_getter :locking_column
152
- end
153
-
154
- # Quote the column name used for optimistic locking.
155
- def quoted_locking_column
156
- connection.quote_column_name(locking_column)
157
- end
158
-
159
161
  # Reset the column used for optimistic locking back to the +lock_version+ default.
160
162
  def reset_locking_column
161
163
  self.locking_column = DEFAULT_LOCKING_COLUMN
@@ -168,18 +170,37 @@ module ActiveRecord
168
170
  super
169
171
  end
170
172
 
171
- # If the locking column has no default value set,
172
- # start the lock version at zero. Note we can't use
173
- # <tt>locking_enabled?</tt> at this point as
174
- # <tt>@attributes</tt> may not have been initialized yet.
175
- def initialize_attributes(attributes, options = {}) #:nodoc:
176
- if attributes.key?(locking_column) && lock_optimistically
177
- attributes[locking_column] ||= 0
173
+ private
174
+
175
+ # We need to apply this decorator here, rather than on module inclusion. The closure
176
+ # created by the matcher would otherwise evaluate for `ActiveRecord::Base`, not the
177
+ # sub class being decorated. As such, changes to `lock_optimistically`, or
178
+ # `locking_column` would not be picked up.
179
+ def inherited(subclass)
180
+ subclass.class_eval do
181
+ is_lock_column = ->(name, _) { lock_optimistically && name == locking_column }
182
+ decorate_matching_attribute_types(is_lock_column, :_optimistic_locking) do |type|
183
+ LockingType.new(type)
184
+ end
178
185
  end
179
-
180
- attributes
186
+ super
181
187
  end
182
188
  end
183
189
  end
190
+
191
+ class LockingType < SimpleDelegator # :nodoc:
192
+ def type_cast_from_database(value)
193
+ # `nil` *should* be changed to 0
194
+ super.to_i
195
+ end
196
+
197
+ def init_with(coder)
198
+ __setobj__(coder['subtype'])
199
+ end
200
+
201
+ def encode_with(coder)
202
+ coder['subtype'] = __getobj__
203
+ end
204
+ end
184
205
  end
185
206
  end