mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Move types to the top level ActiveRecord
namespace
`ActiveRecord::ConnectionAdapters::Type::Value` => `ActiveRecord::Type::Value`
This commit is contained in:
parent
7f73b9152c
commit
728fa69839
41 changed files with 592 additions and 643 deletions
|
@ -1,9 +1,9 @@
|
|||
require 'date'
|
||||
require 'bigdecimal'
|
||||
require 'bigdecimal/util'
|
||||
require 'active_record/type'
|
||||
require 'active_support/core_ext/benchmark'
|
||||
require 'active_record/connection_adapters/schema_cache'
|
||||
require 'active_record/connection_adapters/type'
|
||||
require 'active_record/connection_adapters/abstract/schema_dumper'
|
||||
require 'active_record/connection_adapters/abstract/schema_creation'
|
||||
require 'monitor'
|
||||
|
|
|
@ -298,7 +298,7 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
class << self
|
||||
TYPES = ConnectionAdapters::Type::HashLookupTypeMap.new # :nodoc:
|
||||
TYPES = Type::HashLookupTypeMap.new # :nodoc:
|
||||
|
||||
delegate :register_type, :alias_type, to: :TYPES
|
||||
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
require 'active_record/connection_adapters/type/numeric'
|
||||
require 'active_record/connection_adapters/type/time_value'
|
||||
require 'active_record/connection_adapters/type/value'
|
||||
|
||||
require 'active_record/connection_adapters/type/binary'
|
||||
require 'active_record/connection_adapters/type/boolean'
|
||||
require 'active_record/connection_adapters/type/date'
|
||||
require 'active_record/connection_adapters/type/date_time'
|
||||
require 'active_record/connection_adapters/type/decimal'
|
||||
require 'active_record/connection_adapters/type/decimal_without_scale'
|
||||
require 'active_record/connection_adapters/type/float'
|
||||
require 'active_record/connection_adapters/type/integer'
|
||||
require 'active_record/connection_adapters/type/string'
|
||||
require 'active_record/connection_adapters/type/text'
|
||||
require 'active_record/connection_adapters/type/time'
|
||||
|
||||
require 'active_record/connection_adapters/type/type_map'
|
||||
require 'active_record/connection_adapters/type/hash_lookup_type_map'
|
||||
|
||||
module ActiveRecord
|
||||
module ConnectionAdapters
|
||||
module Type # :nodoc:
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,19 +0,0 @@
|
|||
module ActiveRecord
|
||||
module ConnectionAdapters
|
||||
module Type
|
||||
class Binary < Value # :nodoc:
|
||||
def type
|
||||
:binary
|
||||
end
|
||||
|
||||
def binary?
|
||||
true
|
||||
end
|
||||
|
||||
def klass
|
||||
::String
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,21 +0,0 @@
|
|||
module ActiveRecord
|
||||
module ConnectionAdapters
|
||||
module Type
|
||||
class Boolean < Value # :nodoc:
|
||||
def type
|
||||
:boolean
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def cast_value(value)
|
||||
if value == ''
|
||||
nil
|
||||
else
|
||||
Column::TRUE_VALUES.include?(value)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,44 +0,0 @@
|
|||
module ActiveRecord
|
||||
module ConnectionAdapters
|
||||
module Type
|
||||
class Date < Value # :nodoc:
|
||||
def type
|
||||
:date
|
||||
end
|
||||
|
||||
def klass
|
||||
::Date
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def cast_value(value)
|
||||
if value.is_a?(::String)
|
||||
return if value.empty?
|
||||
fast_string_to_date(value) || fallback_string_to_date(value)
|
||||
elsif value.respond_to?(:to_date)
|
||||
value.to_date
|
||||
else
|
||||
value
|
||||
end
|
||||
end
|
||||
|
||||
def fast_string_to_date(string)
|
||||
if string =~ Column::Format::ISO_DATE
|
||||
new_date $1.to_i, $2.to_i, $3.to_i
|
||||
end
|
||||
end
|
||||
|
||||
def fallback_string_to_date(string)
|
||||
new_date(*::Date._parse(string, false).values_at(:year, :mon, :mday))
|
||||
end
|
||||
|
||||
def new_date(year, mon, mday)
|
||||
if year && year != 0
|
||||
::Date.new(year, mon, mday) rescue nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,35 +0,0 @@
|
|||
module ActiveRecord
|
||||
module ConnectionAdapters
|
||||
module Type
|
||||
class DateTime < Value # :nodoc:
|
||||
include TimeValue
|
||||
|
||||
def type
|
||||
:datetime
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def cast_value(string)
|
||||
return string unless string.is_a?(::String)
|
||||
return if string.empty?
|
||||
|
||||
fast_string_to_time(string) || fallback_string_to_time(string)
|
||||
end
|
||||
|
||||
# '0.123456' -> 123456
|
||||
# '1.123456' -> 123456
|
||||
def microseconds(time)
|
||||
time[:sec_fraction] ? (time[:sec_fraction] * 1_000_000).to_i : 0
|
||||
end
|
||||
|
||||
def fallback_string_to_time(string)
|
||||
time_hash = ::Date._parse(string)
|
||||
time_hash[:sec_fraction] = microseconds(time_hash)
|
||||
|
||||
new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction, :offset))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,27 +0,0 @@
|
|||
module ActiveRecord
|
||||
module ConnectionAdapters
|
||||
module Type
|
||||
class Decimal < Value # :nodoc:
|
||||
include Numeric
|
||||
|
||||
def type
|
||||
:decimal
|
||||
end
|
||||
|
||||
def klass
|
||||
::BigDecimal
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def cast_value(value)
|
||||
if value.respond_to?(:to_d)
|
||||
value.to_d
|
||||
else
|
||||
value.to_s.to_d
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,13 +0,0 @@
|
|||
require 'active_record/connection_adapters/type/integer'
|
||||
|
||||
module ActiveRecord
|
||||
module ConnectionAdapters
|
||||
module Type
|
||||
class DecimalWithoutScale < Integer # :nodoc:
|
||||
def type
|
||||
:decimal
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,25 +0,0 @@
|
|||
module ActiveRecord
|
||||
module ConnectionAdapters
|
||||
module Type
|
||||
class Float < Value # :nodoc:
|
||||
include Numeric
|
||||
|
||||
def type
|
||||
:float
|
||||
end
|
||||
|
||||
def klass
|
||||
::Float
|
||||
end
|
||||
|
||||
alias type_cast_for_database type_cast
|
||||
|
||||
private
|
||||
|
||||
def cast_value(value)
|
||||
value.to_f
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,21 +0,0 @@
|
|||
module ActiveRecord
|
||||
module ConnectionAdapters
|
||||
module Type
|
||||
class HashLookupTypeMap < TypeMap # :nodoc:
|
||||
delegate :key?, to: :@mapping
|
||||
|
||||
def lookup(type, *args)
|
||||
@mapping.fetch(type, proc { default_value }).call(type, *args)
|
||||
end
|
||||
|
||||
def fetch(type, *args, &block)
|
||||
@mapping.fetch(type, block).call(type, *args)
|
||||
end
|
||||
|
||||
def alias_type(type, alias_type)
|
||||
register_type(type) { |_, *args| lookup(alias_type, *args) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,29 +0,0 @@
|
|||
module ActiveRecord
|
||||
module ConnectionAdapters
|
||||
module Type
|
||||
class Integer < Value # :nodoc:
|
||||
include Numeric
|
||||
|
||||
def type
|
||||
:integer
|
||||
end
|
||||
|
||||
def klass
|
||||
::Fixnum
|
||||
end
|
||||
|
||||
alias type_cast_for_database type_cast
|
||||
|
||||
private
|
||||
|
||||
def cast_value(value)
|
||||
case value
|
||||
when true then 1
|
||||
when false then 0
|
||||
else value.to_i rescue nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,20 +0,0 @@
|
|||
module ActiveRecord
|
||||
module ConnectionAdapters
|
||||
module Type
|
||||
module Numeric # :nodoc:
|
||||
def number?
|
||||
true
|
||||
end
|
||||
|
||||
def type_cast_for_write(value)
|
||||
case value
|
||||
when true then 1
|
||||
when false then 0
|
||||
when ::String then value.presence
|
||||
else super
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,29 +0,0 @@
|
|||
module ActiveRecord
|
||||
module ConnectionAdapters
|
||||
module Type
|
||||
class String < Value # :nodoc:
|
||||
def type
|
||||
:string
|
||||
end
|
||||
|
||||
def text?
|
||||
true
|
||||
end
|
||||
|
||||
def klass
|
||||
::String
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def cast_value(value)
|
||||
case value
|
||||
when true then "1"
|
||||
when false then "0"
|
||||
else value.to_s
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,13 +0,0 @@
|
|||
require 'active_record/connection_adapters/type/string'
|
||||
|
||||
module ActiveRecord
|
||||
module ConnectionAdapters
|
||||
module Type
|
||||
class Text < String # :nodoc:
|
||||
def type
|
||||
:text
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,28 +0,0 @@
|
|||
module ActiveRecord
|
||||
module ConnectionAdapters
|
||||
module Type
|
||||
class Time < Value
|
||||
include TimeValue
|
||||
|
||||
def type
|
||||
:time
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def cast_value(value)
|
||||
return value unless value.is_a?(::String)
|
||||
return if value.empty?
|
||||
|
||||
dummy_time_value = "2000-01-01 #{value}"
|
||||
|
||||
fast_string_to_time(dummy_time_value) || begin
|
||||
time_hash = ::Date._parse(dummy_time_value)
|
||||
return if time_hash[:hour].nil?
|
||||
new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,36 +0,0 @@
|
|||
module ActiveRecord
|
||||
module ConnectionAdapters
|
||||
module Type
|
||||
module TimeValue # :nodoc:
|
||||
def klass
|
||||
::Time
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def new_time(year, mon, mday, hour, min, sec, microsec, offset = nil)
|
||||
# Treat 0000-00-00 00:00:00 as nil.
|
||||
return if year.nil? || (year == 0 && mon == 0 && mday == 0)
|
||||
|
||||
if offset
|
||||
time = ::Time.utc(year, mon, mday, hour, min, sec, microsec) rescue nil
|
||||
return unless time
|
||||
|
||||
time -= offset
|
||||
Base.default_timezone == :utc ? time : time.getlocal
|
||||
else
|
||||
::Time.public_send(Base.default_timezone, year, mon, mday, hour, min, sec, microsec) rescue nil
|
||||
end
|
||||
end
|
||||
|
||||
# Doesn't handle time zones.
|
||||
def fast_string_to_time(string)
|
||||
if string =~ Column::Format::ISO_DATETIME
|
||||
microsec = ($7.to_r * 1_000_000).to_i
|
||||
new_time $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, microsec
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,50 +0,0 @@
|
|||
module ActiveRecord
|
||||
module ConnectionAdapters
|
||||
module Type
|
||||
class TypeMap # :nodoc:
|
||||
def initialize
|
||||
@mapping = {}
|
||||
end
|
||||
|
||||
def lookup(lookup_key, *args)
|
||||
matching_pair = @mapping.reverse_each.detect do |key, _|
|
||||
key === lookup_key
|
||||
end
|
||||
|
||||
if matching_pair
|
||||
matching_pair.last.call(lookup_key, *args)
|
||||
else
|
||||
default_value
|
||||
end
|
||||
end
|
||||
|
||||
def register_type(key, value = nil, &block)
|
||||
raise ::ArgumentError unless value || block
|
||||
|
||||
if block
|
||||
@mapping[key] = block
|
||||
else
|
||||
@mapping[key] = proc { value }
|
||||
end
|
||||
end
|
||||
|
||||
def alias_type(key, target_key)
|
||||
register_type(key) do |sql_type, *args|
|
||||
metadata = sql_type[/\(.*\)/, 0]
|
||||
lookup("#{target_key}#{metadata}", *args)
|
||||
end
|
||||
end
|
||||
|
||||
def clear
|
||||
@mapping.clear
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def default_value
|
||||
@default_value ||= Value.new
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,61 +0,0 @@
|
|||
module ActiveRecord
|
||||
module ConnectionAdapters
|
||||
module Type
|
||||
class Value # :nodoc:
|
||||
attr_reader :precision, :scale, :limit
|
||||
|
||||
# Valid options are +precision+, +scale+, and +limit+.
|
||||
# They are only used when dumping schema.
|
||||
def initialize(options = {})
|
||||
options.assert_valid_keys(:precision, :scale, :limit)
|
||||
@precision = options[:precision]
|
||||
@scale = options[:scale]
|
||||
@limit = options[:limit]
|
||||
end
|
||||
|
||||
# The simplified that this object represents. Subclasses
|
||||
# should override this method.
|
||||
def type; end
|
||||
|
||||
# Takes an input from the database, or from attribute setters,
|
||||
# and casts it to a type appropriate for this object. This method
|
||||
# should not be overriden by subclasses. Instead, override `cast_value`.
|
||||
def type_cast(value)
|
||||
cast_value(value) unless value.nil?
|
||||
end
|
||||
|
||||
def type_cast_for_write(value)
|
||||
value
|
||||
end
|
||||
|
||||
def type_cast_for_database(value)
|
||||
type_cast_for_write(value)
|
||||
end
|
||||
|
||||
def text?
|
||||
false
|
||||
end
|
||||
|
||||
def number?
|
||||
false
|
||||
end
|
||||
|
||||
def binary?
|
||||
false
|
||||
end
|
||||
|
||||
def klass
|
||||
::Object
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Responsible for casting values from external sources to the appropriate
|
||||
# type. Called by `type_cast` for all values except `nil`.
|
||||
def cast_value(value) # :api: public
|
||||
value
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -516,7 +516,7 @@ module ActiveRecord
|
|||
|
||||
# Determines if a hash contains a truthy _destroy key.
|
||||
def has_destroy_flag?(hash)
|
||||
ConnectionAdapters::Type::Boolean.new.type_cast(hash['_destroy'])
|
||||
Type::Boolean.new.type_cast(hash['_destroy'])
|
||||
end
|
||||
|
||||
# Determines if a new record should be rejected by checking
|
||||
|
|
|
@ -2,7 +2,7 @@ module ActiveRecord
|
|||
module Properties # :nodoc:
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
Type = ConnectionAdapters::Type
|
||||
Type = ActiveRecord::Type
|
||||
|
||||
module ClassMethods
|
||||
# Defines or overrides a property on this model. This allows customization of
|
||||
|
|
18
activerecord/lib/active_record/type.rb
Normal file
18
activerecord/lib/active_record/type.rb
Normal file
|
@ -0,0 +1,18 @@
|
|||
require 'active_record/type/numeric'
|
||||
require 'active_record/type/time_value'
|
||||
require 'active_record/type/value'
|
||||
|
||||
require 'active_record/type/binary'
|
||||
require 'active_record/type/boolean'
|
||||
require 'active_record/type/date'
|
||||
require 'active_record/type/date_time'
|
||||
require 'active_record/type/decimal'
|
||||
require 'active_record/type/decimal_without_scale'
|
||||
require 'active_record/type/float'
|
||||
require 'active_record/type/integer'
|
||||
require 'active_record/type/string'
|
||||
require 'active_record/type/text'
|
||||
require 'active_record/type/time'
|
||||
|
||||
require 'active_record/type/type_map'
|
||||
require 'active_record/type/hash_lookup_type_map'
|
17
activerecord/lib/active_record/type/binary.rb
Normal file
17
activerecord/lib/active_record/type/binary.rb
Normal file
|
@ -0,0 +1,17 @@
|
|||
module ActiveRecord
|
||||
module Type
|
||||
class Binary < Value # :nodoc:
|
||||
def type
|
||||
:binary
|
||||
end
|
||||
|
||||
def binary?
|
||||
true
|
||||
end
|
||||
|
||||
def klass
|
||||
::String
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
19
activerecord/lib/active_record/type/boolean.rb
Normal file
19
activerecord/lib/active_record/type/boolean.rb
Normal file
|
@ -0,0 +1,19 @@
|
|||
module ActiveRecord
|
||||
module Type
|
||||
class Boolean < Value # :nodoc:
|
||||
def type
|
||||
:boolean
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def cast_value(value)
|
||||
if value == ''
|
||||
nil
|
||||
else
|
||||
ConnectionAdapters::Column::TRUE_VALUES.include?(value)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
42
activerecord/lib/active_record/type/date.rb
Normal file
42
activerecord/lib/active_record/type/date.rb
Normal file
|
@ -0,0 +1,42 @@
|
|||
module ActiveRecord
|
||||
module Type
|
||||
class Date < Value # :nodoc:
|
||||
def type
|
||||
:date
|
||||
end
|
||||
|
||||
def klass
|
||||
::Date
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def cast_value(value)
|
||||
if value.is_a?(::String)
|
||||
return if value.empty?
|
||||
fast_string_to_date(value) || fallback_string_to_date(value)
|
||||
elsif value.respond_to?(:to_date)
|
||||
value.to_date
|
||||
else
|
||||
value
|
||||
end
|
||||
end
|
||||
|
||||
def fast_string_to_date(string)
|
||||
if string =~ ConnectionAdapters::Column::Format::ISO_DATE
|
||||
new_date $1.to_i, $2.to_i, $3.to_i
|
||||
end
|
||||
end
|
||||
|
||||
def fallback_string_to_date(string)
|
||||
new_date(*::Date._parse(string, false).values_at(:year, :mon, :mday))
|
||||
end
|
||||
|
||||
def new_date(year, mon, mday)
|
||||
if year && year != 0
|
||||
::Date.new(year, mon, mday) rescue nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
33
activerecord/lib/active_record/type/date_time.rb
Normal file
33
activerecord/lib/active_record/type/date_time.rb
Normal file
|
@ -0,0 +1,33 @@
|
|||
module ActiveRecord
|
||||
module Type
|
||||
class DateTime < Value # :nodoc:
|
||||
include TimeValue
|
||||
|
||||
def type
|
||||
:datetime
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def cast_value(string)
|
||||
return string unless string.is_a?(::String)
|
||||
return if string.empty?
|
||||
|
||||
fast_string_to_time(string) || fallback_string_to_time(string)
|
||||
end
|
||||
|
||||
# '0.123456' -> 123456
|
||||
# '1.123456' -> 123456
|
||||
def microseconds(time)
|
||||
time[:sec_fraction] ? (time[:sec_fraction] * 1_000_000).to_i : 0
|
||||
end
|
||||
|
||||
def fallback_string_to_time(string)
|
||||
time_hash = ::Date._parse(string)
|
||||
time_hash[:sec_fraction] = microseconds(time_hash)
|
||||
|
||||
new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction, :offset))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
25
activerecord/lib/active_record/type/decimal.rb
Normal file
25
activerecord/lib/active_record/type/decimal.rb
Normal file
|
@ -0,0 +1,25 @@
|
|||
module ActiveRecord
|
||||
module Type
|
||||
class Decimal < Value # :nodoc:
|
||||
include Numeric
|
||||
|
||||
def type
|
||||
:decimal
|
||||
end
|
||||
|
||||
def klass
|
||||
::BigDecimal
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def cast_value(value)
|
||||
if value.respond_to?(:to_d)
|
||||
value.to_d
|
||||
else
|
||||
value.to_s.to_d
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
11
activerecord/lib/active_record/type/decimal_without_scale.rb
Normal file
11
activerecord/lib/active_record/type/decimal_without_scale.rb
Normal file
|
@ -0,0 +1,11 @@
|
|||
require 'active_record/type/integer'
|
||||
|
||||
module ActiveRecord
|
||||
module Type
|
||||
class DecimalWithoutScale < Integer # :nodoc:
|
||||
def type
|
||||
:decimal
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
23
activerecord/lib/active_record/type/float.rb
Normal file
23
activerecord/lib/active_record/type/float.rb
Normal file
|
@ -0,0 +1,23 @@
|
|||
module ActiveRecord
|
||||
module Type
|
||||
class Float < Value # :nodoc:
|
||||
include Numeric
|
||||
|
||||
def type
|
||||
:float
|
||||
end
|
||||
|
||||
def klass
|
||||
::Float
|
||||
end
|
||||
|
||||
alias type_cast_for_database type_cast
|
||||
|
||||
private
|
||||
|
||||
def cast_value(value)
|
||||
value.to_f
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
19
activerecord/lib/active_record/type/hash_lookup_type_map.rb
Normal file
19
activerecord/lib/active_record/type/hash_lookup_type_map.rb
Normal file
|
@ -0,0 +1,19 @@
|
|||
module ActiveRecord
|
||||
module Type
|
||||
class HashLookupTypeMap < TypeMap # :nodoc:
|
||||
delegate :key?, to: :@mapping
|
||||
|
||||
def lookup(type, *args)
|
||||
@mapping.fetch(type, proc { default_value }).call(type, *args)
|
||||
end
|
||||
|
||||
def fetch(type, *args, &block)
|
||||
@mapping.fetch(type, block).call(type, *args)
|
||||
end
|
||||
|
||||
def alias_type(type, alias_type)
|
||||
register_type(type) { |_, *args| lookup(alias_type, *args) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
27
activerecord/lib/active_record/type/integer.rb
Normal file
27
activerecord/lib/active_record/type/integer.rb
Normal file
|
@ -0,0 +1,27 @@
|
|||
module ActiveRecord
|
||||
module Type
|
||||
class Integer < Value # :nodoc:
|
||||
include Numeric
|
||||
|
||||
def type
|
||||
:integer
|
||||
end
|
||||
|
||||
def klass
|
||||
::Fixnum
|
||||
end
|
||||
|
||||
alias type_cast_for_database type_cast
|
||||
|
||||
private
|
||||
|
||||
def cast_value(value)
|
||||
case value
|
||||
when true then 1
|
||||
when false then 0
|
||||
else value.to_i rescue nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
18
activerecord/lib/active_record/type/numeric.rb
Normal file
18
activerecord/lib/active_record/type/numeric.rb
Normal file
|
@ -0,0 +1,18 @@
|
|||
module ActiveRecord
|
||||
module Type
|
||||
module Numeric # :nodoc:
|
||||
def number?
|
||||
true
|
||||
end
|
||||
|
||||
def type_cast_for_write(value)
|
||||
case value
|
||||
when true then 1
|
||||
when false then 0
|
||||
when ::String then value.presence
|
||||
else super
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
27
activerecord/lib/active_record/type/string.rb
Normal file
27
activerecord/lib/active_record/type/string.rb
Normal file
|
@ -0,0 +1,27 @@
|
|||
module ActiveRecord
|
||||
module Type
|
||||
class String < Value # :nodoc:
|
||||
def type
|
||||
:string
|
||||
end
|
||||
|
||||
def text?
|
||||
true
|
||||
end
|
||||
|
||||
def klass
|
||||
::String
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def cast_value(value)
|
||||
case value
|
||||
when true then "1"
|
||||
when false then "0"
|
||||
else value.to_s
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
11
activerecord/lib/active_record/type/text.rb
Normal file
11
activerecord/lib/active_record/type/text.rb
Normal file
|
@ -0,0 +1,11 @@
|
|||
require 'active_record/type/string'
|
||||
|
||||
module ActiveRecord
|
||||
module Type
|
||||
class Text < String # :nodoc:
|
||||
def type
|
||||
:text
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
26
activerecord/lib/active_record/type/time.rb
Normal file
26
activerecord/lib/active_record/type/time.rb
Normal file
|
@ -0,0 +1,26 @@
|
|||
module ActiveRecord
|
||||
module Type
|
||||
class Time < Value # :nodoc:
|
||||
include TimeValue
|
||||
|
||||
def type
|
||||
:time
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def cast_value(value)
|
||||
return value unless value.is_a?(::String)
|
||||
return if value.empty?
|
||||
|
||||
dummy_time_value = "2000-01-01 #{value}"
|
||||
|
||||
fast_string_to_time(dummy_time_value) || begin
|
||||
time_hash = ::Date._parse(dummy_time_value)
|
||||
return if time_hash[:hour].nil?
|
||||
new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
34
activerecord/lib/active_record/type/time_value.rb
Normal file
34
activerecord/lib/active_record/type/time_value.rb
Normal file
|
@ -0,0 +1,34 @@
|
|||
module ActiveRecord
|
||||
module Type
|
||||
module TimeValue # :nodoc:
|
||||
def klass
|
||||
::Time
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def new_time(year, mon, mday, hour, min, sec, microsec, offset = nil)
|
||||
# Treat 0000-00-00 00:00:00 as nil.
|
||||
return if year.nil? || (year == 0 && mon == 0 && mday == 0)
|
||||
|
||||
if offset
|
||||
time = ::Time.utc(year, mon, mday, hour, min, sec, microsec) rescue nil
|
||||
return unless time
|
||||
|
||||
time -= offset
|
||||
Base.default_timezone == :utc ? time : time.getlocal
|
||||
else
|
||||
::Time.public_send(Base.default_timezone, year, mon, mday, hour, min, sec, microsec) rescue nil
|
||||
end
|
||||
end
|
||||
|
||||
# Doesn't handle time zones.
|
||||
def fast_string_to_time(string)
|
||||
if string =~ ConnectionAdapters::Column::Format::ISO_DATETIME
|
||||
microsec = ($7.to_r * 1_000_000).to_i
|
||||
new_time $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, microsec
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
48
activerecord/lib/active_record/type/type_map.rb
Normal file
48
activerecord/lib/active_record/type/type_map.rb
Normal file
|
@ -0,0 +1,48 @@
|
|||
module ActiveRecord
|
||||
module Type
|
||||
class TypeMap # :nodoc:
|
||||
def initialize
|
||||
@mapping = {}
|
||||
end
|
||||
|
||||
def lookup(lookup_key, *args)
|
||||
matching_pair = @mapping.reverse_each.detect do |key, _|
|
||||
key === lookup_key
|
||||
end
|
||||
|
||||
if matching_pair
|
||||
matching_pair.last.call(lookup_key, *args)
|
||||
else
|
||||
default_value
|
||||
end
|
||||
end
|
||||
|
||||
def register_type(key, value = nil, &block)
|
||||
raise ::ArgumentError unless value || block
|
||||
|
||||
if block
|
||||
@mapping[key] = block
|
||||
else
|
||||
@mapping[key] = proc { value }
|
||||
end
|
||||
end
|
||||
|
||||
def alias_type(key, target_key)
|
||||
register_type(key) do |sql_type, *args|
|
||||
metadata = sql_type[/\(.*\)/, 0]
|
||||
lookup("#{target_key}#{metadata}", *args)
|
||||
end
|
||||
end
|
||||
|
||||
def clear
|
||||
@mapping.clear
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def default_value
|
||||
@default_value ||= Value.new
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
59
activerecord/lib/active_record/type/value.rb
Normal file
59
activerecord/lib/active_record/type/value.rb
Normal file
|
@ -0,0 +1,59 @@
|
|||
module ActiveRecord
|
||||
module Type
|
||||
class Value # :nodoc:
|
||||
attr_reader :precision, :scale, :limit
|
||||
|
||||
# Valid options are +precision+, +scale+, and +limit+.
|
||||
# They are only used when dumping schema.
|
||||
def initialize(options = {})
|
||||
options.assert_valid_keys(:precision, :scale, :limit)
|
||||
@precision = options[:precision]
|
||||
@scale = options[:scale]
|
||||
@limit = options[:limit]
|
||||
end
|
||||
|
||||
# The simplified that this object represents. Subclasses
|
||||
# should override this method.
|
||||
def type; end
|
||||
|
||||
# Takes an input from the database, or from attribute setters,
|
||||
# and casts it to a type appropriate for this object. This method
|
||||
# should not be overriden by subclasses. Instead, override `cast_value`.
|
||||
def type_cast(value)
|
||||
cast_value(value) unless value.nil?
|
||||
end
|
||||
|
||||
def type_cast_for_write(value)
|
||||
value
|
||||
end
|
||||
|
||||
def type_cast_for_database(value)
|
||||
type_cast_for_write(value)
|
||||
end
|
||||
|
||||
def text?
|
||||
false
|
||||
end
|
||||
|
||||
def number?
|
||||
false
|
||||
end
|
||||
|
||||
def binary?
|
||||
false
|
||||
end
|
||||
|
||||
def klass
|
||||
::Object
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Responsible for casting values from external sources to the appropriate
|
||||
# type. Called by `type_cast` for all values except `nil`.
|
||||
def cast_value(value) # :api: public
|
||||
value
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -83,7 +83,7 @@ end
|
|||
class PostgresqlCompositeWithCustomOIDTest < ActiveRecord::TestCase
|
||||
include PostgresqlCompositeBehavior
|
||||
|
||||
class FullAddressType < ActiveRecord::ConnectionAdapters::Type::Value
|
||||
class FullAddressType < ActiveRecord::Type::Value
|
||||
def type; :full_address end
|
||||
|
||||
def type_cast(value)
|
||||
|
|
|
@ -1,142 +0,0 @@
|
|||
require "cases/helper"
|
||||
|
||||
module ActiveRecord
|
||||
module ConnectionAdapters
|
||||
module Type
|
||||
class TypeMapTest < ActiveRecord::TestCase
|
||||
def test_default_type
|
||||
mapping = TypeMap.new
|
||||
|
||||
assert_kind_of Value, mapping.lookup(:undefined)
|
||||
end
|
||||
|
||||
def test_registering_types
|
||||
boolean = Boolean.new
|
||||
mapping = TypeMap.new
|
||||
|
||||
mapping.register_type(/boolean/i, boolean)
|
||||
|
||||
assert_equal mapping.lookup('boolean'), boolean
|
||||
end
|
||||
|
||||
def test_overriding_registered_types
|
||||
time = Time.new
|
||||
timestamp = DateTime.new
|
||||
mapping = TypeMap.new
|
||||
|
||||
mapping.register_type(/time/i, time)
|
||||
mapping.register_type(/time/i, timestamp)
|
||||
|
||||
assert_equal mapping.lookup('time'), timestamp
|
||||
end
|
||||
|
||||
def test_fuzzy_lookup
|
||||
string = String.new
|
||||
mapping = TypeMap.new
|
||||
|
||||
mapping.register_type(/varchar/i, string)
|
||||
|
||||
assert_equal mapping.lookup('varchar(20)'), string
|
||||
end
|
||||
|
||||
def test_aliasing_types
|
||||
string = String.new
|
||||
mapping = TypeMap.new
|
||||
|
||||
mapping.register_type(/string/i, string)
|
||||
mapping.alias_type(/varchar/i, 'string')
|
||||
|
||||
assert_equal mapping.lookup('varchar'), string
|
||||
end
|
||||
|
||||
def test_changing_type_changes_aliases
|
||||
time = Time.new
|
||||
timestamp = DateTime.new
|
||||
mapping = TypeMap.new
|
||||
|
||||
mapping.register_type(/timestamp/i, time)
|
||||
mapping.alias_type(/datetime/i, 'timestamp')
|
||||
mapping.register_type(/timestamp/i, timestamp)
|
||||
|
||||
assert_equal mapping.lookup('datetime'), timestamp
|
||||
end
|
||||
|
||||
def test_aliases_keep_metadata
|
||||
mapping = TypeMap.new
|
||||
|
||||
mapping.register_type(/decimal/i) { |sql_type| sql_type }
|
||||
mapping.alias_type(/number/i, 'decimal')
|
||||
|
||||
assert_equal mapping.lookup('number(20)'), 'decimal(20)'
|
||||
assert_equal mapping.lookup('number'), 'decimal'
|
||||
end
|
||||
|
||||
def test_register_proc
|
||||
string = String.new
|
||||
binary = Binary.new
|
||||
mapping = TypeMap.new
|
||||
|
||||
mapping.register_type(/varchar/i) do |type|
|
||||
if type.include?('(')
|
||||
string
|
||||
else
|
||||
binary
|
||||
end
|
||||
end
|
||||
|
||||
assert_equal mapping.lookup('varchar(20)'), string
|
||||
assert_equal mapping.lookup('varchar'), binary
|
||||
end
|
||||
|
||||
def test_additional_lookup_args
|
||||
mapping = TypeMap.new
|
||||
|
||||
mapping.register_type(/varchar/i) do |type, limit|
|
||||
if limit > 255
|
||||
'text'
|
||||
else
|
||||
'string'
|
||||
end
|
||||
end
|
||||
mapping.alias_type(/string/i, 'varchar')
|
||||
|
||||
assert_equal mapping.lookup('varchar', 200), 'string'
|
||||
assert_equal mapping.lookup('varchar', 400), 'text'
|
||||
assert_equal mapping.lookup('string', 400), 'text'
|
||||
end
|
||||
|
||||
def test_requires_value_or_block
|
||||
mapping = TypeMap.new
|
||||
|
||||
assert_raises(ArgumentError) do
|
||||
mapping.register_type(/only key/i)
|
||||
end
|
||||
end
|
||||
|
||||
def test_lookup_non_strings
|
||||
mapping = HashLookupTypeMap.new
|
||||
|
||||
mapping.register_type(1, 'string')
|
||||
mapping.register_type(2, 'int')
|
||||
mapping.alias_type(3, 1)
|
||||
|
||||
assert_equal mapping.lookup(1), 'string'
|
||||
assert_equal mapping.lookup(2), 'int'
|
||||
assert_equal mapping.lookup(3), 'string'
|
||||
assert_kind_of Type::Value, mapping.lookup(4)
|
||||
end
|
||||
|
||||
def test_clear_mappings
|
||||
time = Time.new
|
||||
mapping = TypeMap.new
|
||||
|
||||
mapping.register_type(/time/i, time)
|
||||
mapping.clear
|
||||
|
||||
assert_not_equal mapping.lookup('time'), time
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
130
activerecord/test/cases/type/type_map_test.rb
Normal file
130
activerecord/test/cases/type/type_map_test.rb
Normal file
|
@ -0,0 +1,130 @@
|
|||
require "cases/helper"
|
||||
|
||||
module ActiveRecord
|
||||
module Type
|
||||
class TypeMapTest < ActiveRecord::TestCase
|
||||
def test_default_type
|
||||
mapping = TypeMap.new
|
||||
|
||||
assert_kind_of Value, mapping.lookup(:undefined)
|
||||
end
|
||||
|
||||
def test_registering_types
|
||||
boolean = Boolean.new
|
||||
mapping = TypeMap.new
|
||||
|
||||
mapping.register_type(/boolean/i, boolean)
|
||||
|
||||
assert_equal mapping.lookup('boolean'), boolean
|
||||
end
|
||||
|
||||
def test_overriding_registered_types
|
||||
time = Time.new
|
||||
timestamp = DateTime.new
|
||||
mapping = TypeMap.new
|
||||
|
||||
mapping.register_type(/time/i, time)
|
||||
mapping.register_type(/time/i, timestamp)
|
||||
|
||||
assert_equal mapping.lookup('time'), timestamp
|
||||
end
|
||||
|
||||
def test_fuzzy_lookup
|
||||
string = String.new
|
||||
mapping = TypeMap.new
|
||||
|
||||
mapping.register_type(/varchar/i, string)
|
||||
|
||||
assert_equal mapping.lookup('varchar(20)'), string
|
||||
end
|
||||
|
||||
def test_aliasing_types
|
||||
string = String.new
|
||||
mapping = TypeMap.new
|
||||
|
||||
mapping.register_type(/string/i, string)
|
||||
mapping.alias_type(/varchar/i, 'string')
|
||||
|
||||
assert_equal mapping.lookup('varchar'), string
|
||||
end
|
||||
|
||||
def test_changing_type_changes_aliases
|
||||
time = Time.new
|
||||
timestamp = DateTime.new
|
||||
mapping = TypeMap.new
|
||||
|
||||
mapping.register_type(/timestamp/i, time)
|
||||
mapping.alias_type(/datetime/i, 'timestamp')
|
||||
mapping.register_type(/timestamp/i, timestamp)
|
||||
|
||||
assert_equal mapping.lookup('datetime'), timestamp
|
||||
end
|
||||
|
||||
def test_aliases_keep_metadata
|
||||
mapping = TypeMap.new
|
||||
|
||||
mapping.register_type(/decimal/i) { |sql_type| sql_type }
|
||||
mapping.alias_type(/number/i, 'decimal')
|
||||
|
||||
assert_equal mapping.lookup('number(20)'), 'decimal(20)'
|
||||
assert_equal mapping.lookup('number'), 'decimal'
|
||||
end
|
||||
|
||||
def test_register_proc
|
||||
string = String.new
|
||||
binary = Binary.new
|
||||
mapping = TypeMap.new
|
||||
|
||||
mapping.register_type(/varchar/i) do |type|
|
||||
if type.include?('(')
|
||||
string
|
||||
else
|
||||
binary
|
||||
end
|
||||
end
|
||||
|
||||
assert_equal mapping.lookup('varchar(20)'), string
|
||||
assert_equal mapping.lookup('varchar'), binary
|
||||
end
|
||||
|
||||
def test_additional_lookup_args
|
||||
mapping = TypeMap.new
|
||||
|
||||
mapping.register_type(/varchar/i) do |type, limit|
|
||||
if limit > 255
|
||||
'text'
|
||||
else
|
||||
'string'
|
||||
end
|
||||
end
|
||||
mapping.alias_type(/string/i, 'varchar')
|
||||
|
||||
assert_equal mapping.lookup('varchar', 200), 'string'
|
||||
assert_equal mapping.lookup('varchar', 400), 'text'
|
||||
assert_equal mapping.lookup('string', 400), 'text'
|
||||
end
|
||||
|
||||
def test_requires_value_or_block
|
||||
mapping = TypeMap.new
|
||||
|
||||
assert_raises(ArgumentError) do
|
||||
mapping.register_type(/only key/i)
|
||||
end
|
||||
end
|
||||
|
||||
def test_lookup_non_strings
|
||||
mapping = HashLookupTypeMap.new
|
||||
|
||||
mapping.register_type(1, 'string')
|
||||
mapping.register_type(2, 'int')
|
||||
mapping.alias_type(3, 1)
|
||||
|
||||
assert_equal mapping.lookup(1), 'string'
|
||||
assert_equal mapping.lookup(2), 'int'
|
||||
assert_equal mapping.lookup(3), 'string'
|
||||
assert_kind_of Type::Value, mapping.lookup(4)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
Loading…
Reference in a new issue