activerecord 3.2.22.5 → 4.2.11.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 (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,81 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ module ActiveRecord
4
+ module NullRelation # :nodoc:
5
+ def exec_queries
6
+ @records = []
7
+ end
8
+
9
+ def pluck(*column_names)
10
+ []
11
+ end
12
+
13
+ def delete_all(_conditions = nil)
14
+ 0
15
+ end
16
+
17
+ def update_all(_updates, _conditions = nil, _options = {})
18
+ 0
19
+ end
20
+
21
+ def delete(_id_or_array)
22
+ 0
23
+ end
24
+
25
+ def size
26
+ calculate :size, nil
27
+ end
28
+
29
+ def empty?
30
+ true
31
+ end
32
+
33
+ def any?
34
+ false
35
+ end
36
+
37
+ def many?
38
+ false
39
+ end
40
+
41
+ def to_sql
42
+ ""
43
+ end
44
+
45
+ def count(*)
46
+ calculate :count, nil
47
+ end
48
+
49
+ def sum(*)
50
+ calculate :sum, nil
51
+ end
52
+
53
+ def average(*)
54
+ calculate :average, nil
55
+ end
56
+
57
+ def minimum(*)
58
+ calculate :minimum, nil
59
+ end
60
+
61
+ def maximum(*)
62
+ calculate :maximum, nil
63
+ end
64
+
65
+ def calculate(operation, _column_name, _options = {})
66
+ # TODO: Remove _options argument as soon we remove support to
67
+ # activerecord-deprecated_finders.
68
+ if [:count, :sum, :size].include? operation
69
+ group_values.any? ? Hash.new : 0
70
+ elsif [:average, :minimum, :maximum].include?(operation) && group_values.any?
71
+ Hash.new
72
+ else
73
+ nil
74
+ end
75
+ end
76
+
77
+ def exists?(_id = false)
78
+ false
79
+ end
80
+ end
81
+ end
@@ -1,5 +1,3 @@
1
- require 'active_support/concern'
2
-
3
1
  module ActiveRecord
4
2
  # = Active Record Persistence
5
3
  module Persistence
@@ -9,58 +7,94 @@ module ActiveRecord
9
7
  # Creates an object (or multiple objects) and saves it to the database, if validations pass.
10
8
  # The resulting object is returned whether the object was saved successfully to the database or not.
11
9
  #
12
- # The +attributes+ parameter can be either be a Hash or an Array of Hashes. These Hashes describe the
10
+ # The +attributes+ parameter can be either a Hash or an Array of Hashes. These Hashes describe the
13
11
  # attributes on the objects that are to be created.
14
12
  #
15
- # +create+ respects mass-assignment security and accepts either +:as+ or +:without_protection+ options
16
- # in the +options+ parameter.
17
- #
18
13
  # ==== Examples
19
14
  # # Create a single new object
20
- # User.create(:first_name => 'Jamie')
21
- #
22
- # # Create a single new object using the :admin mass-assignment security role
23
- # User.create({ :first_name => 'Jamie', :is_admin => true }, :as => :admin)
24
- #
25
- # # Create a single new object bypassing mass-assignment security
26
- # User.create({ :first_name => 'Jamie', :is_admin => true }, :without_protection => true)
15
+ # User.create(first_name: 'Jamie')
27
16
  #
28
17
  # # Create an Array of new objects
29
- # User.create([{ :first_name => 'Jamie' }, { :first_name => 'Jeremy' }])
18
+ # User.create([{ first_name: 'Jamie' }, { first_name: 'Jeremy' }])
30
19
  #
31
20
  # # Create a single object and pass it into a block to set other attributes.
32
- # User.create(:first_name => 'Jamie') do |u|
21
+ # User.create(first_name: 'Jamie') do |u|
33
22
  # u.is_admin = false
34
23
  # end
35
24
  #
36
25
  # # Creating an Array of new objects using a block, where the block is executed for each object:
37
- # User.create([{ :first_name => 'Jamie' }, { :first_name => 'Jeremy' }]) do |u|
26
+ # User.create([{ first_name: 'Jamie' }, { first_name: 'Jeremy' }]) do |u|
38
27
  # u.is_admin = false
39
28
  # end
40
- def create(attributes = nil, options = {}, &block)
29
+ def create(attributes = nil, &block)
41
30
  if attributes.is_a?(Array)
42
- attributes.collect { |attr| create(attr, options, &block) }
31
+ attributes.collect { |attr| create(attr, &block) }
43
32
  else
44
- object = new(attributes, options, &block)
33
+ object = new(attributes, &block)
45
34
  object.save
46
35
  object
47
36
  end
48
37
  end
38
+
39
+ # Creates an object (or multiple objects) and saves it to the database,
40
+ # if validations pass. Raises a RecordInvalid error if validations fail,
41
+ # unlike Base#create.
42
+ #
43
+ # The +attributes+ parameter can be either a Hash or an Array of Hashes.
44
+ # These describe which attributes to be created on the object, or
45
+ # multiple objects when given an Array of Hashes.
46
+ def create!(attributes = nil, &block)
47
+ if attributes.is_a?(Array)
48
+ attributes.collect { |attr| create!(attr, &block) }
49
+ else
50
+ object = new(attributes, &block)
51
+ object.save!
52
+ object
53
+ end
54
+ end
55
+
56
+ # Given an attributes hash, +instantiate+ returns a new instance of
57
+ # the appropriate class. Accepts only keys as strings.
58
+ #
59
+ # For example, +Post.all+ may return Comments, Messages, and Emails
60
+ # by storing the record's subclass in a +type+ attribute. By calling
61
+ # +instantiate+ instead of +new+, finder methods ensure they get new
62
+ # instances of the appropriate class for each record.
63
+ #
64
+ # See +ActiveRecord::Inheritance#discriminate_class_for_record+ to see
65
+ # how this "single-table" inheritance mapping is implemented.
66
+ def instantiate(attributes, column_types = {})
67
+ klass = discriminate_class_for_record(attributes)
68
+ attributes = klass.attributes_builder.build_from_database(attributes, column_types)
69
+ klass.allocate.init_with('attributes' => attributes, 'new_record' => false)
70
+ end
71
+
72
+ private
73
+ # Called by +instantiate+ to decide which class to use for a new
74
+ # record instance.
75
+ #
76
+ # See +ActiveRecord::Inheritance#discriminate_class_for_record+ for
77
+ # the single-table inheritance discriminator.
78
+ def discriminate_class_for_record(record)
79
+ self
80
+ end
49
81
  end
50
82
 
51
83
  # Returns true if this object hasn't been saved yet -- that is, a record
52
- # for the object doesn't exist in the data store yet; otherwise, returns false.
84
+ # for the object doesn't exist in the database yet; otherwise, returns false.
53
85
  def new_record?
86
+ sync_with_transaction_state
54
87
  @new_record
55
88
  end
56
89
 
57
90
  # Returns true if this object has been destroyed, otherwise returns false.
58
91
  def destroyed?
92
+ sync_with_transaction_state
59
93
  @destroyed
60
94
  end
61
95
 
62
- # Returns if the record is persisted, i.e. it's not a new record and it was
63
- # not destroyed.
96
+ # Returns true if the record is persisted, i.e. it's not a new record and it was
97
+ # not destroyed, otherwise returns false.
64
98
  def persisted?
65
99
  !(new_record? || destroyed?)
66
100
  end
@@ -72,19 +106,20 @@ module ActiveRecord
72
106
  #
73
107
  # By default, save always run validations. If any of them fail the action
74
108
  # is cancelled and +save+ returns +false+. However, if you supply
75
- # :validate => false, validations are bypassed altogether. See
109
+ # validate: false, validations are bypassed altogether. See
76
110
  # ActiveRecord::Validations for more information.
77
111
  #
78
112
  # There's a series of callbacks associated with +save+. If any of the
79
113
  # <tt>before_*</tt> callbacks return +false+ the action is cancelled and
80
114
  # +save+ returns +false+. See ActiveRecord::Callbacks for further
81
115
  # details.
116
+ #
117
+ # Attributes marked as readonly are silently ignored if the record is
118
+ # being updated.
82
119
  def save(*)
83
- begin
84
- create_or_update
85
- rescue ActiveRecord::RecordInvalid
86
- false
87
- end
120
+ create_or_update
121
+ rescue ActiveRecord::RecordInvalid
122
+ false
88
123
  end
89
124
 
90
125
  # Saves the model.
@@ -100,8 +135,11 @@ module ActiveRecord
100
135
  # the <tt>before_*</tt> callbacks return +false+ the action is cancelled
101
136
  # and <tt>save!</tt> raises ActiveRecord::RecordNotSaved. See
102
137
  # ActiveRecord::Callbacks for further details.
138
+ #
139
+ # Attributes marked as readonly are silently ignored if the record is
140
+ # being updated.
103
141
  def save!(*)
104
- create_or_update || raise(RecordNotSaved)
142
+ create_or_update || raise(RecordNotSaved.new("Failed to save the record", self))
105
143
  end
106
144
 
107
145
  # Deletes the record in the database and freezes this instance to
@@ -112,45 +150,47 @@ module ActiveRecord
112
150
  # record's primary key, and no callbacks are executed.
113
151
  #
114
152
  # To enforce the object's +before_destroy+ and +after_destroy+
115
- # callbacks, Observer methods, or any <tt>:dependent</tt> association
153
+ # callbacks or any <tt>:dependent</tt> association
116
154
  # options, use <tt>#destroy</tt>.
117
155
  def delete
118
- if persisted?
119
- self.class.delete(id)
120
- IdentityMap.remove(self) if IdentityMap.enabled?
121
- end
156
+ self.class.delete(id) if persisted?
122
157
  @destroyed = true
123
158
  freeze
124
159
  end
125
160
 
126
161
  # Deletes the record in the database and freezes this instance to reflect
127
162
  # that no changes should be made (since they can't be persisted).
163
+ #
164
+ # There's a series of callbacks associated with <tt>destroy</tt>. If
165
+ # the <tt>before_destroy</tt> callback return +false+ the action is cancelled
166
+ # and <tt>destroy</tt> returns +false+. See
167
+ # ActiveRecord::Callbacks for further details.
128
168
  def destroy
169
+ raise ReadOnlyRecord, "#{self.class} is marked as readonly" if readonly?
129
170
  destroy_associations
130
-
131
- if persisted?
132
- IdentityMap.remove(self) if IdentityMap.enabled?
133
- pk = self.class.primary_key
134
- column = self.class.columns_hash[pk]
135
- substitute = connection.substitute_at(column, 0)
136
-
137
- relation = self.class.unscoped.where(
138
- self.class.arel_table[pk].eq(substitute))
139
-
140
- relation.bind_values = [[column, id]]
141
- relation.delete_all
142
- end
143
-
171
+ self.class.connection.add_transaction_record(self)
172
+ destroy_row if persisted?
144
173
  @destroyed = true
145
174
  freeze
146
175
  end
147
176
 
177
+ # Deletes the record in the database and freezes this instance to reflect
178
+ # that no changes should be made (since they can't be persisted).
179
+ #
180
+ # There's a series of callbacks associated with <tt>destroy!</tt>. If
181
+ # the <tt>before_destroy</tt> callback return +false+ the action is cancelled
182
+ # and <tt>destroy!</tt> raises ActiveRecord::RecordNotDestroyed. See
183
+ # ActiveRecord::Callbacks for further details.
184
+ def destroy!
185
+ destroy || raise(RecordNotDestroyed.new("Failed to destroy the record", self))
186
+ end
187
+
148
188
  # Returns an instance of the specified +klass+ with the attributes of the
149
189
  # current record. This is mostly useful in relation to single-table
150
190
  # inheritance structures where you want a subclass to appear as the
151
191
  # superclass. This can be used along with record identification in
152
192
  # Action Pack to allow, say, <tt>Client < Company</tt> to do something
153
- # like render <tt>:partial => @client.becomes(Company)</tt> to render that
193
+ # like render <tt>partial: @client.becomes(Company)</tt> to render that
154
194
  # instance using the companies/company partial instead of clients/client.
155
195
  #
156
196
  # Note: The new instance will share a link to the same attributes as the original class.
@@ -158,11 +198,27 @@ module ActiveRecord
158
198
  def becomes(klass)
159
199
  became = klass.new
160
200
  became.instance_variable_set("@attributes", @attributes)
161
- became.instance_variable_set("@attributes_cache", @attributes_cache)
201
+ changed_attributes = @changed_attributes if defined?(@changed_attributes)
202
+ became.instance_variable_set("@changed_attributes", changed_attributes || {})
162
203
  became.instance_variable_set("@new_record", new_record?)
163
204
  became.instance_variable_set("@destroyed", destroyed?)
164
205
  became.instance_variable_set("@errors", errors)
165
- became.send("#{klass.inheritance_column}=", klass.name) unless self.class.descends_from_active_record?
206
+ became
207
+ end
208
+
209
+ # Wrapper around +becomes+ that also changes the instance's sti column value.
210
+ # This is especially useful if you want to persist the changed class in your
211
+ # database.
212
+ #
213
+ # Note: The old instance's sti column value will be changed too, as both objects
214
+ # share the same set of attributes.
215
+ def becomes!(klass)
216
+ became = becomes(klass)
217
+ sti_type = nil
218
+ if !klass.descends_from_active_record?
219
+ sti_type = klass.sti_name
220
+ end
221
+ became.public_send("#{klass.inheritance_column}=", sti_type)
166
222
  became
167
223
  end
168
224
 
@@ -174,61 +230,81 @@ module ActiveRecord
174
230
  # * updated_at/updated_on column is updated if that column is available.
175
231
  # * Updates all the attributes that are dirty in this object.
176
232
  #
233
+ # This method raises an +ActiveRecord::ActiveRecordError+ if the
234
+ # attribute is marked as readonly.
235
+ #
236
+ # See also +update_column+.
177
237
  def update_attribute(name, value)
178
238
  name = name.to_s
179
- raise ActiveRecordError, "#{name} is marked as readonly" if self.class.readonly_attributes.include?(name)
239
+ verify_readonly_attribute(name)
180
240
  send("#{name}=", value)
181
- save(:validate => false)
182
- end
183
-
184
- # Updates a single attribute of an object, without calling save.
185
- #
186
- # * Validation is skipped.
187
- # * Callbacks are skipped.
188
- # * updated_at/updated_on column is not updated if that column is available.
189
- #
190
- # Raises an +ActiveRecordError+ when called on new objects, or when the +name+
191
- # attribute is marked as readonly.
192
- def update_column(name, value)
193
- name = name.to_s
194
- raise ActiveRecordError, "#{name} is marked as readonly" if self.class.readonly_attributes.include?(name)
195
- raise ActiveRecordError, "can not update on a new record object" unless persisted?
196
-
197
- updated_count = self.class.unscoped.update_all({ name => value }, self.class.primary_key => id)
198
-
199
- raw_write_attribute(name, value)
200
-
201
- updated_count == 1
241
+ save(validate: false)
202
242
  end
203
243
 
204
244
  # Updates the attributes of the model from the passed-in hash and saves the
205
245
  # record, all wrapped in a transaction. If the object is invalid, the saving
206
246
  # will fail and false will be returned.
207
- #
208
- # When updating model attributes, mass-assignment security protection is respected.
209
- # If no +:as+ option is supplied then the +:default+ role will be used.
210
- # If you want to bypass the protection given by +attr_protected+ and
211
- # +attr_accessible+ then you can do so using the +:without_protection+ option.
212
- def update_attributes(attributes, options = {})
247
+ def update(attributes)
213
248
  # The following transaction covers any possible database side-effects of the
214
249
  # attributes assignment. For example, setting the IDs of a child collection.
215
250
  with_transaction_returning_status do
216
- self.assign_attributes(attributes, options)
251
+ assign_attributes(attributes)
217
252
  save
218
253
  end
219
254
  end
220
255
 
221
- # Updates its receiver just like +update_attributes+ but calls <tt>save!</tt> instead
256
+ alias update_attributes update
257
+
258
+ # Updates its receiver just like +update+ but calls <tt>save!</tt> instead
222
259
  # of +save+, so an exception is raised if the record is invalid.
223
- def update_attributes!(attributes, options = {})
260
+ def update!(attributes)
224
261
  # The following transaction covers any possible database side-effects of the
225
262
  # attributes assignment. For example, setting the IDs of a child collection.
226
263
  with_transaction_returning_status do
227
- self.assign_attributes(attributes, options)
264
+ assign_attributes(attributes)
228
265
  save!
229
266
  end
230
267
  end
231
268
 
269
+ alias update_attributes! update!
270
+
271
+ # Equivalent to <code>update_columns(name => value)</code>.
272
+ def update_column(name, value)
273
+ update_columns(name => value)
274
+ end
275
+
276
+ # Updates the attributes directly in the database issuing an UPDATE SQL
277
+ # statement and sets them in the receiver:
278
+ #
279
+ # user.update_columns(last_request_at: Time.current)
280
+ #
281
+ # This is the fastest way to update attributes because it goes straight to
282
+ # the database, but take into account that in consequence the regular update
283
+ # procedures are totally bypassed. In particular:
284
+ #
285
+ # * Validations are skipped.
286
+ # * Callbacks are skipped.
287
+ # * +updated_at+/+updated_on+ are not updated.
288
+ #
289
+ # This method raises an +ActiveRecord::ActiveRecordError+ when called on new
290
+ # objects, or when at least one of the attributes is marked as readonly.
291
+ def update_columns(attributes)
292
+ raise ActiveRecordError, "cannot update a new record" if new_record?
293
+ raise ActiveRecordError, "cannot update a destroyed record" if destroyed?
294
+
295
+ attributes.each_key do |key|
296
+ verify_readonly_attribute(key.to_s)
297
+ end
298
+
299
+ updated_count = self.class.unscoped.where(self.class.primary_key => id).update_all(attributes)
300
+
301
+ attributes.each do |k, v|
302
+ raw_write_attribute(k, v)
303
+ end
304
+
305
+ updated_count == 1
306
+ end
307
+
232
308
  # Initializes +attribute+ to zero if +nil+ and adds the value passed as +by+ (default is 1).
233
309
  # The increment is performed directly on the underlying attribute, no setter is invoked.
234
310
  # Only makes sense for number-based attributes. Returns +self+.
@@ -280,60 +356,124 @@ module ActiveRecord
280
356
  toggle(attribute).update_attribute(attribute, self[attribute])
281
357
  end
282
358
 
283
- # Reloads the attributes of this object from the database.
284
- # The optional options argument is passed to find when reloading so you
285
- # may do e.g. record.reload(:lock => true) to reload the same record with
286
- # an exclusive row lock.
359
+ # Reloads the record from the database.
360
+ #
361
+ # This method finds record by its primary key (which could be assigned manually) and
362
+ # modifies the receiver in-place:
363
+ #
364
+ # account = Account.new
365
+ # # => #<Account id: nil, email: nil>
366
+ # account.id = 1
367
+ # account.reload
368
+ # # Account Load (1.2ms) SELECT "accounts".* FROM "accounts" WHERE "accounts"."id" = $1 LIMIT 1 [["id", 1]]
369
+ # # => #<Account id: 1, email: 'account@example.com'>
370
+ #
371
+ # Attributes are reloaded from the database, and caches busted, in
372
+ # particular the associations cache and the QueryCache.
373
+ #
374
+ # If the record no longer exists in the database <tt>ActiveRecord::RecordNotFound</tt>
375
+ # is raised. Otherwise, in addition to the in-place modification the method
376
+ # returns +self+ for convenience.
377
+ #
378
+ # The optional <tt>:lock</tt> flag option allows you to lock the reloaded record:
379
+ #
380
+ # reload(lock: true) # reload with pessimistic locking
381
+ #
382
+ # Reloading is commonly used in test suites to test something is actually
383
+ # written to the database, or when some action modifies the corresponding
384
+ # row in the database but not the object in memory:
385
+ #
386
+ # assert account.deposit!(25)
387
+ # assert_equal 25, account.credit # check it is updated in memory
388
+ # assert_equal 25, account.reload.credit # check it is also persisted
389
+ #
390
+ # Another common use case is optimistic locking handling:
391
+ #
392
+ # def with_optimistic_retry
393
+ # begin
394
+ # yield
395
+ # rescue ActiveRecord::StaleObjectError
396
+ # begin
397
+ # # Reload lock_version in particular.
398
+ # reload
399
+ # rescue ActiveRecord::RecordNotFound
400
+ # # If the record is gone there is nothing to do.
401
+ # else
402
+ # retry
403
+ # end
404
+ # end
405
+ # end
406
+ #
287
407
  def reload(options = nil)
288
408
  clear_aggregation_cache
289
409
  clear_association_cache
410
+ self.class.connection.clear_query_cache
290
411
 
291
- IdentityMap.without do
292
- fresh_object = self.class.unscoped { self.class.find(self.id, options) }
293
- @attributes.update(fresh_object.instance_variable_get('@attributes'))
294
- end
412
+ fresh_object =
413
+ if options && options[:lock]
414
+ self.class.unscoped { self.class.lock(options[:lock]).find(id) }
415
+ else
416
+ self.class.unscoped { self.class.find(id) }
417
+ end
295
418
 
296
- @attributes_cache = {}
419
+ @attributes = fresh_object.instance_variable_get('@attributes')
420
+ @new_record = false
297
421
  self
298
422
  end
299
423
 
300
424
  # Saves the record with the updated_at/on attributes set to the current time.
301
- # Please note that no validation is performed and no callbacks are executed.
302
- # If an attribute name is passed, that attribute is updated along with
303
- # updated_at/on attributes.
425
+ # Please note that no validation is performed and only the +after_touch+,
426
+ # +after_commit+ and +after_rollback+ callbacks are executed.
304
427
  #
305
- # product.touch # updates updated_at/on
306
- # product.touch(:designed_at) # updates the designed_at attribute and updated_at/on
428
+ # If attribute names are passed, they are updated along with updated_at/on
429
+ # attributes.
307
430
  #
308
- # If used along with +belongs_to+ then +touch+ will invoke +touch+ method on associated object.
431
+ # product.touch # updates updated_at/on
432
+ # product.touch(:designed_at) # updates the designed_at attribute and updated_at/on
433
+ # product.touch(:started_at, :ended_at) # updates started_at, ended_at and updated_at/on attributes
434
+ #
435
+ # If used along with +belongs_to+ then +touch+ will invoke +touch+ method on
436
+ # associated object.
309
437
  #
310
438
  # class Brake < ActiveRecord::Base
311
- # belongs_to :car, :touch => true
439
+ # belongs_to :car, touch: true
312
440
  # end
313
441
  #
314
442
  # class Car < ActiveRecord::Base
315
- # belongs_to :corporation, :touch => true
443
+ # belongs_to :corporation, touch: true
316
444
  # end
317
445
  #
318
446
  # # triggers @brake.car.touch and @brake.car.corporation.touch
319
447
  # @brake.touch
320
- def touch(name = nil)
448
+ #
449
+ # Note that +touch+ must be used on a persisted object, or else an
450
+ # ActiveRecordError will be thrown. For example:
451
+ #
452
+ # ball = Ball.new
453
+ # ball.touch(:updated_at) # => raises ActiveRecordError
454
+ #
455
+ def touch(*names)
456
+ raise ActiveRecordError, "cannot touch on a new record object" unless persisted?
457
+
321
458
  attributes = timestamp_attributes_for_update_in_model
322
- attributes << name if name
459
+ attributes.concat(names)
323
460
 
324
461
  unless attributes.empty?
325
462
  current_time = current_time_from_proper_timezone
326
463
  changes = {}
327
464
 
328
465
  attributes.each do |column|
329
- changes[column.to_s] = write_attribute(column.to_s, current_time)
466
+ column = column.to_s
467
+ changes[column] = write_attribute(column, current_time)
330
468
  end
331
469
 
332
470
  changes[self.class.locking_column] = increment_lock if locking_enabled?
333
471
 
334
- @changed_attributes.except!(*changes.keys)
472
+ clear_attribute_changes(changes.keys)
335
473
  primary_key = self.class.primary_key
336
- self.class.unscoped.update_all(changes, { primary_key => self[primary_key] }) == 1
474
+ self.class.unscoped.where(primary_key => self[primary_key]).update_all(changes) == 1
475
+ else
476
+ true
337
477
  end
338
478
  end
339
479
 
@@ -343,34 +483,53 @@ module ActiveRecord
343
483
  def destroy_associations
344
484
  end
345
485
 
486
+ def destroy_row
487
+ relation_for_destroy.delete_all
488
+ end
489
+
490
+ def relation_for_destroy
491
+ pk = self.class.primary_key
492
+ column = self.class.columns_hash[pk]
493
+ substitute = self.class.connection.substitute_at(column)
494
+
495
+ relation = self.class.unscoped.where(
496
+ self.class.arel_table[pk].eq(substitute))
497
+
498
+ relation.bind_values = [[column, id]]
499
+ relation
500
+ end
501
+
346
502
  def create_or_update
347
- raise ReadOnlyRecord if readonly?
348
- result = new_record? ? create : update
503
+ raise ReadOnlyRecord, "#{self.class} is marked as readonly" if readonly?
504
+ result = new_record? ? _create_record : _update_record
349
505
  result != false
350
506
  end
351
507
 
352
508
  # Updates the associated record with values matching those of the instance attributes.
353
509
  # Returns the number of affected rows.
354
- def update(attribute_names = @attributes.keys)
355
- attributes_with_values = arel_attributes_values(false, false, attribute_names)
356
- return 0 if attributes_with_values.empty?
357
- klass = self.class
358
- stmt = klass.unscoped.where(klass.arel_table[klass.primary_key].eq(id)).arel.compile_update(attributes_with_values)
359
- klass.connection.update stmt
510
+ def _update_record(attribute_names = self.attribute_names)
511
+ attributes_values = arel_attributes_with_values_for_update(attribute_names)
512
+ if attributes_values.empty?
513
+ 0
514
+ else
515
+ self.class.unscoped._update_record attributes_values, id, id_was
516
+ end
360
517
  end
361
518
 
362
519
  # Creates a record with values matching those of the instance attributes
363
520
  # and returns its id.
364
- def create
365
- attributes_values = arel_attributes_values(!id.nil?)
521
+ def _create_record(attribute_names = self.attribute_names)
522
+ attributes_values = arel_attributes_with_values_for_create(attribute_names)
366
523
 
367
524
  new_id = self.class.unscoped.insert attributes_values
368
-
369
525
  self.id ||= new_id if self.class.primary_key
370
526
 
371
- IdentityMap.add(self) if IdentityMap.enabled?
372
527
  @new_record = false
373
528
  id
374
529
  end
530
+
531
+ def verify_readonly_attribute(name)
532
+ raise ActiveRecordError, "#{name} is marked as readonly" if self.class.readonly_attributes.include?(name)
533
+ end
375
534
  end
376
535
  end