options_model 0.0.6 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
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.