activerecord 4.2.0 → 4.2.11

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 (110) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +657 -1
  3. data/lib/active_record.rb +3 -0
  4. data/lib/active_record/aggregations.rb +6 -3
  5. data/lib/active_record/association_relation.rb +13 -0
  6. data/lib/active_record/associations.rb +5 -4
  7. data/lib/active_record/associations/association.rb +15 -3
  8. data/lib/active_record/associations/association_scope.rb +1 -0
  9. data/lib/active_record/associations/belongs_to_association.rb +13 -5
  10. data/lib/active_record/associations/builder/association.rb +1 -1
  11. data/lib/active_record/associations/builder/collection_association.rb +5 -1
  12. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +8 -4
  13. data/lib/active_record/associations/collection_association.rb +35 -15
  14. data/lib/active_record/associations/collection_proxy.rb +15 -9
  15. data/lib/active_record/associations/foreign_association.rb +11 -0
  16. data/lib/active_record/associations/has_many_association.rb +30 -15
  17. data/lib/active_record/associations/has_many_through_association.rb +11 -2
  18. data/lib/active_record/associations/has_one_association.rb +1 -0
  19. data/lib/active_record/associations/join_dependency.rb +8 -2
  20. data/lib/active_record/associations/join_dependency/join_association.rb +7 -1
  21. data/lib/active_record/associations/preloader.rb +4 -4
  22. data/lib/active_record/associations/preloader/association.rb +5 -1
  23. data/lib/active_record/associations/singular_association.rb +2 -8
  24. data/lib/active_record/associations/through_association.rb +11 -6
  25. data/lib/active_record/attribute.rb +15 -1
  26. data/lib/active_record/attribute_assignment.rb +2 -2
  27. data/lib/active_record/attribute_methods.rb +4 -8
  28. data/lib/active_record/attribute_methods/before_type_cast.rb +5 -0
  29. data/lib/active_record/attribute_methods/dirty.rb +14 -4
  30. data/lib/active_record/attribute_methods/time_zone_conversion.rb +5 -1
  31. data/lib/active_record/attribute_methods/write.rb +1 -1
  32. data/lib/active_record/attribute_set.rb +4 -0
  33. data/lib/active_record/attribute_set/builder.rb +32 -12
  34. data/lib/active_record/attributes.rb +8 -0
  35. data/lib/active_record/autosave_association.rb +24 -9
  36. data/lib/active_record/base.rb +4 -5
  37. data/lib/active_record/callbacks.rb +1 -1
  38. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +12 -6
  39. data/lib/active_record/connection_adapters/abstract/database_statements.rb +23 -3
  40. data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -1
  41. data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -0
  42. data/lib/active_record/connection_adapters/abstract/savepoints.rb +1 -1
  43. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +26 -16
  44. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +87 -24
  45. data/lib/active_record/connection_adapters/abstract/transaction.rb +2 -6
  46. data/lib/active_record/connection_adapters/abstract_adapter.rb +25 -7
  47. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +73 -10
  48. data/lib/active_record/connection_adapters/column.rb +2 -2
  49. data/lib/active_record/connection_adapters/mysql2_adapter.rb +7 -21
  50. data/lib/active_record/connection_adapters/mysql_adapter.rb +10 -3
  51. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +1 -1
  52. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +2 -1
  53. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +1 -0
  54. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +9 -0
  55. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +3 -1
  56. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +1 -1
  57. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +4 -0
  58. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +17 -5
  59. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +3 -3
  60. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +21 -13
  61. data/lib/active_record/connection_adapters/postgresql_adapter.rb +12 -12
  62. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +12 -28
  63. data/lib/active_record/connection_handling.rb +1 -1
  64. data/lib/active_record/core.rb +28 -15
  65. data/lib/active_record/counter_cache.rb +1 -1
  66. data/lib/active_record/enum.rb +2 -3
  67. data/lib/active_record/errors.rb +6 -5
  68. data/lib/active_record/explain_subscriber.rb +1 -1
  69. data/lib/active_record/fixtures.rb +9 -7
  70. data/lib/active_record/gem_version.rb +1 -1
  71. data/lib/active_record/legacy_yaml_adapter.rb +30 -0
  72. data/lib/active_record/locking/optimistic.rb +16 -14
  73. data/lib/active_record/migration.rb +38 -10
  74. data/lib/active_record/model_schema.rb +4 -2
  75. data/lib/active_record/nested_attributes.rb +13 -3
  76. data/lib/active_record/no_touching.rb +1 -1
  77. data/lib/active_record/persistence.rb +7 -4
  78. data/lib/active_record/railtie.rb +5 -3
  79. data/lib/active_record/railties/databases.rake +17 -24
  80. data/lib/active_record/reflection.rb +40 -28
  81. data/lib/active_record/relation.rb +3 -2
  82. data/lib/active_record/relation/calculations.rb +10 -3
  83. data/lib/active_record/relation/delegation.rb +1 -1
  84. data/lib/active_record/relation/finder_methods.rb +4 -16
  85. data/lib/active_record/relation/merger.rb +24 -1
  86. data/lib/active_record/relation/predicate_builder.rb +32 -3
  87. data/lib/active_record/relation/predicate_builder/array_handler.rb +3 -2
  88. data/lib/active_record/relation/query_methods.rb +29 -27
  89. data/lib/active_record/relation/spawn_methods.rb +7 -3
  90. data/lib/active_record/schema_dumper.rb +1 -1
  91. data/lib/active_record/schema_migration.rb +1 -4
  92. data/lib/active_record/scoping/default.rb +1 -0
  93. data/lib/active_record/tasks/database_tasks.rb +5 -2
  94. data/lib/active_record/tasks/mysql_database_tasks.rb +30 -16
  95. data/lib/active_record/tasks/postgresql_database_tasks.rb +19 -8
  96. data/lib/active_record/transactions.rb +21 -11
  97. data/lib/active_record/type/boolean.rb +1 -0
  98. data/lib/active_record/type/date.rb +4 -0
  99. data/lib/active_record/type/date_time.rb +14 -3
  100. data/lib/active_record/type/decimal.rb +27 -3
  101. data/lib/active_record/type/hash_lookup_type_map.rb +8 -2
  102. data/lib/active_record/type/integer.rb +9 -5
  103. data/lib/active_record/type/numeric.rb +1 -1
  104. data/lib/active_record/type/serialized.rb +7 -1
  105. data/lib/active_record/type/string.rb +4 -0
  106. data/lib/active_record/type/value.rb +9 -0
  107. data/lib/active_record/validations/uniqueness.rb +16 -6
  108. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +0 -3
  109. data/lib/rails/generators/active_record/migration/templates/migration.rb +0 -6
  110. metadata +9 -7
@@ -14,7 +14,8 @@ module ActiveRecord
14
14
  it for 'IN' conditions.
15
15
  MSG
16
16
 
17
- values = values.flatten
17
+ flat_values = values.flatten
18
+ values = flat_values unless flat_values.include?(nil)
18
19
  end
19
20
 
20
21
  return attribute.in([]) if values.empty? && nils.empty?
@@ -37,7 +38,7 @@ module ActiveRecord
37
38
  array_predicates.inject { |composite, predicate| composite.or(predicate) }
38
39
  end
39
40
 
40
- module NullPredicate
41
+ module NullPredicate # :nodoc:
41
42
  def self.or(other)
42
43
  other
43
44
  end
@@ -258,7 +258,7 @@ module ActiveRecord
258
258
  def _select!(*fields) # :nodoc:
259
259
  fields.flatten!
260
260
  fields.map! do |field|
261
- klass.attribute_alias?(field) ? klass.attribute_alias(field) : field
261
+ klass.attribute_alias?(field) ? klass.attribute_alias(field).to_sym : field
262
262
  end
263
263
  self.select_values += fields
264
264
  self
@@ -757,6 +757,9 @@ module ActiveRecord
757
757
 
758
758
  def from!(value, subquery_name = nil) # :nodoc:
759
759
  self.from_value = [value, subquery_name]
760
+ if value.is_a? Relation
761
+ self.bind_values = value.arel.bind_values + value.bind_values + bind_values
762
+ end
760
763
  self
761
764
  end
762
765
 
@@ -868,12 +871,11 @@ module ActiveRecord
868
871
 
869
872
  arel.take(connection.sanitize_limit(limit_value)) if limit_value
870
873
  arel.skip(offset_value.to_i) if offset_value
871
-
872
- arel.group(*group_values.uniq.reject(&:blank?)) unless group_values.empty?
874
+ arel.group(*arel_columns(group_values.uniq.reject(&:blank?))) unless group_values.empty?
873
875
 
874
876
  build_order(arel)
875
877
 
876
- build_select(arel, select_values.uniq)
878
+ build_select(arel)
877
879
 
878
880
  arel.distinct(distinct_value)
879
881
  arel.from(build_from) if from_value
@@ -905,9 +907,9 @@ module ActiveRecord
905
907
  def where_unscoping(target_value)
906
908
  target_value = target_value.to_s
907
909
 
908
- where_values.reject! do |rel|
910
+ self.where_values = where_values.reject do |rel|
909
911
  case rel
910
- when Arel::Nodes::Between, Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual, Arel::Nodes::LessThanOrEqual, Arel::Nodes::GreaterThanOrEqual
912
+ when Arel::Nodes::Between, Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual, Arel::Nodes::LessThan, Arel::Nodes::LessThanOrEqual, Arel::Nodes::GreaterThan, Arel::Nodes::GreaterThanOrEqual
911
913
  subrelation = (rel.left.kind_of?(Arel::Attributes::Attribute) ? rel.left : rel.right)
912
914
  subrelation.name == target_value
913
915
  end
@@ -963,12 +965,9 @@ module ActiveRecord
963
965
 
964
966
  def create_binds(opts)
965
967
  bindable, non_binds = opts.partition do |column, value|
966
- case value
967
- when String, Integer, ActiveRecord::StatementCache::Substitute
968
- @klass.columns_hash.include? column.to_s
969
- else
970
- false
971
- end
968
+ PredicateBuilder.can_be_bound?(value) &&
969
+ @klass.columns_hash.include?(column.to_s) &&
970
+ !@klass.reflect_on_aggregation(column)
972
971
  end
973
972
 
974
973
  association_binds, non_binds = non_binds.partition do |column, value|
@@ -978,6 +977,8 @@ module ActiveRecord
978
977
  new_opts = {}
979
978
  binds = []
980
979
 
980
+ connection = self.connection
981
+
981
982
  bindable.each do |(column,value)|
982
983
  binds.push [@klass.columns_hash[column.to_s], value]
983
984
  new_opts[column] = connection.substitute_at(column)
@@ -1006,7 +1007,6 @@ module ActiveRecord
1006
1007
  case opts
1007
1008
  when Relation
1008
1009
  name ||= 'subquery'
1009
- self.bind_values = opts.bind_values + self.bind_values
1010
1010
  opts.arel.as(name.to_s)
1011
1011
  else
1012
1012
  opts
@@ -1054,22 +1054,26 @@ module ActiveRecord
1054
1054
  manager
1055
1055
  end
1056
1056
 
1057
- def build_select(arel, selects)
1058
- if !selects.empty?
1059
- expanded_select = selects.map do |field|
1060
- if (Symbol === field || String === field) && columns_hash.key?(field.to_s)
1061
- arel_table[field]
1062
- else
1063
- field
1064
- end
1065
- end
1066
-
1067
- arel.project(*expanded_select)
1057
+ def build_select(arel)
1058
+ if select_values.any?
1059
+ arel.project(*arel_columns(select_values.uniq))
1068
1060
  else
1069
1061
  arel.project(@klass.arel_table[Arel.star])
1070
1062
  end
1071
1063
  end
1072
1064
 
1065
+ def arel_columns(columns)
1066
+ columns.map do |field|
1067
+ if (Symbol === field || String === field) && columns_hash.key?(field.to_s) && !from_value
1068
+ arel_table[field]
1069
+ elsif Symbol === field
1070
+ connection.quote_table_name(field.to_s)
1071
+ else
1072
+ field
1073
+ end
1074
+ end
1075
+ end
1076
+
1073
1077
  def reverse_sql_order(order_query)
1074
1078
  order_query = ["#{quoted_table_name}.#{quoted_primary_key} ASC"] if order_query.empty?
1075
1079
 
@@ -1159,13 +1163,11 @@ module ActiveRecord
1159
1163
  end
1160
1164
  end
1161
1165
 
1162
- # This function is recursive just for better readablity.
1163
- # #where argument doesn't support more than one level nested hash in real world.
1164
1166
  def add_relations_to_bind_values(attributes)
1165
1167
  if attributes.is_a?(Hash)
1166
1168
  attributes.each_value do |value|
1167
1169
  if value.is_a?(ActiveRecord::Relation)
1168
- self.bind_values += value.bind_values
1170
+ self.bind_values += value.arel.bind_values + value.bind_values
1169
1171
  else
1170
1172
  add_relations_to_bind_values(value)
1171
1173
  end
@@ -12,6 +12,7 @@ module ActiveRecord
12
12
 
13
13
  # Merges in the conditions from <tt>other</tt>, if <tt>other</tt> is an <tt>ActiveRecord::Relation</tt>.
14
14
  # Returns an array representing the intersection of the resulting records with <tt>other</tt>, if <tt>other</tt> is an array.
15
+ #
15
16
  # Post.where(published: true).joins(:comments).merge( Comment.where(spam: false) )
16
17
  # # Performs a single join query with both where conditions.
17
18
  #
@@ -37,11 +38,14 @@ module ActiveRecord
37
38
  end
38
39
 
39
40
  def merge!(other) # :nodoc:
40
- if !other.is_a?(Relation) && other.respond_to?(:to_proc)
41
+ if other.is_a?(Hash)
42
+ Relation::HashMerger.new(self, other).merge
43
+ elsif other.is_a?(Relation)
44
+ Relation::Merger.new(self, other).merge
45
+ elsif other.respond_to?(:to_proc)
41
46
  instance_exec(&other)
42
47
  else
43
- klass = other.is_a?(Hash) ? Relation::HashMerger : Relation::Merger
44
- klass.new(self, other).merge
48
+ raise ArgumentError, "#{other.inspect} is not an ActiveRecord::Relation"
45
49
  end
46
50
  end
47
51
 
@@ -122,7 +122,7 @@ HEADER
122
122
  tbl.print ", id: :bigserial"
123
123
  elsif pkcol.sql_type == 'uuid'
124
124
  tbl.print ", id: :uuid"
125
- tbl.print %Q(, default: "#{pkcol.default_function}") if pkcol.default_function
125
+ tbl.print %Q(, default: #{pkcol.default_function.inspect})
126
126
  end
127
127
  else
128
128
  tbl.print ", id: false"
@@ -34,10 +34,7 @@ module ActiveRecord
34
34
  end
35
35
 
36
36
  def drop_table
37
- if table_exists?
38
- connection.remove_index table_name, name: index_name
39
- connection.drop_table(table_name)
40
- end
37
+ connection.drop_table table_name if table_exists?
41
38
  end
42
39
 
43
40
  def normalize_migration_number(number)
@@ -95,6 +95,7 @@ module ActiveRecord
95
95
  end
96
96
 
97
97
  def build_default_scope(base_rel = relation) # :nodoc:
98
+ return if abstract_class?
98
99
  if !Base.is_a?(method(:default_scope).owner)
99
100
  # The user has defined their own default scope method, so call that
100
101
  evaluate_default_scope { default_scope }
@@ -130,13 +130,16 @@ module ActiveRecord
130
130
  end
131
131
 
132
132
  def migrate
133
+ raise "Empty VERSION provided" if ENV["VERSION"] && ENV["VERSION"].empty?
134
+
133
135
  verbose = ENV["VERBOSE"] ? ENV["VERBOSE"] == "true" : true
134
136
  version = ENV["VERSION"] ? ENV["VERSION"].to_i : nil
135
137
  scope = ENV['SCOPE']
136
138
  verbose_was, Migration.verbose = Migration.verbose, verbose
137
- Migrator.migrate(Migrator.migrations_paths, version) do |migration|
139
+ Migrator.migrate(migrations_paths, version) do |migration|
138
140
  scope.blank? || scope == migration.scope
139
141
  end
142
+ ActiveRecord::Base.clear_cache!
140
143
  ensure
141
144
  Migration.verbose = verbose_was
142
145
  end
@@ -197,7 +200,7 @@ module ActiveRecord
197
200
  load_schema_current(format, file)
198
201
  end
199
202
 
200
- def schema_file(format = ActiveSupport::Base.schema_format)
203
+ def schema_file(format = ActiveRecord::Base.schema_format)
201
204
  case format
202
205
  when :ruby
203
206
  File.join(db_dir, "schema.rb")
@@ -56,21 +56,20 @@ module ActiveRecord
56
56
  end
57
57
 
58
58
  def structure_dump(filename)
59
- args = prepare_command_options('mysqldump')
59
+ args = prepare_command_options
60
60
  args.concat(["--result-file", "#{filename}"])
61
61
  args.concat(["--no-data"])
62
62
  args.concat(["#{configuration['database']}"])
63
- unless Kernel.system(*args)
64
- $stderr.puts "Could not dump the database structure. "\
65
- "Make sure `mysqldump` is in your PATH and check the command output for warnings."
66
- end
63
+
64
+ run_cmd('mysqldump', args, 'dumping')
67
65
  end
68
66
 
69
67
  def structure_load(filename)
70
- args = prepare_command_options('mysql')
68
+ args = prepare_command_options
71
69
  args.concat(['--execute', %{SET FOREIGN_KEY_CHECKS = 0; SOURCE #{filename}; SET FOREIGN_KEY_CHECKS = 1}])
72
70
  args.concat(["--database", "#{configuration['database']}"])
73
- Kernel.system(*args)
71
+
72
+ run_cmd('mysql', args, 'loading')
74
73
  end
75
74
 
76
75
  private
@@ -129,16 +128,31 @@ IDENTIFIED BY '#{configuration['password']}' WITH GRANT OPTION;
129
128
  $stdin.gets.strip
130
129
  end
131
130
 
132
- def prepare_command_options(command)
133
- args = [command]
134
- args.concat(['--user', configuration['username']]) if configuration['username']
135
- args << "--password=#{configuration['password']}" if configuration['password']
136
- args.concat(['--default-character-set', configuration['encoding']]) if configuration['encoding']
137
- configuration.slice('host', 'port', 'socket').each do |k, v|
138
- args.concat([ "--#{k}", v.to_s ]) if v
139
- end
131
+ def prepare_command_options
132
+ {
133
+ 'host' => '--host',
134
+ 'port' => '--port',
135
+ 'socket' => '--socket',
136
+ 'username' => '--user',
137
+ 'password' => '--password',
138
+ 'encoding' => '--default-character-set',
139
+ 'sslca' => '--ssl-ca',
140
+ 'sslcert' => '--ssl-cert',
141
+ 'sslcapath' => '--ssl-capath',
142
+ 'sslcipher' => '--ssl-cipher',
143
+ 'sslkey' => '--ssl-key'
144
+ }.map { |opt, arg| "#{arg}=#{configuration[opt]}" if configuration[opt] }.compact
145
+ end
146
+
147
+ def run_cmd(cmd, args, action)
148
+ fail run_cmd_error(cmd, args, action) unless Kernel.system(cmd, *args)
149
+ end
140
150
 
141
- args
151
+ def run_cmd_error(cmd, args, action)
152
+ msg = "failed to execute:\n"
153
+ msg << "#{cmd}"
154
+ msg << "Please check the output above for any errors and make sure that `#{cmd}` is installed in your PATH and has proper permissions.\n\n"
155
+ msg
142
156
  end
143
157
  end
144
158
  end
@@ -1,5 +1,3 @@
1
- require 'shellwords'
2
-
3
1
  module ActiveRecord
4
2
  module Tasks # :nodoc:
5
3
  class PostgreSQLDatabaseTasks # :nodoc:
@@ -46,20 +44,22 @@ module ActiveRecord
46
44
 
47
45
  def structure_dump(filename)
48
46
  set_psql_env
47
+ args = ['-s', '-x', '-O', '-f', filename]
49
48
  search_path = configuration['schema_search_path']
50
49
  unless search_path.blank?
51
- search_path = search_path.split(",").map{|search_path_part| "--schema=#{Shellwords.escape(search_path_part.strip)}" }.join(" ")
50
+ args += search_path.split(',').map do |part|
51
+ "--schema=#{part.strip}"
52
+ end
52
53
  end
53
-
54
- command = "pg_dump -i -s -x -O -f #{Shellwords.escape(filename)} #{search_path} #{Shellwords.escape(configuration['database'])}"
55
- raise 'Error dumping database' unless Kernel.system(command)
56
-
54
+ args << configuration['database']
55
+ run_cmd('pg_dump', args, 'dumping')
57
56
  File.open(filename, "a") { |f| f << "SET search_path TO #{connection.schema_search_path};\n\n" }
58
57
  end
59
58
 
60
59
  def structure_load(filename)
61
60
  set_psql_env
62
- Kernel.system("psql -q -f #{Shellwords.escape(filename)} #{configuration['database']}")
61
+ args = [ '-q', '-f', filename, configuration['database'] ]
62
+ run_cmd('psql', args, 'loading')
63
63
  end
64
64
 
65
65
  private
@@ -85,6 +85,17 @@ module ActiveRecord
85
85
  ENV['PGPASSWORD'] = configuration['password'].to_s if configuration['password']
86
86
  ENV['PGUSER'] = configuration['username'].to_s if configuration['username']
87
87
  end
88
+
89
+ def run_cmd(cmd, args, action)
90
+ fail run_cmd_error(cmd, args, action) unless Kernel.system(cmd, *args)
91
+ end
92
+
93
+ def run_cmd_error(cmd, args, action)
94
+ msg = "failed to execute:\n"
95
+ msg << "#{cmd} #{args.join(' ')}\n\n"
96
+ msg << "Please check the output above for any errors and make sure that `#{cmd}` is installed in your PATH and has proper permissions.\n\n"
97
+ msg
98
+ end
88
99
  end
89
100
  end
90
101
  end
@@ -328,9 +328,13 @@ module ActiveRecord
328
328
  # Add the record to the current transaction so that the +after_rollback+ and +after_commit+ callbacks
329
329
  # can be called.
330
330
  def add_to_transaction
331
- if self.class.connection.add_transaction_record(self)
332
- remember_transaction_record_state
331
+ if has_transactional_callbacks?
332
+ self.class.connection.add_transaction_record(self)
333
+ else
334
+ sync_with_transaction_state
335
+ set_transaction_state(self.class.connection.transaction_state)
333
336
  end
337
+ remember_transaction_record_state
334
338
  end
335
339
 
336
340
  # Executes +method+ within a transaction and captures its return value as a
@@ -353,6 +357,10 @@ module ActiveRecord
353
357
  raise ActiveRecord::Rollback unless status
354
358
  end
355
359
  status
360
+ ensure
361
+ if @transaction_state && @transaction_state.committed?
362
+ clear_transaction_record_state
363
+ end
356
364
  end
357
365
 
358
366
  protected
@@ -360,14 +368,12 @@ module ActiveRecord
360
368
  # Save the new record state and id of a record so it can be restored later if a transaction fails.
361
369
  def remember_transaction_record_state #:nodoc:
362
370
  @_start_transaction_state[:id] = id
363
- unless @_start_transaction_state.include?(:new_record)
364
- @_start_transaction_state[:new_record] = @new_record
365
- end
366
- unless @_start_transaction_state.include?(:destroyed)
367
- @_start_transaction_state[:destroyed] = @destroyed
368
- end
371
+ @_start_transaction_state.reverse_merge!(
372
+ new_record: @new_record,
373
+ destroyed: @destroyed,
374
+ frozen?: frozen?,
375
+ )
369
376
  @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) + 1
370
- @_start_transaction_state[:frozen?] = frozen?
371
377
  end
372
378
 
373
379
  # Clear the new record state and id of a record.
@@ -387,10 +393,14 @@ module ActiveRecord
387
393
  transaction_level = (@_start_transaction_state[:level] || 0) - 1
388
394
  if transaction_level < 1 || force
389
395
  restore_state = @_start_transaction_state
390
- thaw unless restore_state[:frozen?]
396
+ thaw
391
397
  @new_record = restore_state[:new_record]
392
398
  @destroyed = restore_state[:destroyed]
393
- write_attribute(self.class.primary_key, restore_state[:id])
399
+ pk = self.class.primary_key
400
+ if pk && read_attribute(pk) != restore_state[:id]
401
+ write_attribute(pk, restore_state[:id])
402
+ end
403
+ freeze if restore_state[:frozen?]
394
404
  end
395
405
  end
396
406
  end
@@ -16,6 +16,7 @@ module ActiveRecord
16
16
  if !ConnectionAdapters::Column::FALSE_VALUES.include?(value)
17
17
  ActiveSupport::Deprecation.warn(<<-MSG.squish)
18
18
  You attempted to assign a value which is not explicitly `true` or `false`
19
+ (#{value.inspect})
19
20
  to a boolean column. Currently this value casts to `false`. This will
20
21
  change to match Ruby's semantics, and will cast to `true` in Rails 5.
21
22
  If you would like to maintain the current behavior, you should
@@ -9,6 +9,10 @@ module ActiveRecord
9
9
  ::Date
10
10
  end
11
11
 
12
+ def type_cast_for_database(value)
13
+ type_cast(value)
14
+ end
15
+
12
16
  def type_cast_for_schema(value)
13
17
  "'#{value.to_s(:db)}'"
14
18
  end
@@ -8,17 +8,28 @@ module ActiveRecord
8
8
  end
9
9
 
10
10
  def type_cast_for_database(value)
11
+ return super unless value.acts_like?(:time)
12
+
11
13
  zone_conversion_method = ActiveRecord::Base.default_timezone == :utc ? :getutc : :getlocal
12
14
 
13
- if value.acts_like?(:time)
14
- value.send(zone_conversion_method)
15
+ if value.respond_to?(zone_conversion_method)
16
+ value = value.send(zone_conversion_method)
17
+ end
18
+
19
+ return value unless has_precision?
20
+
21
+ result = value.to_s(:db)
22
+ if value.respond_to?(:usec) && (1..6).cover?(precision)
23
+ "#{result}.#{sprintf("%0#{precision}d", value.usec / 10 ** (6 - precision))}"
15
24
  else
16
- super
25
+ result
17
26
  end
18
27
  end
19
28
 
20
29
  private
21
30
 
31
+ alias has_precision? precision
32
+
22
33
  def cast_value(string)
23
34
  return string unless string.is_a?(::String)
24
35
  return if string.empty?