1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00

Merge pull request #18067 from kamipo/format_datetime_string_according_to_precision

Format the datetime string according to the precision of the datetime field.

Conflicts:
	activerecord/CHANGELOG.md
This commit is contained in:
Rafael Mendonça França 2015-01-02 11:17:55 -03:00
commit acb1991910
6 changed files with 225 additions and 1 deletions

View file

@ -1,3 +1,18 @@
* Format the datetime string according to the precision of the datetime field.
Incompatible to rounding behavior between MySQL 5.6 and earlier.
In 5.5, when you insert `2014-08-17 12:30:00.999999` the fractional part
is ignored. In 5.6, it's rounded to `2014-08-17 12:30:01`:
http://bugs.mysql.com/bug.php?id=68760
*Ryuta Kamizono*
* Allow precision option for MySQL datetimes.
*Ryuta Kamizono*
* Fixed automatic inverse_of for models nested in module.
*Andrew McCloud*

View file

@ -591,6 +591,13 @@ module ActiveRecord
when 0x1000000..0xffffffff; 'longtext'
else raise(ActiveRecordError, "No text type has character length #{limit}")
end
when 'datetime'
return super unless precision
case precision
when 0..6; "datetime(#{precision})"
else raise(ActiveRecordError, "No datetime type has precision of #{precision}. The allowed range of precision is from 0 to 6.")
end
else
super
end
@ -679,6 +686,11 @@ module ActiveRecord
m.alias_type %r(year)i, 'integer'
m.alias_type %r(bit)i, 'binary'
m.register_type(%r(datetime)i) do |sql_type|
precision = extract_precision(sql_type)
MysqlDateTime.new(precision: precision)
end
m.register_type(%r(enum)i) do |sql_type|
limit = sql_type[/^enum\((.+)\)/i, 1]
.split(',').map{|enum| enum.strip.length - 2}.max
@ -872,6 +884,22 @@ module ActiveRecord
TableDefinition.new(native_database_types, name, temporary, options, as)
end
class MysqlDateTime < Type::DateTime # :nodoc:
def type_cast_for_database(value)
if value.acts_like?(:time) && value.respond_to?(:usec)
result = super.to_s(:db)
case precision
when 1..6
"#{result}.#{sprintf("%0#{precision}d", value.usec / 10 ** (6 - precision))}"
else
result
end
else
super
end
end
end
class MysqlString < Type::String # :nodoc:
def type_cast_for_database(value)
case value

View file

@ -0,0 +1,87 @@
require 'cases/helper'
if mysql_56?
class DateTimeTest < ActiveRecord::TestCase
self.use_transactional_fixtures = false
class Foo < ActiveRecord::Base; end
def test_default_datetime_precision
ActiveRecord::Base.connection.create_table(:foos, force: true)
ActiveRecord::Base.connection.add_column :foos, :created_at, :datetime
ActiveRecord::Base.connection.add_column :foos, :updated_at, :datetime
assert_nil activerecord_column_option('foos', 'created_at', 'precision')
end
def test_datetime_data_type_with_precision
ActiveRecord::Base.connection.create_table(:foos, force: true)
ActiveRecord::Base.connection.add_column :foos, :created_at, :datetime, precision: 1
ActiveRecord::Base.connection.add_column :foos, :updated_at, :datetime, precision: 5
assert_equal 1, activerecord_column_option('foos', 'created_at', 'precision')
assert_equal 5, activerecord_column_option('foos', 'updated_at', 'precision')
end
def test_timestamps_helper_with_custom_precision
ActiveRecord::Base.connection.create_table(:foos, force: true) do |t|
t.timestamps null: true, precision: 4
end
assert_equal 4, activerecord_column_option('foos', 'created_at', 'precision')
assert_equal 4, activerecord_column_option('foos', 'updated_at', 'precision')
end
def test_passing_precision_to_datetime_does_not_set_limit
ActiveRecord::Base.connection.create_table(:foos, force: true) do |t|
t.timestamps null: true, precision: 4
end
assert_nil activerecord_column_option('foos', 'created_at', 'limit')
assert_nil activerecord_column_option('foos', 'updated_at', 'limit')
end
def test_invalid_datetime_precision_raises_error
assert_raises ActiveRecord::ActiveRecordError do
ActiveRecord::Base.connection.create_table(:foos, force: true) do |t|
t.timestamps null: true, precision: 7
end
end
end
def test_mysql_agrees_with_activerecord_about_precision
ActiveRecord::Base.connection.create_table(:foos, force: true) do |t|
t.timestamps null: true, precision: 4
end
assert_equal 4, mysql_datetime_precision('foos', 'created_at')
assert_equal 4, mysql_datetime_precision('foos', 'updated_at')
end
def test_formatting_datetime_according_to_precision
ActiveRecord::Base.connection.create_table(:foos, force: true) do |t|
t.datetime :created_at, precision: 0
t.datetime :updated_at, precision: 4
end
date = ::Time.utc(2014, 8, 17, 12, 30, 0, 999999)
Foo.create!(created_at: date, updated_at: date)
assert foo = Foo.find_by(created_at: date)
assert_equal date.to_s, foo.created_at.to_s
assert_equal date.to_s, foo.updated_at.to_s
assert_equal 000000, foo.created_at.usec
assert_equal 999900, foo.updated_at.usec
end
private
def mysql_datetime_precision(table_name, column_name)
results = ActiveRecord::Base.connection.exec_query("SELECT column_name, datetime_precision FROM information_schema.columns WHERE table_name = '#{table_name}'")
result = results.find do |result_hash|
result_hash["column_name"] == column_name
end
result && result["datetime_precision"]
end
def activerecord_column_option(tablename, column_name, option)
result = ActiveRecord::Base.connection.columns(tablename).find do |column|
column.name == column_name
end
result && result.send(option)
end
end
end

View file

@ -0,0 +1,87 @@
require 'cases/helper'
if mysql_56?
class DateTimeTest < ActiveRecord::TestCase
self.use_transactional_fixtures = false
class Foo < ActiveRecord::Base; end
def test_default_datetime_precision
ActiveRecord::Base.connection.create_table(:foos, force: true)
ActiveRecord::Base.connection.add_column :foos, :created_at, :datetime
ActiveRecord::Base.connection.add_column :foos, :updated_at, :datetime
assert_nil activerecord_column_option('foos', 'created_at', 'precision')
end
def test_datetime_data_type_with_precision
ActiveRecord::Base.connection.create_table(:foos, force: true)
ActiveRecord::Base.connection.add_column :foos, :created_at, :datetime, precision: 1
ActiveRecord::Base.connection.add_column :foos, :updated_at, :datetime, precision: 5
assert_equal 1, activerecord_column_option('foos', 'created_at', 'precision')
assert_equal 5, activerecord_column_option('foos', 'updated_at', 'precision')
end
def test_timestamps_helper_with_custom_precision
ActiveRecord::Base.connection.create_table(:foos, force: true) do |t|
t.timestamps null: true, precision: 4
end
assert_equal 4, activerecord_column_option('foos', 'created_at', 'precision')
assert_equal 4, activerecord_column_option('foos', 'updated_at', 'precision')
end
def test_passing_precision_to_datetime_does_not_set_limit
ActiveRecord::Base.connection.create_table(:foos, force: true) do |t|
t.timestamps null: true, precision: 4
end
assert_nil activerecord_column_option('foos', 'created_at', 'limit')
assert_nil activerecord_column_option('foos', 'updated_at', 'limit')
end
def test_invalid_datetime_precision_raises_error
assert_raises ActiveRecord::ActiveRecordError do
ActiveRecord::Base.connection.create_table(:foos, force: true) do |t|
t.timestamps null: true, precision: 7
end
end
end
def test_mysql_agrees_with_activerecord_about_precision
ActiveRecord::Base.connection.create_table(:foos, force: true) do |t|
t.timestamps null: true, precision: 4
end
assert_equal 4, mysql_datetime_precision('foos', 'created_at')
assert_equal 4, mysql_datetime_precision('foos', 'updated_at')
end
def test_formatting_datetime_according_to_precision
ActiveRecord::Base.connection.create_table(:foos, force: true) do |t|
t.datetime :created_at, precision: 0
t.datetime :updated_at, precision: 4
end
date = ::Time.utc(2014, 8, 17, 12, 30, 0, 999999)
Foo.create!(created_at: date, updated_at: date)
assert foo = Foo.find_by(created_at: date)
assert_equal date.to_s, foo.created_at.to_s
assert_equal date.to_s, foo.updated_at.to_s
assert_equal 000000, foo.created_at.usec
assert_equal 999900, foo.updated_at.usec
end
private
def mysql_datetime_precision(table_name, column_name)
results = ActiveRecord::Base.connection.exec_query("SELECT column_name, datetime_precision FROM information_schema.columns WHERE table_name = '#{table_name}'")
result = results.find do |result_hash|
result_hash["column_name"] == column_name
end
result && result["datetime_precision"]
end
def activerecord_column_option(tablename, column_name, option)
result = ActiveRecord::Base.connection.columns(tablename).find do |column|
column.name == column_name
end
result && result.send(option)
end
end
end

View file

@ -232,6 +232,13 @@ class SchemaDumperTest < ActiveRecord::TestCase
end
end
if mysql_56?
def test_schema_dump_includes_datetime_precision
output = standard_dump
assert_match %r{t.datetime\s+"written_on",\s+precision: 6$}, output
end
end
def test_schema_dump_includes_decimal_options
output = dump_all_table_schema([/^[^n]/])
assert_match %r{precision: 3,[[:space:]]+scale: 2,[[:space:]]+default: 2.78}, output

View file

@ -726,7 +726,7 @@ ActiveRecord::Schema.define do
t.string :author_name
t.string :author_email_address
if mysql_56?
t.datetime :written_on, limit: 6
t.datetime :written_on, precision: 6
else
t.datetime :written_on
end