Skip to content

Commit

Permalink
loosen rails requirement
Browse files Browse the repository at this point in the history
  • Loading branch information
jasl committed Dec 5, 2017
1 parent 522782c commit 00b2992
Show file tree
Hide file tree
Showing 23 changed files with 921 additions and 15 deletions.
6 changes: 3 additions & 3 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ PATH
remote: .
specs:
options_model (0.0.6)
activemodel (~> 5.0)
activesupport (~> 5.0)
activemodel (>= 4.2, < 6.0)
activesupport (>= 4.2, < 6.0)

GEM
remote: https://rubygems.org/
Expand Down Expand Up @@ -117,4 +117,4 @@ DEPENDENCIES
rails (~> 5.0)

BUNDLED WITH
1.15.4
1.16.0
21 changes: 11 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,24 +11,25 @@ support attribute:

## Usage

```ruby
class Person < OptionsModel::Base
attribute :name, :string
attribute :age, :integer
attribute :name, :string
attribute :age, :integer

validates :name, presence: true
validates :name, presence: true
end

class Book < OptionsModel::Base
embeds_one :author, class_name: 'Person'
embeds_one :author, class_name: 'Person'

attribute :title, :string
attribute :tags, :string, array: true
attribute :price, :decimal, default: 0
attribute :meta, :json, default: {}
attribute :bought_at, :datetime, default: -> { Time.new }
attribute :title, :string
attribute :tags, :string, array: true
attribute :price, :decimal, default: 0
attribute :bought_at, :datetime, default: -> { Time.new }

validates :title, presence: true
validates :title, presence: true
end
```

## Installation
Add this line to your application's Gemfile:
Expand Down
49 changes: 49 additions & 0 deletions lib/active_model/type.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# frozen_string_literal: true

require "active_model/type/helpers"
require "active_model/type/value"

require "active_model/type/big_integer"
require "active_model/type/binary"
require "active_model/type/boolean"
require "active_model/type/date"
require "active_model/type/date_time"
require "active_model/type/decimal"
require "active_model/type/float"
require "active_model/type/immutable_string"
require "active_model/type/integer"
require "active_model/type/string"
require "active_model/type/time"

require "active_model/type/registry"

module ActiveModel
module Type
@registry = Registry.new

class << self
attr_accessor :registry # :nodoc:

# Add a new type to the registry, allowing it to be get through ActiveModel::Type#lookup
def register(type_name, klass = nil, **options, &block)
registry.register(type_name, klass, **options, &block)
end

def lookup(*args, **kwargs) # :nodoc:
registry.lookup(*args, **kwargs)
end
end

register(:big_integer, Type::BigInteger)
register(:binary, Type::Binary)
register(:boolean, Type::Boolean)
register(:date, Type::Date)
register(:datetime, Type::DateTime)
register(:decimal, Type::Decimal)
register(:float, Type::Float)
register(:immutable_string, Type::ImmutableString)
register(:integer, Type::Integer)
register(:string, Type::String)
register(:time, Type::Time)
end
end
15 changes: 15 additions & 0 deletions lib/active_model/type/big_integer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# frozen_string_literal: true

require "active_model/type/integer"

module ActiveModel
module Type
class BigInteger < Integer # :nodoc:
private

def max_value
::Float::INFINITY
end
end
end
end
52 changes: 52 additions & 0 deletions lib/active_model/type/binary.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# frozen_string_literal: true

module ActiveModel
module Type
class Binary < Value # :nodoc:
def type
:binary
end

def binary?
true
end

def cast(value)
if value.is_a?(Data)
value.to_s
else
super
end
end

def serialize(value)
return if value.nil?
Data.new(super)
end

def changed_in_place?(raw_old_value, value)
old_value = deserialize(raw_old_value)
old_value != value
end

class Data # :nodoc:
def initialize(value)
@value = value.to_s
end

def to_s
@value
end
alias_method :to_str, :to_s

def hex
@value.unpack("H*")[0]
end

def ==(other)
other == to_s || super
end
end
end
end
end
34 changes: 34 additions & 0 deletions lib/active_model/type/boolean.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# frozen_string_literal: true

module ActiveModel
module Type
# == Active \Model \Type \Boolean
#
# A class that behaves like a boolean type, including rules for coercion of user input.
#
# === Coercion
# Values set from user input will first be coerced into the appropriate ruby type.
# Coercion behavior is roughly mapped to Ruby's boolean semantics.
#
# - "false", "f" , "0", +0+ or any other value in +FALSE_VALUES+ will be coerced to +false+
# - Empty strings are coerced to +nil+
# - All other values will be coerced to +true+
class Boolean < Value
FALSE_VALUES = [false, 0, "0", "f", "F", "false", "FALSE", "off", "OFF"].to_set

def type # :nodoc:
:boolean
end

private

def cast_value(value)
if value == ""
nil
else
!FALSE_VALUES.include?(value)
end
end
end
end
end
56 changes: 56 additions & 0 deletions lib/active_model/type/date.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# frozen_string_literal: true

module ActiveModel
module Type
class Date < Value # :nodoc:
include Helpers::AcceptsMultiparameterTime.new

def type
:date
end

def serialize(value)
cast(value)
end

def type_cast_for_schema(value)
value.to_s(:db).inspect
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

ISO_DATE = /\A(\d{4})-(\d\d)-(\d\d)\z/
def fast_string_to_date(string)
if string =~ 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

def value_from_multiparameter_assignment(*)
time = super
time && time.to_date
end
end
end
end
46 changes: 46 additions & 0 deletions lib/active_model/type/date_time.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# frozen_string_literal: true

module ActiveModel
module Type
class DateTime < Value # :nodoc:
include Helpers::TimeValue
include Helpers::AcceptsMultiparameterTime.new(
defaults: { 4 => 0, 5 => 0 }
)

def type
:datetime
end

private

def cast_value(value)
return apply_seconds_precision(value) unless value.is_a?(::String)
return if value.empty?

fast_string_to_time(value) || fallback_string_to_time(value)
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

def value_from_multiparameter_assignment(values_hash)
missing_parameter = (1..3).detect { |key| !values_hash.key?(key) }
if missing_parameter
raise ArgumentError, missing_parameter
end
super
end
end
end
end
70 changes: 70 additions & 0 deletions lib/active_model/type/decimal.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# frozen_string_literal: true

require "bigdecimal/util"

module ActiveModel
module Type
class Decimal < Value # :nodoc:
include Helpers::Numeric
BIGDECIMAL_PRECISION = 18

def type
:decimal
end

def type_cast_for_schema(value)
value.to_s.inspect
end

private

def cast_value(value)
casted_value = \
case value
when ::Float
convert_float_to_big_decimal(value)
when ::Numeric
BigDecimal(value, precision || BIGDECIMAL_PRECISION)
when ::String
begin
value.to_d
rescue ArgumentError
BigDecimal(0)
end
else
if value.respond_to?(:to_d)
value.to_d
else
cast_value(value.to_s)
end
end

apply_scale(casted_value)
end

def convert_float_to_big_decimal(value)
if precision
BigDecimal(apply_scale(value), float_precision)
else
value.to_d
end
end

def float_precision
if precision.to_i > ::Float::DIG + 1
::Float::DIG + 1
else
precision.to_i
end
end

def apply_scale(value)
if scale
value.round(scale)
else
value
end
end
end
end
end
Loading

0 comments on commit 00b2992

Please sign in to comment.