activerecord 3.1.10 → 4.2.11

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 (237) hide show
  1. checksums.yaml +6 -6
  2. data/CHANGELOG.md +1837 -338
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +39 -43
  5. data/examples/performance.rb +51 -20
  6. data/examples/simple.rb +4 -4
  7. data/lib/active_record/aggregations.rb +57 -43
  8. data/lib/active_record/association_relation.rb +35 -0
  9. data/lib/active_record/associations/alias_tracker.rb +47 -39
  10. data/lib/active_record/associations/association.rb +71 -85
  11. data/lib/active_record/associations/association_scope.rb +138 -89
  12. data/lib/active_record/associations/belongs_to_association.rb +65 -25
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +9 -3
  14. data/lib/active_record/associations/builder/association.rb +125 -29
  15. data/lib/active_record/associations/builder/belongs_to.rb +91 -60
  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 -52
  20. data/lib/active_record/associations/builder/singular_association.rb +22 -29
  21. data/lib/active_record/associations/collection_association.rb +294 -187
  22. data/lib/active_record/associations/collection_proxy.rb +961 -94
  23. data/lib/active_record/associations/foreign_association.rb +11 -0
  24. data/lib/active_record/associations/has_many_association.rb +118 -23
  25. data/lib/active_record/associations/has_many_through_association.rb +115 -45
  26. data/lib/active_record/associations/has_one_association.rb +57 -24
  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 -102
  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 +61 -32
  38. data/lib/active_record/associations/preloader.rb +113 -87
  39. data/lib/active_record/associations/singular_association.rb +29 -13
  40. data/lib/active_record/associations/through_association.rb +37 -19
  41. data/lib/active_record/associations.rb +505 -371
  42. data/lib/active_record/attribute.rb +163 -0
  43. data/lib/active_record/attribute_assignment.rb +212 -0
  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 +141 -51
  47. data/lib/active_record/attribute_methods/primary_key.rb +87 -36
  48. data/lib/active_record/attribute_methods/query.rb +5 -4
  49. data/lib/active_record/attribute_methods/read.rb +74 -117
  50. data/lib/active_record/attribute_methods/serialization.rb +70 -0
  51. data/lib/active_record/attribute_methods/time_zone_conversion.rb +49 -47
  52. data/lib/active_record/attribute_methods/write.rb +60 -21
  53. data/lib/active_record/attribute_methods.rb +409 -48
  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 +279 -232
  58. data/lib/active_record/base.rb +84 -1969
  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 +422 -243
  63. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  64. data/lib/active_record/connection_adapters/abstract/database_statements.rb +170 -194
  65. data/lib/active_record/connection_adapters/abstract/query_cache.rb +32 -19
  66. data/lib/active_record/connection_adapters/abstract/quoting.rb +79 -57
  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 +273 -170
  70. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +50 -0
  71. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +731 -254
  72. data/lib/active_record/connection_adapters/abstract/transaction.rb +215 -0
  73. data/lib/active_record/connection_adapters/abstract_adapter.rb +339 -95
  74. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +946 -0
  75. data/lib/active_record/connection_adapters/column.rb +33 -221
  76. data/lib/active_record/connection_adapters/connection_specification.rb +275 -0
  77. data/lib/active_record/connection_adapters/mysql2_adapter.rb +140 -602
  78. data/lib/active_record/connection_adapters/mysql_adapter.rb +254 -756
  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 +445 -902
  114. data/lib/active_record/connection_adapters/schema_cache.rb +94 -0
  115. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +578 -25
  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 +159 -102
  119. data/lib/active_record/dynamic_matchers.rb +140 -0
  120. data/lib/active_record/enum.rb +197 -0
  121. data/lib/active_record/errors.rb +102 -34
  122. data/lib/active_record/explain.rb +38 -0
  123. data/lib/active_record/explain_registry.rb +30 -0
  124. data/lib/active_record/explain_subscriber.rb +29 -0
  125. data/lib/active_record/fixture_set/file.rb +56 -0
  126. data/lib/active_record/fixtures.rb +318 -260
  127. data/lib/active_record/gem_version.rb +15 -0
  128. data/lib/active_record/inheritance.rb +247 -0
  129. data/lib/active_record/integration.rb +113 -0
  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 +80 -52
  133. data/lib/active_record/locking/pessimistic.rb +27 -5
  134. data/lib/active_record/log_subscriber.rb +25 -18
  135. data/lib/active_record/migration/command_recorder.rb +130 -38
  136. data/lib/active_record/migration/join_table.rb +15 -0
  137. data/lib/active_record/migration.rb +532 -201
  138. data/lib/active_record/model_schema.rb +342 -0
  139. data/lib/active_record/nested_attributes.rb +229 -139
  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 +304 -99
  143. data/lib/active_record/query_cache.rb +25 -43
  144. data/lib/active_record/querying.rb +68 -0
  145. data/lib/active_record/railtie.rb +86 -45
  146. data/lib/active_record/railties/console_sandbox.rb +3 -4
  147. data/lib/active_record/railties/controller_runtime.rb +7 -4
  148. data/lib/active_record/railties/databases.rake +198 -377
  149. data/lib/active_record/railties/jdbcmysql_error.rb +2 -2
  150. data/lib/active_record/readonly_attributes.rb +23 -0
  151. data/lib/active_record/reflection.rb +516 -165
  152. data/lib/active_record/relation/batches.rb +96 -45
  153. data/lib/active_record/relation/calculations.rb +221 -144
  154. data/lib/active_record/relation/delegation.rb +140 -0
  155. data/lib/active_record/relation/finder_methods.rb +362 -243
  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 -41
  160. data/lib/active_record/relation/query_methods.rb +982 -155
  161. data/lib/active_record/relation/spawn_methods.rb +50 -110
  162. data/lib/active_record/relation.rb +371 -180
  163. data/lib/active_record/result.rb +109 -12
  164. data/lib/active_record/runtime_registry.rb +22 -0
  165. data/lib/active_record/sanitization.rb +191 -0
  166. data/lib/active_record/schema.rb +19 -13
  167. data/lib/active_record/schema_dumper.rb +111 -61
  168. data/lib/active_record/schema_migration.rb +53 -0
  169. data/lib/active_record/scoping/default.rb +135 -0
  170. data/lib/active_record/scoping/named.rb +164 -0
  171. data/lib/active_record/scoping.rb +87 -0
  172. data/lib/active_record/serialization.rb +7 -45
  173. data/lib/active_record/serializers/xml_serializer.rb +14 -65
  174. data/lib/active_record/statement_cache.rb +111 -0
  175. data/lib/active_record/store.rb +205 -0
  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 +35 -14
  181. data/lib/active_record/transactions.rb +141 -74
  182. data/lib/active_record/translation.rb +22 -0
  183. data/lib/active_record/type/big_integer.rb +13 -0
  184. data/lib/active_record/type/binary.rb +50 -0
  185. data/lib/active_record/type/boolean.rb +31 -0
  186. data/lib/active_record/type/date.rb +50 -0
  187. data/lib/active_record/type/date_time.rb +54 -0
  188. data/lib/active_record/type/decimal.rb +64 -0
  189. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  190. data/lib/active_record/type/decorator.rb +14 -0
  191. data/lib/active_record/type/float.rb +19 -0
  192. data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
  193. data/lib/active_record/type/integer.rb +59 -0
  194. data/lib/active_record/type/mutable.rb +16 -0
  195. data/lib/active_record/type/numeric.rb +36 -0
  196. data/lib/active_record/type/serialized.rb +62 -0
  197. data/lib/active_record/type/string.rb +40 -0
  198. data/lib/active_record/type/text.rb +11 -0
  199. data/lib/active_record/type/time.rb +26 -0
  200. data/lib/active_record/type/time_value.rb +38 -0
  201. data/lib/active_record/type/type_map.rb +64 -0
  202. data/lib/active_record/type/unsigned_integer.rb +15 -0
  203. data/lib/active_record/type/value.rb +110 -0
  204. data/lib/active_record/type.rb +23 -0
  205. data/lib/active_record/validations/associated.rb +27 -18
  206. data/lib/active_record/validations/presence.rb +67 -0
  207. data/lib/active_record/validations/uniqueness.rb +125 -66
  208. data/lib/active_record/validations.rb +37 -30
  209. data/lib/active_record/version.rb +5 -7
  210. data/lib/active_record.rb +80 -25
  211. data/lib/rails/generators/active_record/migration/migration_generator.rb +54 -9
  212. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +19 -0
  213. data/lib/rails/generators/active_record/migration/templates/migration.rb +25 -11
  214. data/lib/rails/generators/active_record/migration.rb +11 -8
  215. data/lib/rails/generators/active_record/model/model_generator.rb +17 -4
  216. data/lib/rails/generators/active_record/model/templates/model.rb +5 -2
  217. data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
  218. data/lib/rails/generators/active_record.rb +3 -11
  219. metadata +132 -53
  220. data/examples/associations.png +0 -0
  221. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -62
  222. data/lib/active_record/associations/join_helper.rb +0 -55
  223. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  224. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -135
  225. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -556
  226. data/lib/active_record/dynamic_finder_match.rb +0 -56
  227. data/lib/active_record/dynamic_scope_match.rb +0 -23
  228. data/lib/active_record/identity_map.rb +0 -163
  229. data/lib/active_record/named_scope.rb +0 -200
  230. data/lib/active_record/observer.rb +0 -121
  231. data/lib/active_record/session_store.rb +0 -358
  232. data/lib/active_record/test_case.rb +0 -69
  233. data/lib/rails/generators/active_record/model/templates/migration.rb +0 -17
  234. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  235. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  236. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  237. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -16
@@ -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 = nil
12
+
13
+ STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
14
+ end
15
+ end
@@ -0,0 +1,247 @@
1
+ require 'active_support/core_ext/hash/indifferent_access'
2
+
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
+ #
35
+ module Inheritance
36
+ extend ActiveSupport::Concern
37
+
38
+ included do
39
+ # Determines whether to store the full constant name including namespace when using STI.
40
+ class_attribute :store_full_sti_class, instance_writer: false
41
+ self.store_full_sti_class = true
42
+ end
43
+
44
+ module ClassMethods
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.
67
+ def descends_from_active_record?
68
+ if self == Base
69
+ false
70
+ elsif superclass.abstract_class?
71
+ superclass.descends_from_active_record?
72
+ else
73
+ superclass == Base || !columns_hash.include?(inheritance_column)
74
+ end
75
+ end
76
+
77
+ def finder_needs_type_condition? #:nodoc:
78
+ # This is like this because benchmarking justifies the strange :false stuff
79
+ :true == (@finder_needs_type_condition ||= descends_from_active_record? ? :false : :true)
80
+ end
81
+
82
+ def symbolized_base_class
83
+ ActiveSupport::Deprecation.warn('`ActiveRecord::Base.symbolized_base_class` is deprecated and will be removed without replacement.')
84
+ @symbolized_base_class ||= base_class.to_s.to_sym
85
+ end
86
+
87
+ def symbolized_sti_name
88
+ ActiveSupport::Deprecation.warn('`ActiveRecord::Base.symbolized_sti_name` is deprecated and will be removed without replacement.')
89
+ @symbolized_sti_name ||= sti_name.present? ? sti_name.to_sym : symbolized_base_class
90
+ end
91
+
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
96
+ # through some arbitrarily deep hierarchy, B.base_class will return A.
97
+ #
98
+ # If B < A and C < B and if A is an abstract_class then both B.base_class
99
+ # and C.base_class would return B as the answer since A is an abstract_class.
100
+ def base_class
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
110
+ end
111
+
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
+ #
127
+ attr_accessor :abstract_class
128
+
129
+ # Returns whether this class is an abstract class or not.
130
+ def abstract_class?
131
+ defined?(@abstract_class) && @abstract_class == true
132
+ end
133
+
134
+ def sti_name
135
+ store_full_sti_class ? name : name.demodulize
136
+ end
137
+
138
+ protected
139
+
140
+ # Returns the class type of the record using the current module as a prefix. So descendants of
141
+ # MyApp::Business::Account would appear as MyApp::Business::AccountSubclass.
142
+ def compute_type(type_name)
143
+ if type_name.match(/^::/)
144
+ # If the type is prefixed with a scope operator then we assume that
145
+ # the type_name is an absolute reference.
146
+ ActiveSupport::Dependencies.constantize(type_name)
147
+ else
148
+ # Build a list of candidates to search for
149
+ candidates = []
150
+ name.scan(/::|$/) { candidates.unshift "#{$`}::#{type_name}" }
151
+ candidates << type_name
152
+
153
+ candidates.each do |candidate|
154
+ constant = ActiveSupport::Dependencies.safe_constantize(candidate)
155
+ return constant if candidate == constant.to_s
156
+ end
157
+
158
+ raise NameError.new("uninitialized constant #{candidates.first}", candidates.first)
159
+ end
160
+ end
161
+
162
+ private
163
+
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])
170
+ else
171
+ super
172
+ end
173
+ end
174
+
175
+ def using_single_table_inheritance?(record)
176
+ record[inheritance_column].present? && columns_hash.include?(inheritance_column)
177
+ end
178
+
179
+ def find_sti_class(type_name)
180
+ if store_full_sti_class
181
+ ActiveSupport::Dependencies.constantize(type_name)
182
+ else
183
+ compute_type(type_name)
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."
191
+ end
192
+
193
+ def type_condition(table = arel_table)
194
+ sti_column = table[inheritance_column]
195
+ sti_names = ([self] + descendants).map { |model| model.sti_name }
196
+
197
+ sti_column.in(sti_names)
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
226
+ end
227
+
228
+ private
229
+
230
+ def initialize_internals_callback
231
+ super
232
+ ensure_proper_type
233
+ end
234
+
235
+ # Sets the attribute used for single table inheritance to this class name if this is not the
236
+ # ActiveRecord::Base descendant.
237
+ # Considering the hierarchy Reply < Message < ActiveRecord::Base, this makes it possible to
238
+ # do Reply.new without having to set <tt>Reply[Reply.inheritance_column] = "Reply"</tt> yourself.
239
+ # No such attribute would be set for objects of the Message class in that example.
240
+ def ensure_proper_type
241
+ klass = self.class
242
+ if klass.finder_needs_type_condition?
243
+ write_attribute(klass.inheritance_column, klass.sti_name)
244
+ end
245
+ end
246
+ end
247
+ end
@@ -0,0 +1,113 @@
1
+ require 'active_support/core_ext/string/filters'
2
+
3
+ module ActiveRecord
4
+ module Integration
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ ##
9
+ # :singleton-method:
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.
14
+ class_attribute :cache_timestamp_format, :instance_writer => false
15
+ self.cache_timestamp_format = :nsec
16
+ end
17
+
18
+ # Returns a String, which Action Pack uses for constructing an URL to this
19
+ # object. The default implementation returns this record's id as a String,
20
+ # or nil if this record's unsaved.
21
+ #
22
+ # For example, suppose that you have a User model, and that you have a
23
+ # <tt>resources :users</tt> route. Normally, +user_path+ will
24
+ # construct a path with the user object's 'id' in it:
25
+ #
26
+ # user = User.find_by(name: 'Phusion')
27
+ # user_path(user) # => "/users/1"
28
+ #
29
+ # You can override +to_param+ in your model to make +user_path+ construct
30
+ # a path using the user's name instead of the user's id:
31
+ #
32
+ # class User < ActiveRecord::Base
33
+ # def to_param # overridden
34
+ # name
35
+ # end
36
+ # end
37
+ #
38
+ # user = User.find_by(name: 'Phusion')
39
+ # user_path(user) # => "/users/Phusion"
40
+ def to_param
41
+ # We can't use alias_method here, because method 'id' optimizes itself on the fly.
42
+ id && id.to_s # Be sure to stringify the id for routes
43
+ end
44
+
45
+ # Returns a cache key that can be used to identify this record.
46
+ #
47
+ # Product.new.cache_key # => "products/new"
48
+ # Product.find(5).cache_key # => "products/5" (updated_at not available)
49
+ # Person.find(5).cache_key # => "people/5-20071224150000" (updated_at available)
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)
56
+ case
57
+ when new_record?
58
+ "#{model_name.cache_key}/new"
59
+ when timestamp_names.any?
60
+ timestamp = max_updated_column_timestamp(timestamp_names)
61
+ timestamp = timestamp.utc.to_s(cache_timestamp_format)
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}"
66
+ else
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
110
+ end
111
+ end
112
+ end
113
+ 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.
@@ -37,21 +37,22 @@ module ActiveRecord
37
37
  # You're then responsible for dealing with the conflict by rescuing the exception and either rolling back, merging,
38
38
  # or otherwise apply the business logic needed to resolve the conflict.
39
39
  #
40
- # You must ensure that your database schema defaults the +lock_version+ column to 0.
40
+ # This locking mechanism will function inside a single Ruby process. To make it work across all
41
+ # web requests, the recommended approach is to add +lock_version+ as a hidden field to your form.
41
42
  #
42
43
  # This behavior can be turned off by setting <tt>ActiveRecord::Base.lock_optimistically = false</tt>.
43
- # To override the name of the +lock_version+ column, invoke the <tt>set_locking_column</tt> method.
44
- # 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
+ #
45
50
  module Optimistic
46
51
  extend ActiveSupport::Concern
47
52
 
48
53
  included do
49
- cattr_accessor :lock_optimistically, :instance_writer => false
54
+ class_attribute :lock_optimistically, instance_writer: false
50
55
  self.lock_optimistically = true
51
-
52
- class << self
53
- alias_method :locking_column=, :set_locking_column
54
- end
55
56
  end
56
57
 
57
58
  def locking_enabled? #:nodoc:
@@ -65,22 +66,16 @@ module ActiveRecord
65
66
  send(lock_col + '=', previous_lock_value + 1)
66
67
  end
67
68
 
68
- def attributes_from_column_definition
69
- result = super
70
-
71
- # If the locking column has no default value set,
72
- # start the lock version at zero. Note we can't use
73
- # <tt>locking_enabled?</tt> at this point as
74
- # <tt>@attributes</tt> may not have been initialized yet.
75
-
76
- if result.key?(self.class.locking_column) && lock_optimistically
77
- result[self.class.locking_column] ||= 0
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]
78
74
  end
79
-
80
- result
75
+ super
81
76
  end
82
77
 
83
- def update(attribute_names = @attributes.keys) #:nodoc:
78
+ def _update_record(attribute_names = self.attribute_names) #:nodoc:
84
79
  return super unless locking_enabled?
85
80
  return 0 if attribute_names.empty?
86
81
 
@@ -94,16 +89,17 @@ module ActiveRecord
94
89
  begin
95
90
  relation = self.class.unscoped
96
91
 
97
- stmt = relation.where(
98
- relation.table[self.class.primary_key].eq(id).and(
99
- relation.table[lock_col].eq(quote_value(previous_lock_value))
100
- )
101
- ).arel.compile_update(arel_attributes_values(false, false, attribute_names))
102
-
103
- 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
+ )
104
100
 
105
101
  unless affected_rows == 1
106
- raise ActiveRecord::StaleObjectError, "Attempted to update a stale object: #{self.class.name}"
102
+ raise ActiveRecord::StaleObjectError.new(self, "update")
107
103
  end
108
104
 
109
105
  affected_rows
@@ -115,24 +111,29 @@ module ActiveRecord
115
111
  end
116
112
  end
117
113
 
118
- def destroy #:nodoc:
119
- return super unless locking_enabled?
114
+ def destroy_row
115
+ affected_rows = super
120
116
 
121
- if persisted?
122
- table = self.class.arel_table
123
- lock_col = self.class.locking_column
124
- predicate = table[self.class.primary_key].eq(id).
125
- and(table[lock_col].eq(send(lock_col).to_i))
117
+ if locking_enabled? && affected_rows != 1
118
+ raise ActiveRecord::StaleObjectError.new(self, "destroy")
119
+ end
126
120
 
127
- affected_rows = self.class.unscoped.where(predicate).delete_all
121
+ affected_rows
122
+ end
128
123
 
129
- unless affected_rows == 1
130
- raise ActiveRecord::StaleObjectError, "Attempted to delete a stale object: #{self.class.name}"
131
- end
124
+ def relation_for_destroy
125
+ relation = super
126
+
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]
132
134
  end
133
135
 
134
- @destroyed = true
135
- freeze
136
+ relation
136
137
  end
137
138
 
138
139
  module ClassMethods
@@ -146,24 +147,20 @@ module ActiveRecord
146
147
  end
147
148
 
148
149
  # Set the column to use for optimistic locking. Defaults to +lock_version+.
149
- def set_locking_column(value = nil, &block)
150
- define_attr_method :locking_column, value, &block
151
- value
150
+ def locking_column=(value)
151
+ clear_caches_calculated_from_columns
152
+ @locking_column = value.to_s
152
153
  end
153
154
 
154
155
  # The version column used for optimistic locking. Defaults to +lock_version+.
155
156
  def locking_column
156
- reset_locking_column
157
- end
158
-
159
- # Quote the column name used for optimistic locking.
160
- def quoted_locking_column
161
- connection.quote_column_name(locking_column)
157
+ reset_locking_column unless defined?(@locking_column)
158
+ @locking_column
162
159
  end
163
160
 
164
161
  # Reset the column used for optimistic locking back to the +lock_version+ default.
165
162
  def reset_locking_column
166
- set_locking_column DEFAULT_LOCKING_COLUMN
163
+ self.locking_column = DEFAULT_LOCKING_COLUMN
167
164
  end
168
165
 
169
166
  # Make sure the lock version column gets updated when counters are
@@ -172,6 +169,37 @@ module ActiveRecord
172
169
  counters = counters.merge(locking_column => 1) if locking_enabled?
173
170
  super
174
171
  end
172
+
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
185
+ end
186
+ super
187
+ end
188
+ end
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__
175
203
  end
176
204
  end
177
205
  end