options_model 0.0.6 → 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +11 -10
- data/lib/active_model/type.rb +49 -0
- data/lib/active_model/type/big_integer.rb +15 -0
- data/lib/active_model/type/binary.rb +52 -0
- data/lib/active_model/type/boolean.rb +34 -0
- data/lib/active_model/type/date.rb +56 -0
- data/lib/active_model/type/date_time.rb +46 -0
- data/lib/active_model/type/decimal.rb +70 -0
- data/lib/active_model/type/float.rb +36 -0
- data/lib/active_model/type/helpers.rb +6 -0
- data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +37 -0
- data/lib/active_model/type/helpers/mutable.rb +20 -0
- data/lib/active_model/type/helpers/numeric.rb +37 -0
- data/lib/active_model/type/helpers/time_value.rb +79 -0
- data/lib/active_model/type/immutable_string.rb +32 -0
- data/lib/active_model/type/integer.rb +70 -0
- data/lib/active_model/type/registry.rb +70 -0
- data/lib/active_model/type/string.rb +26 -0
- data/lib/active_model/type/time.rb +48 -0
- data/lib/active_model/type/value.rb +118 -0
- data/lib/options_model.rb +4 -0
- data/lib/options_model/version.rb +1 -1
- metadata +42 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b48f69018888ef5765996819027a9bd0d68981b6
|
4
|
+
data.tar.gz: 0170a1dc7069a68cc48185a8544522543fbea127
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fb560074151d10c1bd1ec3d6d9025e263507c0609c041548d5e6c9be1797c414d678f981cc3678f0ed92866b8c4adf92ce80bc330909b5cd65c5ff4553ac23da
|
7
|
+
data.tar.gz: 8f72cdfa3d24c02116665f4910c8f3110e66570787938dd76be5c84f3dde6672cca6944b42098c642647285dfe6421196181a9e856b81d6a5091ca006d77f8b4
|
data/README.md
CHANGED
@@ -11,24 +11,25 @@ support attribute:
|
|
11
11
|
|
12
12
|
## Usage
|
13
13
|
|
14
|
+
```ruby
|
14
15
|
class Person < OptionsModel::Base
|
15
|
-
|
16
|
-
|
16
|
+
attribute :name, :string
|
17
|
+
attribute :age, :integer
|
17
18
|
|
18
|
-
|
19
|
+
validates :name, presence: true
|
19
20
|
end
|
20
21
|
|
21
22
|
class Book < OptionsModel::Base
|
22
|
-
|
23
|
+
embeds_one :author, class_name: 'Person'
|
23
24
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
attribute :bought_at, :datetime, default: -> { Time.new }
|
25
|
+
attribute :title, :string
|
26
|
+
attribute :tags, :string, array: true
|
27
|
+
attribute :price, :decimal, default: 0
|
28
|
+
attribute :bought_at, :datetime, default: -> { Time.new }
|
29
29
|
|
30
|
-
|
30
|
+
validates :title, presence: true
|
31
31
|
end
|
32
|
+
```
|
32
33
|
|
33
34
|
## Installation
|
34
35
|
Add this line to your application's Gemfile:
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_model/type/helpers"
|
4
|
+
require "active_model/type/value"
|
5
|
+
|
6
|
+
require "active_model/type/big_integer"
|
7
|
+
require "active_model/type/binary"
|
8
|
+
require "active_model/type/boolean"
|
9
|
+
require "active_model/type/date"
|
10
|
+
require "active_model/type/date_time"
|
11
|
+
require "active_model/type/decimal"
|
12
|
+
require "active_model/type/float"
|
13
|
+
require "active_model/type/immutable_string"
|
14
|
+
require "active_model/type/integer"
|
15
|
+
require "active_model/type/string"
|
16
|
+
require "active_model/type/time"
|
17
|
+
|
18
|
+
require "active_model/type/registry"
|
19
|
+
|
20
|
+
module ActiveModel
|
21
|
+
module Type
|
22
|
+
@registry = Registry.new
|
23
|
+
|
24
|
+
class << self
|
25
|
+
attr_accessor :registry # :nodoc:
|
26
|
+
|
27
|
+
# Add a new type to the registry, allowing it to be get through ActiveModel::Type#lookup
|
28
|
+
def register(type_name, klass = nil, **options, &block)
|
29
|
+
registry.register(type_name, klass, **options, &block)
|
30
|
+
end
|
31
|
+
|
32
|
+
def lookup(*args, **kwargs) # :nodoc:
|
33
|
+
registry.lookup(*args, **kwargs)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
register(:big_integer, Type::BigInteger)
|
38
|
+
register(:binary, Type::Binary)
|
39
|
+
register(:boolean, Type::Boolean)
|
40
|
+
register(:date, Type::Date)
|
41
|
+
register(:datetime, Type::DateTime)
|
42
|
+
register(:decimal, Type::Decimal)
|
43
|
+
register(:float, Type::Float)
|
44
|
+
register(:immutable_string, Type::ImmutableString)
|
45
|
+
register(:integer, Type::Integer)
|
46
|
+
register(:string, Type::String)
|
47
|
+
register(:time, Type::Time)
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
module Type
|
5
|
+
class Binary < Value # :nodoc:
|
6
|
+
def type
|
7
|
+
:binary
|
8
|
+
end
|
9
|
+
|
10
|
+
def binary?
|
11
|
+
true
|
12
|
+
end
|
13
|
+
|
14
|
+
def cast(value)
|
15
|
+
if value.is_a?(Data)
|
16
|
+
value.to_s
|
17
|
+
else
|
18
|
+
super
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def serialize(value)
|
23
|
+
return if value.nil?
|
24
|
+
Data.new(super)
|
25
|
+
end
|
26
|
+
|
27
|
+
def changed_in_place?(raw_old_value, value)
|
28
|
+
old_value = deserialize(raw_old_value)
|
29
|
+
old_value != value
|
30
|
+
end
|
31
|
+
|
32
|
+
class Data # :nodoc:
|
33
|
+
def initialize(value)
|
34
|
+
@value = value.to_s
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_s
|
38
|
+
@value
|
39
|
+
end
|
40
|
+
alias_method :to_str, :to_s
|
41
|
+
|
42
|
+
def hex
|
43
|
+
@value.unpack("H*")[0]
|
44
|
+
end
|
45
|
+
|
46
|
+
def ==(other)
|
47
|
+
other == to_s || super
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
module Type
|
5
|
+
# == Active \Model \Type \Boolean
|
6
|
+
#
|
7
|
+
# A class that behaves like a boolean type, including rules for coercion of user input.
|
8
|
+
#
|
9
|
+
# === Coercion
|
10
|
+
# Values set from user input will first be coerced into the appropriate ruby type.
|
11
|
+
# Coercion behavior is roughly mapped to Ruby's boolean semantics.
|
12
|
+
#
|
13
|
+
# - "false", "f" , "0", +0+ or any other value in +FALSE_VALUES+ will be coerced to +false+
|
14
|
+
# - Empty strings are coerced to +nil+
|
15
|
+
# - All other values will be coerced to +true+
|
16
|
+
class Boolean < Value
|
17
|
+
FALSE_VALUES = [false, 0, "0", "f", "F", "false", "FALSE", "off", "OFF"].to_set
|
18
|
+
|
19
|
+
def type # :nodoc:
|
20
|
+
:boolean
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def cast_value(value)
|
26
|
+
if value == ""
|
27
|
+
nil
|
28
|
+
else
|
29
|
+
!FALSE_VALUES.include?(value)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
module Type
|
5
|
+
class Date < Value # :nodoc:
|
6
|
+
include Helpers::AcceptsMultiparameterTime.new
|
7
|
+
|
8
|
+
def type
|
9
|
+
:date
|
10
|
+
end
|
11
|
+
|
12
|
+
def serialize(value)
|
13
|
+
cast(value)
|
14
|
+
end
|
15
|
+
|
16
|
+
def type_cast_for_schema(value)
|
17
|
+
value.to_s(:db).inspect
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def cast_value(value)
|
23
|
+
if value.is_a?(::String)
|
24
|
+
return if value.empty?
|
25
|
+
fast_string_to_date(value) || fallback_string_to_date(value)
|
26
|
+
elsif value.respond_to?(:to_date)
|
27
|
+
value.to_date
|
28
|
+
else
|
29
|
+
value
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
ISO_DATE = /\A(\d{4})-(\d\d)-(\d\d)\z/
|
34
|
+
def fast_string_to_date(string)
|
35
|
+
if string =~ ISO_DATE
|
36
|
+
new_date $1.to_i, $2.to_i, $3.to_i
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def fallback_string_to_date(string)
|
41
|
+
new_date(*::Date._parse(string, false).values_at(:year, :mon, :mday))
|
42
|
+
end
|
43
|
+
|
44
|
+
def new_date(year, mon, mday)
|
45
|
+
if year && year != 0
|
46
|
+
::Date.new(year, mon, mday) rescue nil
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def value_from_multiparameter_assignment(*)
|
51
|
+
time = super
|
52
|
+
time && time.to_date
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
module Type
|
5
|
+
class DateTime < Value # :nodoc:
|
6
|
+
include Helpers::TimeValue
|
7
|
+
include Helpers::AcceptsMultiparameterTime.new(
|
8
|
+
defaults: { 4 => 0, 5 => 0 }
|
9
|
+
)
|
10
|
+
|
11
|
+
def type
|
12
|
+
:datetime
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def cast_value(value)
|
18
|
+
return apply_seconds_precision(value) unless value.is_a?(::String)
|
19
|
+
return if value.empty?
|
20
|
+
|
21
|
+
fast_string_to_time(value) || fallback_string_to_time(value)
|
22
|
+
end
|
23
|
+
|
24
|
+
# '0.123456' -> 123456
|
25
|
+
# '1.123456' -> 123456
|
26
|
+
def microseconds(time)
|
27
|
+
time[:sec_fraction] ? (time[:sec_fraction] * 1_000_000).to_i : 0
|
28
|
+
end
|
29
|
+
|
30
|
+
def fallback_string_to_time(string)
|
31
|
+
time_hash = ::Date._parse(string)
|
32
|
+
time_hash[:sec_fraction] = microseconds(time_hash)
|
33
|
+
|
34
|
+
new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction, :offset))
|
35
|
+
end
|
36
|
+
|
37
|
+
def value_from_multiparameter_assignment(values_hash)
|
38
|
+
missing_parameter = (1..3).detect { |key| !values_hash.key?(key) }
|
39
|
+
if missing_parameter
|
40
|
+
raise ArgumentError, missing_parameter
|
41
|
+
end
|
42
|
+
super
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bigdecimal/util"
|
4
|
+
|
5
|
+
module ActiveModel
|
6
|
+
module Type
|
7
|
+
class Decimal < Value # :nodoc:
|
8
|
+
include Helpers::Numeric
|
9
|
+
BIGDECIMAL_PRECISION = 18
|
10
|
+
|
11
|
+
def type
|
12
|
+
:decimal
|
13
|
+
end
|
14
|
+
|
15
|
+
def type_cast_for_schema(value)
|
16
|
+
value.to_s.inspect
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def cast_value(value)
|
22
|
+
casted_value = \
|
23
|
+
case value
|
24
|
+
when ::Float
|
25
|
+
convert_float_to_big_decimal(value)
|
26
|
+
when ::Numeric
|
27
|
+
BigDecimal(value, precision || BIGDECIMAL_PRECISION)
|
28
|
+
when ::String
|
29
|
+
begin
|
30
|
+
value.to_d
|
31
|
+
rescue ArgumentError
|
32
|
+
BigDecimal(0)
|
33
|
+
end
|
34
|
+
else
|
35
|
+
if value.respond_to?(:to_d)
|
36
|
+
value.to_d
|
37
|
+
else
|
38
|
+
cast_value(value.to_s)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
apply_scale(casted_value)
|
43
|
+
end
|
44
|
+
|
45
|
+
def convert_float_to_big_decimal(value)
|
46
|
+
if precision
|
47
|
+
BigDecimal(apply_scale(value), float_precision)
|
48
|
+
else
|
49
|
+
value.to_d
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def float_precision
|
54
|
+
if precision.to_i > ::Float::DIG + 1
|
55
|
+
::Float::DIG + 1
|
56
|
+
else
|
57
|
+
precision.to_i
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def apply_scale(value)
|
62
|
+
if scale
|
63
|
+
value.round(scale)
|
64
|
+
else
|
65
|
+
value
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
module Type
|
5
|
+
class Float < Value # :nodoc:
|
6
|
+
include Helpers::Numeric
|
7
|
+
|
8
|
+
def type
|
9
|
+
:float
|
10
|
+
end
|
11
|
+
|
12
|
+
def type_cast_for_schema(value)
|
13
|
+
return "::Float::NAN" if value.try(:nan?)
|
14
|
+
case value
|
15
|
+
when ::Float::INFINITY then "::Float::INFINITY"
|
16
|
+
when -::Float::INFINITY then "-::Float::INFINITY"
|
17
|
+
else super
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
alias serialize cast
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def cast_value(value)
|
26
|
+
case value
|
27
|
+
when ::Float then value
|
28
|
+
when "Infinity" then ::Float::INFINITY
|
29
|
+
when "-Infinity" then -::Float::INFINITY
|
30
|
+
when "NaN" then ::Float::NAN
|
31
|
+
else value.to_f
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
module Type
|
5
|
+
module Helpers # :nodoc: all
|
6
|
+
class AcceptsMultiparameterTime < Module
|
7
|
+
def initialize(defaults: {})
|
8
|
+
define_method(:cast) do |value|
|
9
|
+
if value.is_a?(Hash)
|
10
|
+
value_from_multiparameter_assignment(value)
|
11
|
+
else
|
12
|
+
super(value)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
define_method(:assert_valid_value) do |value|
|
17
|
+
if value.is_a?(Hash)
|
18
|
+
value_from_multiparameter_assignment(value)
|
19
|
+
else
|
20
|
+
super(value)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
define_method(:value_from_multiparameter_assignment) do |values_hash|
|
25
|
+
defaults.each do |k, v|
|
26
|
+
values_hash[k] ||= v
|
27
|
+
end
|
28
|
+
return unless values_hash[1] && values_hash[2] && values_hash[3]
|
29
|
+
values = values_hash.sort.map(&:last)
|
30
|
+
::Time.send(default_timezone, *values)
|
31
|
+
end
|
32
|
+
private :value_from_multiparameter_assignment
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
module Type
|
5
|
+
module Helpers # :nodoc: all
|
6
|
+
module Mutable
|
7
|
+
def cast(value)
|
8
|
+
deserialize(serialize(value))
|
9
|
+
end
|
10
|
+
|
11
|
+
# +raw_old_value+ will be the `_before_type_cast` version of the
|
12
|
+
# value (likely a string). +new_value+ will be the current, type
|
13
|
+
# cast value.
|
14
|
+
def changed_in_place?(raw_old_value, new_value)
|
15
|
+
raw_old_value != serialize(new_value)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
module Type
|
5
|
+
module Helpers # :nodoc: all
|
6
|
+
module Numeric
|
7
|
+
def cast(value)
|
8
|
+
value = \
|
9
|
+
case value
|
10
|
+
when true then 1
|
11
|
+
when false then 0
|
12
|
+
when ::String then value.presence
|
13
|
+
else value
|
14
|
+
end
|
15
|
+
super(value)
|
16
|
+
end
|
17
|
+
|
18
|
+
def changed?(old_value, _new_value, new_value_before_type_cast) # :nodoc:
|
19
|
+
super || number_to_non_number?(old_value, new_value_before_type_cast)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def number_to_non_number?(old_value, new_value_before_type_cast)
|
25
|
+
old_value != nil && non_numeric_string?(new_value_before_type_cast)
|
26
|
+
end
|
27
|
+
|
28
|
+
def non_numeric_string?(value)
|
29
|
+
# 'wibble'.to_i will give zero, we want to make sure
|
30
|
+
# that we aren't marking int zero to string zero as
|
31
|
+
# changed.
|
32
|
+
value.to_s !~ /\A-?\d+\.?\d*\z/
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/time/zones"
|
4
|
+
|
5
|
+
module ActiveModel
|
6
|
+
module Type
|
7
|
+
module Helpers # :nodoc: all
|
8
|
+
module TimeValue
|
9
|
+
def serialize(value)
|
10
|
+
value = apply_seconds_precision(value)
|
11
|
+
|
12
|
+
if value.acts_like?(:time)
|
13
|
+
zone_conversion_method = is_utc? ? :getutc : :getlocal
|
14
|
+
|
15
|
+
if value.respond_to?(zone_conversion_method)
|
16
|
+
value = value.send(zone_conversion_method)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
value
|
21
|
+
end
|
22
|
+
|
23
|
+
def is_utc?
|
24
|
+
::Time.zone_default.nil? || ::Time.zone_default =~ "UTC"
|
25
|
+
end
|
26
|
+
|
27
|
+
def default_timezone
|
28
|
+
if is_utc?
|
29
|
+
:utc
|
30
|
+
else
|
31
|
+
:local
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def apply_seconds_precision(value)
|
36
|
+
return value unless precision && value.respond_to?(:usec)
|
37
|
+
number_of_insignificant_digits = 6 - precision
|
38
|
+
round_power = 10**number_of_insignificant_digits
|
39
|
+
value.change(usec: value.usec - value.usec % round_power)
|
40
|
+
end
|
41
|
+
|
42
|
+
def type_cast_for_schema(value)
|
43
|
+
value.to_s(:db).inspect
|
44
|
+
end
|
45
|
+
|
46
|
+
def user_input_in_time_zone(value)
|
47
|
+
value.in_time_zone
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def new_time(year, mon, mday, hour, min, sec, microsec, offset = nil)
|
53
|
+
# Treat 0000-00-00 00:00:00 as nil.
|
54
|
+
return if year.nil? || (year == 0 && mon == 0 && mday == 0)
|
55
|
+
|
56
|
+
if offset
|
57
|
+
time = ::Time.utc(year, mon, mday, hour, min, sec, microsec) rescue nil
|
58
|
+
return unless time
|
59
|
+
|
60
|
+
time -= offset
|
61
|
+
is_utc? ? time : time.getlocal
|
62
|
+
else
|
63
|
+
::Time.public_send(default_timezone, year, mon, mday, hour, min, sec, microsec) rescue nil
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
ISO_DATETIME = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(\.\d+)?\z/
|
68
|
+
|
69
|
+
# Doesn't handle time zones.
|
70
|
+
def fast_string_to_time(string)
|
71
|
+
if string =~ ISO_DATETIME
|
72
|
+
microsec = ($7.to_r * 1_000_000).to_i
|
73
|
+
new_time $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, microsec
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
module Type
|
5
|
+
class ImmutableString < Value # :nodoc:
|
6
|
+
def type
|
7
|
+
:string
|
8
|
+
end
|
9
|
+
|
10
|
+
def serialize(value)
|
11
|
+
case value
|
12
|
+
when ::Numeric, ActiveSupport::Duration then value.to_s
|
13
|
+
when true then "t"
|
14
|
+
when false then "f"
|
15
|
+
else super
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def cast_value(value)
|
22
|
+
result = \
|
23
|
+
case value
|
24
|
+
when true then "t"
|
25
|
+
when false then "f"
|
26
|
+
else value.to_s
|
27
|
+
end
|
28
|
+
result.freeze
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
module Type
|
5
|
+
class Integer < Value # :nodoc:
|
6
|
+
include Helpers::Numeric
|
7
|
+
|
8
|
+
# Column storage size in bytes.
|
9
|
+
# 4 bytes means a MySQL int or Postgres integer as opposed to smallint etc.
|
10
|
+
DEFAULT_LIMIT = 4
|
11
|
+
|
12
|
+
def initialize(*)
|
13
|
+
super
|
14
|
+
@range = min_value...max_value
|
15
|
+
end
|
16
|
+
|
17
|
+
def type
|
18
|
+
:integer
|
19
|
+
end
|
20
|
+
|
21
|
+
def deserialize(value)
|
22
|
+
return if value.nil?
|
23
|
+
value.to_i
|
24
|
+
end
|
25
|
+
|
26
|
+
def serialize(value)
|
27
|
+
result = cast(value)
|
28
|
+
if result
|
29
|
+
ensure_in_range(result)
|
30
|
+
end
|
31
|
+
result
|
32
|
+
end
|
33
|
+
|
34
|
+
# TODO Change this to private once we've dropped Ruby 2.2 support.
|
35
|
+
# Workaround for Ruby 2.2 "private attribute?" warning.
|
36
|
+
protected
|
37
|
+
|
38
|
+
attr_reader :range
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def cast_value(value)
|
43
|
+
case value
|
44
|
+
when true then 1
|
45
|
+
when false then 0
|
46
|
+
else
|
47
|
+
value.to_i rescue nil
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def ensure_in_range(value)
|
52
|
+
unless range.cover?(value)
|
53
|
+
raise ActiveModel::RangeError, "#{value} is out of range for #{self.class} with limit #{_limit} bytes"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def max_value
|
58
|
+
1 << (_limit * 8 - 1) # 8 bits per byte with one bit for sign
|
59
|
+
end
|
60
|
+
|
61
|
+
def min_value
|
62
|
+
-max_value
|
63
|
+
end
|
64
|
+
|
65
|
+
def _limit
|
66
|
+
limit || DEFAULT_LIMIT
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
# :stopdoc:
|
5
|
+
module Type
|
6
|
+
class Registry
|
7
|
+
def initialize
|
8
|
+
@registrations = []
|
9
|
+
end
|
10
|
+
|
11
|
+
def register(type_name, klass = nil, **options, &block)
|
12
|
+
block ||= proc { |_, *args| klass.new(*args) }
|
13
|
+
registrations << registration_klass.new(type_name, block, **options)
|
14
|
+
end
|
15
|
+
|
16
|
+
def lookup(symbol, *args)
|
17
|
+
registration = find_registration(symbol, *args)
|
18
|
+
|
19
|
+
if registration
|
20
|
+
registration.call(self, symbol, *args)
|
21
|
+
else
|
22
|
+
raise ArgumentError, "Unknown type #{symbol.inspect}"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# TODO Change this to private once we've dropped Ruby 2.2 support.
|
27
|
+
# Workaround for Ruby 2.2 "private attribute?" warning.
|
28
|
+
protected
|
29
|
+
|
30
|
+
attr_reader :registrations
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def registration_klass
|
35
|
+
Registration
|
36
|
+
end
|
37
|
+
|
38
|
+
def find_registration(symbol, *args)
|
39
|
+
registrations.find { |r| r.matches?(symbol, *args) }
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class Registration
|
44
|
+
# Options must be taken because of https://bugs.ruby-lang.org/issues/10856
|
45
|
+
def initialize(name, block, **)
|
46
|
+
@name = name
|
47
|
+
@block = block
|
48
|
+
end
|
49
|
+
|
50
|
+
def call(_registry, *args, **kwargs)
|
51
|
+
if kwargs.any? # https://bugs.ruby-lang.org/issues/10856
|
52
|
+
block.call(*args, **kwargs)
|
53
|
+
else
|
54
|
+
block.call(*args)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def matches?(type_name, *args, **kwargs)
|
59
|
+
type_name == name
|
60
|
+
end
|
61
|
+
|
62
|
+
# TODO Change this to private once we've dropped Ruby 2.2 support.
|
63
|
+
# Workaround for Ruby 2.2 "private attribute?" warning.
|
64
|
+
protected
|
65
|
+
|
66
|
+
attr_reader :name, :block
|
67
|
+
end
|
68
|
+
end
|
69
|
+
# :startdoc:
|
70
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_model/type/immutable_string"
|
4
|
+
|
5
|
+
module ActiveModel
|
6
|
+
module Type
|
7
|
+
class String < ImmutableString # :nodoc:
|
8
|
+
def changed_in_place?(raw_old_value, new_value)
|
9
|
+
if new_value.is_a?(::String)
|
10
|
+
raw_old_value != new_value
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def cast_value(value)
|
17
|
+
case value
|
18
|
+
when ::String then ::String.new(value)
|
19
|
+
when true then "t".freeze
|
20
|
+
when false then "f".freeze
|
21
|
+
else value.to_s
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
module Type
|
5
|
+
class Time < Value # :nodoc:
|
6
|
+
include Helpers::TimeValue
|
7
|
+
include Helpers::AcceptsMultiparameterTime.new(
|
8
|
+
defaults: { 1 => 1970, 2 => 1, 3 => 1, 4 => 0, 5 => 0 }
|
9
|
+
)
|
10
|
+
|
11
|
+
def type
|
12
|
+
:time
|
13
|
+
end
|
14
|
+
|
15
|
+
def user_input_in_time_zone(value)
|
16
|
+
return unless value.present?
|
17
|
+
|
18
|
+
case value
|
19
|
+
when ::String
|
20
|
+
value = "2000-01-01 #{value}"
|
21
|
+
when ::Time
|
22
|
+
value = value.change(year: 2000, day: 1, month: 1)
|
23
|
+
end
|
24
|
+
|
25
|
+
super(value)
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def cast_value(value)
|
31
|
+
return value unless value.is_a?(::String)
|
32
|
+
return if value.empty?
|
33
|
+
|
34
|
+
if value.start_with?("2000-01-01")
|
35
|
+
dummy_time_value = value
|
36
|
+
else
|
37
|
+
dummy_time_value = "2000-01-01 #{value}"
|
38
|
+
end
|
39
|
+
|
40
|
+
fast_string_to_time(dummy_time_value) || begin
|
41
|
+
time_hash = ::Date._parse(dummy_time_value)
|
42
|
+
return if time_hash[:hour].nil?
|
43
|
+
new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction, :offset))
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
module Type
|
5
|
+
class Value
|
6
|
+
attr_reader :precision, :scale, :limit
|
7
|
+
|
8
|
+
def initialize(precision: nil, limit: nil, scale: nil)
|
9
|
+
@precision = precision
|
10
|
+
@scale = scale
|
11
|
+
@limit = limit
|
12
|
+
end
|
13
|
+
|
14
|
+
def type # :nodoc:
|
15
|
+
end
|
16
|
+
|
17
|
+
# Converts a value from database input to the appropriate ruby type. The
|
18
|
+
# return value of this method will be returned from
|
19
|
+
# ActiveRecord::AttributeMethods::Read#read_attribute. The default
|
20
|
+
# implementation just calls Value#cast.
|
21
|
+
#
|
22
|
+
# +value+ The raw input, as provided from the database.
|
23
|
+
def deserialize(value)
|
24
|
+
cast(value)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Type casts a value from user input (e.g. from a setter). This value may
|
28
|
+
# be a string from the form builder, or a ruby object passed to a setter.
|
29
|
+
# There is currently no way to differentiate between which source it came
|
30
|
+
# from.
|
31
|
+
#
|
32
|
+
# The return value of this method will be returned from
|
33
|
+
# ActiveRecord::AttributeMethods::Read#read_attribute. See also:
|
34
|
+
# Value#cast_value.
|
35
|
+
#
|
36
|
+
# +value+ The raw input, as provided to the attribute setter.
|
37
|
+
def cast(value)
|
38
|
+
cast_value(value) unless value.nil?
|
39
|
+
end
|
40
|
+
|
41
|
+
# Casts a value from the ruby type to a type that the database knows how
|
42
|
+
# to understand. The returned value from this method should be a
|
43
|
+
# +String+, +Numeric+, +Date+, +Time+, +Symbol+, +true+, +false+, or
|
44
|
+
# +nil+.
|
45
|
+
def serialize(value)
|
46
|
+
value
|
47
|
+
end
|
48
|
+
|
49
|
+
# Type casts a value for schema dumping. This method is private, as we are
|
50
|
+
# hoping to remove it entirely.
|
51
|
+
def type_cast_for_schema(value) # :nodoc:
|
52
|
+
value.inspect
|
53
|
+
end
|
54
|
+
|
55
|
+
# These predicates are not documented, as I need to look further into
|
56
|
+
# their use, and see if they can be removed entirely.
|
57
|
+
def binary? # :nodoc:
|
58
|
+
false
|
59
|
+
end
|
60
|
+
|
61
|
+
# Determines whether a value has changed for dirty checking. +old_value+
|
62
|
+
# and +new_value+ will always be type-cast. Types should not need to
|
63
|
+
# override this method.
|
64
|
+
def changed?(old_value, new_value, _new_value_before_type_cast)
|
65
|
+
old_value != new_value
|
66
|
+
end
|
67
|
+
|
68
|
+
# Determines whether the mutable value has been modified since it was
|
69
|
+
# read. Returns +false+ by default. If your type returns an object
|
70
|
+
# which could be mutated, you should override this method. You will need
|
71
|
+
# to either:
|
72
|
+
#
|
73
|
+
# - pass +new_value+ to Value#serialize and compare it to
|
74
|
+
# +raw_old_value+
|
75
|
+
#
|
76
|
+
# or
|
77
|
+
#
|
78
|
+
# - pass +raw_old_value+ to Value#deserialize and compare it to
|
79
|
+
# +new_value+
|
80
|
+
#
|
81
|
+
# +raw_old_value+ The original value, before being passed to
|
82
|
+
# +deserialize+.
|
83
|
+
#
|
84
|
+
# +new_value+ The current value, after type casting.
|
85
|
+
def changed_in_place?(raw_old_value, new_value)
|
86
|
+
false
|
87
|
+
end
|
88
|
+
|
89
|
+
def map(value) # :nodoc:
|
90
|
+
yield value
|
91
|
+
end
|
92
|
+
|
93
|
+
def ==(other)
|
94
|
+
self.class == other.class &&
|
95
|
+
precision == other.precision &&
|
96
|
+
scale == other.scale &&
|
97
|
+
limit == other.limit
|
98
|
+
end
|
99
|
+
alias eql? ==
|
100
|
+
|
101
|
+
def hash
|
102
|
+
[self.class, precision, scale, limit].hash
|
103
|
+
end
|
104
|
+
|
105
|
+
def assert_valid_value(*)
|
106
|
+
end
|
107
|
+
|
108
|
+
private
|
109
|
+
|
110
|
+
# Convenience method for types which do not need separate type casting
|
111
|
+
# behavior for user and database inputs. Called by Value#cast for
|
112
|
+
# values except +nil+.
|
113
|
+
def cast_value(value) # :doc:
|
114
|
+
value
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
data/lib/options_model.rb
CHANGED
metadata
CHANGED
@@ -1,43 +1,55 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: options_model
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- jasl
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-12-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activemodel
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '4.2'
|
20
|
+
- - "<"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '6.0'
|
20
23
|
type: :runtime
|
21
24
|
prerelease: false
|
22
25
|
version_requirements: !ruby/object:Gem::Requirement
|
23
26
|
requirements:
|
24
|
-
- - "
|
27
|
+
- - ">="
|
25
28
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
29
|
+
version: '4.2'
|
30
|
+
- - "<"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '6.0'
|
27
33
|
- !ruby/object:Gem::Dependency
|
28
34
|
name: activesupport
|
29
35
|
requirement: !ruby/object:Gem::Requirement
|
30
36
|
requirements:
|
31
|
-
- - "
|
37
|
+
- - ">="
|
32
38
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
39
|
+
version: '4.2'
|
40
|
+
- - "<"
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '6.0'
|
34
43
|
type: :runtime
|
35
44
|
prerelease: false
|
36
45
|
version_requirements: !ruby/object:Gem::Requirement
|
37
46
|
requirements:
|
38
|
-
- - "
|
47
|
+
- - ">="
|
39
48
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
49
|
+
version: '4.2'
|
50
|
+
- - "<"
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: '6.0'
|
41
53
|
- !ruby/object:Gem::Dependency
|
42
54
|
name: rails
|
43
55
|
requirement: !ruby/object:Gem::Requirement
|
@@ -63,6 +75,25 @@ files:
|
|
63
75
|
- MIT-LICENSE
|
64
76
|
- README.md
|
65
77
|
- Rakefile
|
78
|
+
- lib/active_model/type.rb
|
79
|
+
- lib/active_model/type/big_integer.rb
|
80
|
+
- lib/active_model/type/binary.rb
|
81
|
+
- lib/active_model/type/boolean.rb
|
82
|
+
- lib/active_model/type/date.rb
|
83
|
+
- lib/active_model/type/date_time.rb
|
84
|
+
- lib/active_model/type/decimal.rb
|
85
|
+
- lib/active_model/type/float.rb
|
86
|
+
- lib/active_model/type/helpers.rb
|
87
|
+
- lib/active_model/type/helpers/accepts_multiparameter_time.rb
|
88
|
+
- lib/active_model/type/helpers/mutable.rb
|
89
|
+
- lib/active_model/type/helpers/numeric.rb
|
90
|
+
- lib/active_model/type/helpers/time_value.rb
|
91
|
+
- lib/active_model/type/immutable_string.rb
|
92
|
+
- lib/active_model/type/integer.rb
|
93
|
+
- lib/active_model/type/registry.rb
|
94
|
+
- lib/active_model/type/string.rb
|
95
|
+
- lib/active_model/type/time.rb
|
96
|
+
- lib/active_model/type/value.rb
|
66
97
|
- lib/options_model.rb
|
67
98
|
- lib/options_model/base.rb
|
68
99
|
- lib/options_model/concerns/attribute_assignment.rb
|
@@ -91,7 +122,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
91
122
|
version: '0'
|
92
123
|
requirements: []
|
93
124
|
rubyforge_project:
|
94
|
-
rubygems_version: 2.6.
|
125
|
+
rubygems_version: 2.6.13
|
95
126
|
signing_key:
|
96
127
|
specification_version: 4
|
97
128
|
summary: Make easier to handle model which will be serialized in a real model.
|