activerecord 4.1.15 → 4.2.0.beta1

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 (167) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +634 -2176
  3. data/README.rdoc +15 -10
  4. data/lib/active_record/aggregations.rb +12 -8
  5. data/lib/active_record/associations/association.rb +1 -1
  6. data/lib/active_record/associations/association_scope.rb +53 -21
  7. data/lib/active_record/associations/belongs_to_association.rb +15 -5
  8. data/lib/active_record/associations/builder/association.rb +16 -5
  9. data/lib/active_record/associations/builder/belongs_to.rb +7 -29
  10. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -11
  11. data/lib/active_record/associations/builder/has_one.rb +2 -2
  12. data/lib/active_record/associations/builder/singular_association.rb +8 -1
  13. data/lib/active_record/associations/collection_association.rb +32 -44
  14. data/lib/active_record/associations/collection_proxy.rb +1 -10
  15. data/lib/active_record/associations/has_many_association.rb +60 -14
  16. data/lib/active_record/associations/has_many_through_association.rb +34 -23
  17. data/lib/active_record/associations/has_one_association.rb +0 -1
  18. data/lib/active_record/associations/join_dependency/join_association.rb +18 -14
  19. data/lib/active_record/associations/join_dependency.rb +7 -9
  20. data/lib/active_record/associations/preloader/association.rb +9 -5
  21. data/lib/active_record/associations/preloader/through_association.rb +3 -3
  22. data/lib/active_record/associations/preloader.rb +2 -2
  23. data/lib/active_record/associations/singular_association.rb +16 -1
  24. data/lib/active_record/associations/through_association.rb +6 -22
  25. data/lib/active_record/associations.rb +58 -33
  26. data/lib/active_record/attribute.rb +131 -0
  27. data/lib/active_record/attribute_assignment.rb +19 -11
  28. data/lib/active_record/attribute_decorators.rb +66 -0
  29. data/lib/active_record/attribute_methods/before_type_cast.rb +2 -2
  30. data/lib/active_record/attribute_methods/dirty.rb +85 -42
  31. data/lib/active_record/attribute_methods/primary_key.rb +6 -8
  32. data/lib/active_record/attribute_methods/read.rb +14 -57
  33. data/lib/active_record/attribute_methods/serialization.rb +12 -146
  34. data/lib/active_record/attribute_methods/time_zone_conversion.rb +32 -40
  35. data/lib/active_record/attribute_methods/write.rb +8 -23
  36. data/lib/active_record/attribute_methods.rb +53 -90
  37. data/lib/active_record/attribute_set/builder.rb +32 -0
  38. data/lib/active_record/attribute_set.rb +77 -0
  39. data/lib/active_record/attributes.rb +122 -0
  40. data/lib/active_record/autosave_association.rb +11 -21
  41. data/lib/active_record/base.rb +9 -19
  42. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +69 -45
  43. data/lib/active_record/connection_adapters/abstract/database_statements.rb +22 -42
  44. data/lib/active_record/connection_adapters/abstract/quoting.rb +59 -60
  45. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +37 -2
  46. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +102 -21
  47. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +9 -33
  48. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +178 -55
  49. data/lib/active_record/connection_adapters/abstract/transaction.rb +120 -115
  50. data/lib/active_record/connection_adapters/abstract_adapter.rb +143 -57
  51. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +156 -107
  52. data/lib/active_record/connection_adapters/column.rb +13 -244
  53. data/lib/active_record/connection_adapters/connection_specification.rb +6 -20
  54. data/lib/active_record/connection_adapters/mysql2_adapter.rb +12 -15
  55. data/lib/active_record/connection_adapters/mysql_adapter.rb +55 -143
  56. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +15 -27
  57. data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
  58. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +39 -20
  59. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +96 -0
  60. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  61. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  62. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +14 -0
  63. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
  64. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
  65. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +27 -0
  66. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  67. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +17 -0
  68. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
  69. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  70. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  71. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
  72. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
  73. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
  74. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  75. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
  76. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  77. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +76 -0
  78. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +15 -0
  79. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
  80. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +85 -0
  81. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +26 -0
  82. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  83. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid.rb +29 -388
  85. data/lib/active_record/connection_adapters/postgresql/quoting.rb +42 -122
  86. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +4 -4
  87. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +154 -0
  88. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +86 -34
  89. data/lib/active_record/connection_adapters/postgresql/utils.rb +66 -0
  90. data/lib/active_record/connection_adapters/postgresql_adapter.rb +188 -452
  91. data/lib/active_record/connection_adapters/schema_cache.rb +14 -28
  92. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +54 -47
  93. data/lib/active_record/connection_handling.rb +1 -1
  94. data/lib/active_record/core.rb +119 -22
  95. data/lib/active_record/counter_cache.rb +60 -6
  96. data/lib/active_record/enum.rb +9 -10
  97. data/lib/active_record/errors.rb +27 -26
  98. data/lib/active_record/explain.rb +1 -1
  99. data/lib/active_record/fixtures.rb +52 -45
  100. data/lib/active_record/gem_version.rb +3 -3
  101. data/lib/active_record/inheritance.rb +33 -8
  102. data/lib/active_record/integration.rb +4 -4
  103. data/lib/active_record/locking/optimistic.rb +34 -16
  104. data/lib/active_record/migration/command_recorder.rb +19 -2
  105. data/lib/active_record/migration/join_table.rb +1 -1
  106. data/lib/active_record/migration.rb +22 -32
  107. data/lib/active_record/model_schema.rb +39 -48
  108. data/lib/active_record/nested_attributes.rb +8 -18
  109. data/lib/active_record/persistence.rb +39 -22
  110. data/lib/active_record/query_cache.rb +3 -3
  111. data/lib/active_record/querying.rb +1 -8
  112. data/lib/active_record/railtie.rb +17 -10
  113. data/lib/active_record/railties/databases.rake +47 -42
  114. data/lib/active_record/readonly_attributes.rb +0 -1
  115. data/lib/active_record/reflection.rb +225 -92
  116. data/lib/active_record/relation/batches.rb +0 -2
  117. data/lib/active_record/relation/calculations.rb +28 -32
  118. data/lib/active_record/relation/delegation.rb +1 -1
  119. data/lib/active_record/relation/finder_methods.rb +42 -20
  120. data/lib/active_record/relation/merger.rb +0 -1
  121. data/lib/active_record/relation/predicate_builder/array_handler.rb +16 -11
  122. data/lib/active_record/relation/predicate_builder/relation_handler.rb +0 -4
  123. data/lib/active_record/relation/predicate_builder.rb +1 -22
  124. data/lib/active_record/relation/query_methods.rb +98 -62
  125. data/lib/active_record/relation/spawn_methods.rb +6 -7
  126. data/lib/active_record/relation.rb +35 -11
  127. data/lib/active_record/result.rb +16 -9
  128. data/lib/active_record/sanitization.rb +8 -1
  129. data/lib/active_record/schema.rb +0 -1
  130. data/lib/active_record/schema_dumper.rb +51 -9
  131. data/lib/active_record/schema_migration.rb +4 -0
  132. data/lib/active_record/scoping/default.rb +5 -4
  133. data/lib/active_record/serializers/xml_serializer.rb +3 -7
  134. data/lib/active_record/statement_cache.rb +79 -5
  135. data/lib/active_record/store.rb +5 -5
  136. data/lib/active_record/tasks/database_tasks.rb +37 -5
  137. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
  138. data/lib/active_record/tasks/postgresql_database_tasks.rb +2 -2
  139. data/lib/active_record/timestamp.rb +9 -7
  140. data/lib/active_record/transactions.rb +35 -21
  141. data/lib/active_record/type/binary.rb +40 -0
  142. data/lib/active_record/type/boolean.rb +19 -0
  143. data/lib/active_record/type/date.rb +46 -0
  144. data/lib/active_record/type/date_time.rb +43 -0
  145. data/lib/active_record/type/decimal.rb +40 -0
  146. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  147. data/lib/active_record/type/float.rb +19 -0
  148. data/lib/active_record/type/hash_lookup_type_map.rb +19 -0
  149. data/lib/active_record/type/integer.rb +23 -0
  150. data/lib/active_record/type/mutable.rb +16 -0
  151. data/lib/active_record/type/numeric.rb +36 -0
  152. data/lib/active_record/type/serialized.rb +51 -0
  153. data/lib/active_record/type/string.rb +36 -0
  154. data/lib/active_record/type/text.rb +11 -0
  155. data/lib/active_record/type/time.rb +26 -0
  156. data/lib/active_record/type/time_value.rb +38 -0
  157. data/lib/active_record/type/type_map.rb +48 -0
  158. data/lib/active_record/type/value.rb +101 -0
  159. data/lib/active_record/type.rb +20 -0
  160. data/lib/active_record/validations/uniqueness.rb +9 -23
  161. data/lib/active_record/validations.rb +21 -16
  162. data/lib/active_record.rb +2 -1
  163. data/lib/rails/generators/active_record/migration/migration_generator.rb +8 -4
  164. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +1 -1
  165. data/lib/rails/generators/active_record/model/templates/model.rb +1 -1
  166. metadata +71 -14
  167. data/lib/active_record/connection_adapters/postgresql/cast.rb +0 -168
@@ -180,13 +180,9 @@ module ActiveRecord #:nodoc:
180
180
  class Attribute < ActiveModel::Serializers::Xml::Serializer::Attribute #:nodoc:
181
181
  def compute_type
182
182
  klass = @serializable.class
183
- type = if klass.serialized_attributes.key?(name)
184
- super
185
- elsif klass.columns_hash.key?(name)
186
- klass.columns_hash[name].type
187
- else
188
- NilClass
189
- end
183
+ column = klass.columns_hash[name] || Type::Value.new
184
+
185
+ type = ActiveSupport::XmlMini::TYPE_NAMES[value.class.name] || column.type
190
186
 
191
187
  { :text => :string,
192
188
  :time => :datetime }[type] || type
@@ -14,13 +14,87 @@ module ActiveRecord
14
14
  # The relation returned by the block is cached, and for each +execute+ call the cached relation gets duped.
15
15
  # Database is queried when +to_a+ is called on the relation.
16
16
  class StatementCache
17
- def initialize
18
- @relation = yield
19
- raise ArgumentError.new("Statement cannot be nil") if @relation.nil?
17
+ class Substitute; end
18
+
19
+ class Query
20
+ def initialize(sql)
21
+ @sql = sql
22
+ end
23
+
24
+ def sql_for(binds, connection)
25
+ @sql
26
+ end
27
+ end
28
+
29
+ class PartialQuery < Query
30
+ def initialize values
31
+ @values = values
32
+ @indexes = values.each_with_index.find_all { |thing,i|
33
+ Arel::Nodes::BindParam === thing
34
+ }.map(&:last)
35
+ end
36
+
37
+ def sql_for(binds, connection)
38
+ val = @values.dup
39
+ binds = binds.dup
40
+ @indexes.each { |i| val[i] = connection.quote(*binds.shift.reverse) }
41
+ val.join
42
+ end
43
+ end
44
+
45
+ def self.query(visitor, ast)
46
+ Query.new visitor.accept(ast, Arel::Collectors::SQLString.new).value
47
+ end
48
+
49
+ def self.partial_query(visitor, ast, collector)
50
+ collected = visitor.accept(ast, collector).value
51
+ PartialQuery.new collected
52
+ end
53
+
54
+ class Params
55
+ def bind; Substitute.new; end
20
56
  end
21
57
 
22
- def execute
23
- @relation.dup.to_a
58
+ class BindMap
59
+ def initialize(bind_values)
60
+ @indexes = []
61
+ @bind_values = bind_values
62
+
63
+ bind_values.each_with_index do |(_, value), i|
64
+ if Substitute === value
65
+ @indexes << i
66
+ end
67
+ end
68
+ end
69
+
70
+ def bind(values)
71
+ bvs = @bind_values.map { |pair| pair.dup }
72
+ @indexes.each_with_index { |offset,i| bvs[offset][1] = values[i] }
73
+ bvs
74
+ end
75
+ end
76
+
77
+ attr_reader :bind_map, :query_builder
78
+
79
+ def self.create(connection, block = Proc.new)
80
+ relation = block.call Params.new
81
+ bind_map = BindMap.new relation.bind_values
82
+ query_builder = connection.cacheable_query relation.arel
83
+ new query_builder, bind_map
84
+ end
85
+
86
+ def initialize(query_builder, bind_map)
87
+ @query_builder = query_builder
88
+ @bind_map = bind_map
89
+ end
90
+
91
+ def execute(params, klass, connection)
92
+ bind_values = bind_map.bind params
93
+
94
+ sql = query_builder.sql_for bind_values, connection
95
+
96
+ klass.find_by_sql sql, bind_values
24
97
  end
98
+ alias :call :execute
25
99
  end
26
100
  end
@@ -99,7 +99,7 @@ module ActiveRecord
99
99
  self.local_stored_attributes[store_attribute] |= keys
100
100
  end
101
101
 
102
- def _store_accessors_module
102
+ def _store_accessors_module # :nodoc:
103
103
  @_store_accessors_module ||= begin
104
104
  mod = Module.new
105
105
  include mod
@@ -129,10 +129,10 @@ module ActiveRecord
129
129
 
130
130
  private
131
131
  def store_accessor_for(store_attribute)
132
- @column_types[store_attribute.to_s].accessor
132
+ type_for_attribute(store_attribute.to_s).accessor
133
133
  end
134
134
 
135
- class HashAccessor
135
+ class HashAccessor # :nodoc:
136
136
  def self.read(object, attribute, key)
137
137
  prepare(object, attribute)
138
138
  object.public_send(attribute)[key]
@@ -151,7 +151,7 @@ module ActiveRecord
151
151
  end
152
152
  end
153
153
 
154
- class StringKeyedHashAccessor < HashAccessor
154
+ class StringKeyedHashAccessor < HashAccessor # :nodoc:
155
155
  def self.read(object, attribute, key)
156
156
  super object, attribute, key.to_s
157
157
  end
@@ -161,7 +161,7 @@ module ActiveRecord
161
161
  end
162
162
  end
163
163
 
164
- class IndifferentHashAccessor < ActiveRecord::Store::HashAccessor
164
+ class IndifferentHashAccessor < ActiveRecord::Store::HashAccessor # :nodoc:
165
165
  def self.prepare(object, store_attribute)
166
166
  attribute = object.send(store_attribute)
167
167
  unless attribute.is_a?(ActiveSupport::HashWithIndifferentAccess)
@@ -6,7 +6,7 @@ module ActiveRecord
6
6
  # <tt>ActiveRecord::Tasks::DatabaseTasks</tt> is a utility class, which encapsulates
7
7
  # logic behind common tasks used to manage database and migrations.
8
8
  #
9
- # The tasks defined here are used in rake tasks provided by Active Record.
9
+ # The tasks defined here are used with Rake tasks provided by Active Record.
10
10
  #
11
11
  # In order to use DatabaseTasks, a few config values need to be set. All the needed
12
12
  # config values are set by Rails already, so it's necessary to do it only if you
@@ -14,7 +14,6 @@ module ActiveRecord
14
14
  # (in such case after configuring the database tasks, you can also use the rake tasks
15
15
  # defined in Active Record).
16
16
  #
17
- #
18
17
  # The possible config values are:
19
18
  #
20
19
  # * +env+: current environment (like Rails.env).
@@ -28,7 +27,7 @@ module ActiveRecord
28
27
  # Example usage of +DatabaseTasks+ outside Rails could look as such:
29
28
  #
30
29
  # include ActiveRecord::Tasks
31
- # DatabaseTasks.database_configuration = YAML.load(File.read('my_database_config.yml'))
30
+ # DatabaseTasks.database_configuration = YAML.load_file('my_database_config.yml')
32
31
  # DatabaseTasks.db_dir = 'db'
33
32
  # # other settings...
34
33
  #
@@ -59,7 +58,11 @@ module ActiveRecord
59
58
  end
60
59
 
61
60
  def fixtures_path
62
- @fixtures_path ||= File.join(root, 'test', 'fixtures')
61
+ @fixtures_path ||= if ENV['FIXTURES_PATH']
62
+ File.join(root, ENV['FIXTURES_PATH'])
63
+ else
64
+ File.join(root, 'test', 'fixtures')
65
+ end
63
66
  end
64
67
 
65
68
  def root
@@ -107,6 +110,8 @@ module ActiveRecord
107
110
  def drop(*arguments)
108
111
  configuration = arguments.first
109
112
  class_for_adapter(configuration['adapter']).new(*arguments).drop
113
+ rescue ActiveRecord::NoDatabaseError
114
+ $stderr.puts "Database '#{configuration['database']}' does not exist"
110
115
  rescue Exception => error
111
116
  $stderr.puts error, *(error.backtrace)
112
117
  $stderr.puts "Couldn't drop #{configuration['database']}"
@@ -122,6 +127,16 @@ module ActiveRecord
122
127
  }
123
128
  end
124
129
 
130
+ def migrate
131
+ verbose = ENV["VERBOSE"] ? ENV["VERBOSE"] == "true" : true
132
+ version = ENV["VERSION"] ? ENV["VERSION"].to_i : nil
133
+ scope = ENV['SCOPE']
134
+ Migration.verbose = verbose
135
+ Migrator.migrate(Migrator.migrations_paths, version) do |migration|
136
+ scope.blank? || scope == migration.scope
137
+ end
138
+ end
139
+
125
140
  def charset_current(environment = env)
126
141
  charset ActiveRecord::Base.configurations[environment]
127
142
  end
@@ -144,6 +159,18 @@ module ActiveRecord
144
159
  class_for_adapter(configuration['adapter']).new(configuration).purge
145
160
  end
146
161
 
162
+ def purge_all
163
+ each_local_configuration { |configuration|
164
+ purge configuration
165
+ }
166
+ end
167
+
168
+ def purge_current(environment = env)
169
+ each_current_configuration(environment) { |configuration|
170
+ purge configuration
171
+ }
172
+ end
173
+
147
174
  def structure_dump(*arguments)
148
175
  configuration = arguments.first
149
176
  filename = arguments.delete_at 1
@@ -157,6 +184,10 @@ module ActiveRecord
157
184
  end
158
185
 
159
186
  def load_schema(format = ActiveRecord::Base.schema_format, file = nil)
187
+ ActiveSupport::Deprecation.warn(<<-MESSAGE.strip_heredoc)
188
+ This method will act on a specific connection in the future.
189
+ To act on the current connection, use `load_schema_current` instead.
190
+ MESSAGE
160
191
  load_schema_current(format, file)
161
192
  end
162
193
 
@@ -167,11 +198,13 @@ module ActiveRecord
167
198
  when :ruby
168
199
  file ||= File.join(db_dir, "schema.rb")
169
200
  check_schema_file(file)
201
+ purge(configuration)
170
202
  ActiveRecord::Base.establish_connection(configuration)
171
203
  load(file)
172
204
  when :sql
173
205
  file ||= File.join(db_dir, "structure.sql")
174
206
  check_schema_file(file)
207
+ purge(configuration)
175
208
  structure_load(configuration, file)
176
209
  else
177
210
  raise ArgumentError, "unknown format #{format.inspect}"
@@ -182,7 +215,6 @@ module ActiveRecord
182
215
  each_current_configuration(environment) { |configuration|
183
216
  load_schema_for configuration, format, file
184
217
  }
185
- ActiveRecord::Base.establish_connection(environment.to_sym)
186
218
  end
187
219
 
188
220
  def check_schema_file(filename)
@@ -124,7 +124,7 @@ IDENTIFIED BY '#{configuration['password']}' WITH GRANT OPTION;
124
124
  end
125
125
 
126
126
  def root_password
127
- $stdout.print "Please provide the root password for your mysql installation\n>"
127
+ $stdout.print "Please provide the root password for your MySQL installation\n>"
128
128
  $stdin.gets.strip
129
129
  end
130
130
 
@@ -51,10 +51,10 @@ module ActiveRecord
51
51
  search_path = search_path.split(",").map{|search_path_part| "--schema=#{Shellwords.escape(search_path_part.strip)}" }.join(" ")
52
52
  end
53
53
 
54
- command = "pg_dump -s -x -O -f #{Shellwords.escape(filename)} #{search_path} #{Shellwords.escape(configuration['database'])}"
54
+ command = "pg_dump -i -s -x -O -f #{Shellwords.escape(filename)} #{search_path} #{Shellwords.escape(configuration['database'])}"
55
55
  raise 'Error dumping database' unless Kernel.system(command)
56
56
 
57
- File.open(filename, "a") { |f| f << "SET search_path TO #{ActiveRecord::Base.connection.schema_search_path};\n\n" }
57
+ File.open(filename, "a") { |f| f << "SET search_path TO #{connection.schema_search_path};\n\n" }
58
58
  end
59
59
 
60
60
  def structure_load(filename)
@@ -1,4 +1,3 @@
1
-
2
1
  module ActiveRecord
3
2
  # = Active Record Timestamp
4
3
  #
@@ -48,8 +47,9 @@ module ActiveRecord
48
47
  current_time = current_time_from_proper_timezone
49
48
 
50
49
  all_timestamp_attributes.each do |column|
51
- if respond_to?(column) && respond_to?("#{column}=") && self.send(column).nil?
52
- write_attribute(column.to_s, current_time)
50
+ column = column.to_s
51
+ if has_attribute?(column) && !attribute_present?(column)
52
+ write_attribute(column, current_time)
53
53
  end
54
54
  end
55
55
  end
@@ -99,9 +99,11 @@ module ActiveRecord
99
99
  end
100
100
 
101
101
  def max_updated_column_timestamp(timestamp_names = timestamp_attributes_for_update)
102
- if (timestamps = timestamp_names.map { |attr| self[attr] }.compact).present?
103
- timestamps.map { |ts| ts.to_time }.max
104
- end
102
+ timestamp_names
103
+ .map { |attr| self[attr] }
104
+ .compact
105
+ .map(&:to_time)
106
+ .max
105
107
  end
106
108
 
107
109
  def current_time_from_proper_timezone
@@ -112,7 +114,7 @@ module ActiveRecord
112
114
  def clear_timestamp_attributes
113
115
  all_timestamp_attributes_in_model.each do |attribute_name|
114
116
  self[attribute_name] = nil
115
- changed_attributes.delete(attribute_name)
117
+ clear_attribute_changes([attribute_name])
116
118
  end
117
119
  end
118
120
  end
@@ -1,15 +1,25 @@
1
- require 'thread'
2
-
3
1
  module ActiveRecord
4
2
  # See ActiveRecord::Transactions::ClassMethods for documentation.
5
3
  module Transactions
6
4
  extend ActiveSupport::Concern
7
5
  ACTIONS = [:create, :destroy, :update]
6
+ CALLBACK_WARN_MESSAGE = <<-EOF
7
+ Currently, Active Record will rescue any errors raised within
8
+ after_rollback/after_commit callbacks and print them to the logs. In the next
9
+ version, these errors will no longer be rescued. Instead, they will simply
10
+ bubble just like other Active Record callbacks.
11
+
12
+ You can opt into the new behavior and remove this warning by setting
13
+ config.active_record.raise_in_transactional_callbacks to true.
14
+ EOF
8
15
 
9
16
  included do
10
17
  define_callbacks :commit, :rollback,
11
18
  terminator: ->(_, result) { result == false },
12
19
  scope: [:kind, :name]
20
+
21
+ mattr_accessor :raise_in_transactional_callbacks, instance_writer: false
22
+ self.raise_in_transactional_callbacks = false
13
23
  end
14
24
 
15
25
  # = Active Record Transactions
@@ -225,6 +235,9 @@ module ActiveRecord
225
235
  def after_commit(*args, &block)
226
236
  set_options_for_callbacks!(args)
227
237
  set_callback(:commit, :after, *args, &block)
238
+ unless ActiveRecord::Base.raise_in_transactional_callbacks
239
+ ActiveSupport::Deprecation.warn(CALLBACK_WARN_MESSAGE)
240
+ end
228
241
  end
229
242
 
230
243
  # This callback is called after a create, update, or destroy are rolled back.
@@ -233,6 +246,9 @@ module ActiveRecord
233
246
  def after_rollback(*args, &block)
234
247
  set_options_for_callbacks!(args)
235
248
  set_callback(:rollback, :after, *args, &block)
249
+ unless ActiveRecord::Base.raise_in_transactional_callbacks
250
+ ActiveSupport::Deprecation.warn(CALLBACK_WARN_MESSAGE)
251
+ end
236
252
  end
237
253
 
238
254
  private
@@ -249,7 +265,7 @@ module ActiveRecord
249
265
 
250
266
  def assert_valid_transaction_action(actions)
251
267
  if (actions - ACTIONS).any?
252
- raise ArgumentError, ":on conditions for after_commit and after_rollback callbacks have to be one of #{ACTIONS}"
268
+ raise ArgumentError, ":on conditions for after_commit and after_rollback callbacks have to be one of #{ACTIONS.join(",")}"
253
269
  end
254
270
  end
255
271
  end
@@ -292,16 +308,16 @@ module ActiveRecord
292
308
  #
293
309
  # Ensure that it is not called if the object was never persisted (failed create),
294
310
  # but call it after the commit of a destroyed object.
295
- def committed! #:nodoc:
296
- run_callbacks :commit if destroyed? || persisted?
311
+ def committed!(should_run_callbacks = true) #:nodoc:
312
+ run_callbacks :commit if should_run_callbacks && destroyed? || persisted?
297
313
  ensure
298
- @_start_transaction_state.clear
314
+ force_clear_transaction_record_state
299
315
  end
300
316
 
301
317
  # Call the +after_rollback+ callbacks. The +force_restore_state+ argument indicates if the record
302
318
  # state should be rolled back to the beginning or just to the last savepoint.
303
- def rolledback!(force_restore_state = false) #:nodoc:
304
- run_callbacks :rollback
319
+ def rolledback!(force_restore_state = false, should_run_callbacks = true) #:nodoc:
320
+ run_callbacks :rollback if should_run_callbacks
305
321
  ensure
306
322
  restore_transaction_record_state(force_restore_state)
307
323
  clear_transaction_record_state
@@ -328,7 +344,7 @@ module ActiveRecord
328
344
  begin
329
345
  status = yield
330
346
  rescue ActiveRecord::Rollback
331
- @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1
347
+ clear_transaction_record_state
332
348
  status = nil
333
349
  end
334
350
 
@@ -341,7 +357,7 @@ module ActiveRecord
341
357
 
342
358
  # Save the new record state and id of a record so it can be restored later if a transaction fails.
343
359
  def remember_transaction_record_state #:nodoc:
344
- @_start_transaction_state[:id] = id if has_attribute?(self.class.primary_key)
360
+ @_start_transaction_state[:id] = id
345
361
  unless @_start_transaction_state.include?(:new_record)
346
362
  @_start_transaction_state[:new_record] = @new_record
347
363
  end
@@ -349,13 +365,18 @@ module ActiveRecord
349
365
  @_start_transaction_state[:destroyed] = @destroyed
350
366
  end
351
367
  @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) + 1
352
- @_start_transaction_state[:frozen?] = @attributes.frozen?
368
+ @_start_transaction_state[:frozen?] = frozen?
353
369
  end
354
370
 
355
371
  # Clear the new record state and id of a record.
356
372
  def clear_transaction_record_state #:nodoc:
357
373
  @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1
358
- @_start_transaction_state.clear if @_start_transaction_state[:level] < 1
374
+ force_clear_transaction_record_state if @_start_transaction_state[:level] < 1
375
+ end
376
+
377
+ # Force to clear the transaction record state.
378
+ def force_clear_transaction_record_state #:nodoc:
379
+ @_start_transaction_state.clear
359
380
  end
360
381
 
361
382
  # Restore the new record state and id of a record that was previously saved by a call to save_record_state.
@@ -364,17 +385,10 @@ module ActiveRecord
364
385
  transaction_level = (@_start_transaction_state[:level] || 0) - 1
365
386
  if transaction_level < 1 || force
366
387
  restore_state = @_start_transaction_state
367
- was_frozen = restore_state[:frozen?]
368
- @attributes = @attributes.dup if @attributes.frozen?
388
+ thaw unless restore_state[:frozen?]
369
389
  @new_record = restore_state[:new_record]
370
390
  @destroyed = restore_state[:destroyed]
371
- if restore_state.has_key?(:id)
372
- write_attribute(self.class.primary_key, restore_state[:id])
373
- else
374
- @attributes.delete(self.class.primary_key)
375
- @attributes_cache.delete(self.class.primary_key)
376
- end
377
- @attributes.freeze if was_frozen
391
+ write_attribute(self.class.primary_key, restore_state[:id])
378
392
  end
379
393
  end
380
394
  end
@@ -0,0 +1,40 @@
1
+ module ActiveRecord
2
+ module Type
3
+ class Binary < Value # :nodoc:
4
+ def type
5
+ :binary
6
+ end
7
+
8
+ def binary?
9
+ true
10
+ end
11
+
12
+ def type_cast(value)
13
+ if value.is_a?(Data)
14
+ value.to_s
15
+ else
16
+ super
17
+ end
18
+ end
19
+
20
+ def type_cast_for_database(value)
21
+ return if value.nil?
22
+ Data.new(super)
23
+ end
24
+
25
+ class Data # :nodoc:
26
+ def initialize(value)
27
+ @value = value.to_s
28
+ end
29
+
30
+ def to_s
31
+ @value
32
+ end
33
+
34
+ def hex
35
+ @value.unpack('H*')[0]
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,19 @@
1
+ module ActiveRecord
2
+ module Type
3
+ class Boolean < Value # :nodoc:
4
+ def type
5
+ :boolean
6
+ end
7
+
8
+ private
9
+
10
+ def cast_value(value)
11
+ if value == ''
12
+ nil
13
+ else
14
+ ConnectionAdapters::Column::TRUE_VALUES.include?(value)
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,46 @@
1
+ module ActiveRecord
2
+ module Type
3
+ class Date < Value # :nodoc:
4
+ def type
5
+ :date
6
+ end
7
+
8
+ def klass
9
+ ::Date
10
+ end
11
+
12
+ def type_cast_for_schema(value)
13
+ "'#{value.to_s(:db)}'"
14
+ end
15
+
16
+ private
17
+
18
+ def cast_value(value)
19
+ if value.is_a?(::String)
20
+ return if value.empty?
21
+ fast_string_to_date(value) || fallback_string_to_date(value)
22
+ elsif value.respond_to?(:to_date)
23
+ value.to_date
24
+ else
25
+ value
26
+ end
27
+ end
28
+
29
+ def fast_string_to_date(string)
30
+ if string =~ ConnectionAdapters::Column::Format::ISO_DATE
31
+ new_date $1.to_i, $2.to_i, $3.to_i
32
+ end
33
+ end
34
+
35
+ def fallback_string_to_date(string)
36
+ new_date(*::Date._parse(string, false).values_at(:year, :mon, :mday))
37
+ end
38
+
39
+ def new_date(year, mon, mday)
40
+ if year && year != 0
41
+ ::Date.new(year, mon, mday) rescue nil
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,43 @@
1
+ module ActiveRecord
2
+ module Type
3
+ class DateTime < Value # :nodoc:
4
+ include TimeValue
5
+
6
+ def type
7
+ :datetime
8
+ end
9
+
10
+ def type_cast_for_database(value)
11
+ zone_conversion_method = ActiveRecord::Base.default_timezone == :utc ? :getutc : :getlocal
12
+
13
+ if value.acts_like?(:time)
14
+ value.send(zone_conversion_method)
15
+ else
16
+ super
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def cast_value(string)
23
+ return string unless string.is_a?(::String)
24
+ return if string.empty?
25
+
26
+ fast_string_to_time(string) || fallback_string_to_time(string)
27
+ end
28
+
29
+ # '0.123456' -> 123456
30
+ # '1.123456' -> 123456
31
+ def microseconds(time)
32
+ time[:sec_fraction] ? (time[:sec_fraction] * 1_000_000).to_i : 0
33
+ end
34
+
35
+ def fallback_string_to_time(string)
36
+ time_hash = ::Date._parse(string)
37
+ time_hash[:sec_fraction] = microseconds(time_hash)
38
+
39
+ new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction, :offset))
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,40 @@
1
+ module ActiveRecord
2
+ module Type
3
+ class Decimal < Value # :nodoc:
4
+ include Numeric
5
+
6
+ def type
7
+ :decimal
8
+ end
9
+
10
+ def type_cast_for_schema(value)
11
+ value.to_s
12
+ end
13
+
14
+ private
15
+
16
+ def cast_value(value)
17
+ case value
18
+ when ::Float
19
+ BigDecimal(value, float_precision)
20
+ when ::Numeric, ::String
21
+ BigDecimal(value, precision.to_i)
22
+ else
23
+ if value.respond_to?(:to_d)
24
+ value.to_d
25
+ else
26
+ cast_value(value.to_s)
27
+ end
28
+ end
29
+ end
30
+
31
+ def float_precision
32
+ if precision.to_i > ::Float::DIG + 1
33
+ ::Float::DIG + 1
34
+ else
35
+ precision.to_i
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,11 @@
1
+ require 'active_record/type/integer'
2
+
3
+ module ActiveRecord
4
+ module Type
5
+ class DecimalWithoutScale < Integer # :nodoc:
6
+ def type
7
+ :decimal
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,19 @@
1
+ module ActiveRecord
2
+ module Type
3
+ class Float < Value # :nodoc:
4
+ include Numeric
5
+
6
+ def type
7
+ :float
8
+ end
9
+
10
+ alias type_cast_for_database type_cast
11
+
12
+ private
13
+
14
+ def cast_value(value)
15
+ value.to_f
16
+ end
17
+ end
18
+ end
19
+ end