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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 840526f66190795799451d6aa25f55b2938594b9
4
- data.tar.gz: 5230c178ab5d88acf6f907848c6140a704d57969
3
+ metadata.gz: b48f69018888ef5765996819027a9bd0d68981b6
4
+ data.tar.gz: 0170a1dc7069a68cc48185a8544522543fbea127
5
5
  SHA512:
6
- metadata.gz: fab4e8da52e002215ed4b61815d7d815b2d800a9a9ce49df817c6d0dac2265c85cf88bce719032b3d6f6d270b25b9c92945c8030aa7c53ef319d58a3c46d3287
7
- data.tar.gz: f3c392f13f9ee02bcd94fe00a79fb41bc5d4989690589cdc3739e0023e35f338a2b1f0fc6c28dc822a888d401e175a37994d0e289c5439ad0ff5480d463e06c4
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
- attribute :name, :string
16
- attribute :age, :integer
16
+ attribute :name, :string
17
+ attribute :age, :integer
17
18
 
18
- validates :name, presence: true
19
+ validates :name, presence: true
19
20
  end
20
21
 
21
22
  class Book < OptionsModel::Base
22
- embeds_one :author, class_name: 'Person'
23
+ embeds_one :author, class_name: 'Person'
23
24
 
24
- attribute :title, :string
25
- attribute :tags, :string, array: true
26
- attribute :price, :decimal, default: 0
27
- attribute :meta, :json, default: {}
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
- validates :title, presence: true
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,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_model/type/integer"
4
+
5
+ module ActiveModel
6
+ module Type
7
+ class BigInteger < Integer # :nodoc:
8
+ private
9
+
10
+ def max_value
11
+ ::Float::INFINITY
12
+ end
13
+ end
14
+ end
15
+ 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,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_model/type/helpers/accepts_multiparameter_time"
4
+ require "active_model/type/helpers/numeric"
5
+ require "active_model/type/helpers/mutable"
6
+ require "active_model/type/helpers/time_value"
@@ -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
@@ -10,5 +10,9 @@ require "options_model/concerns/serialization"
10
10
 
11
11
  require "options_model/base"
12
12
 
13
+ unless defined?(ActiveModel::Type)
14
+ require "active_model/type"
15
+ end
16
+
13
17
  module OptionsModel
14
18
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OptionsModel
4
- VERSION = "0.0.6"
4
+ VERSION = "0.0.7"
5
5
  end
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.6
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-10-04 00:00:00.000000000 Z
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: '5.0'
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: '5.0'
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: '5.0'
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: '5.0'
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.12
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.