Skip to content
This repository has been archived by the owner on Aug 17, 2023. It is now read-only.

jobteaser/reasonable

Repository files navigation

Reasonable::Value

Reasonable::Value is a value object implementation in its smallest possible form.

Why another implementation ?

Virtus

Virtus does too many things and is deprecated in favor of dry-struct

dry-struct

I felt that dry-struct was

  1. Not documented enough
  2. Did not properly handle "truly" optional attributes

Installation

Add this line to your application's Gemfile:

gem 'reasonable-value'

And then execute:

$ bundle

Or install it yourself as:

$ gem install reasonable-value

Usage

By default attributes are mandatory, but coercible (meaning that passing a Float when an Integer is expected will not raise an error:

class StandardValue < Reasonable::Value
  attribute :integer, Integer
end

p StandardValue.new
# => TypeError: expected :integer to be a Integer but was a NilClass

p StandardValue.new(integer: nil)
# => TypeError: expected :integer to be a Integer but was a NilClass

p StandardValue.new(integer: 1)
# => #<StandardValue:0x007f65ec156720 @attributes={:integer=>1}>

p StandardValue.new(integer: 1.1)
# => #<StandardValue:0x007f65ec166738 @attributes={:integer=>1}>

If you want optional attributes, you can say so like that:

class OptionalValue < Reasonable::Value
  attribute :string, String, optional: true
end

p OptionalValue.new
# => #<OptionalValue:0x0055ecec2ae2c8 @attributes={}>

p OptionalValue.new(string: nil)
# => #<OptionalValue:0x007f65ec16c430 @attributes={}>

p OptionalValue.new(string: 'string')
# => #<OptionalValue:0x007f65ec174f18 @attributes={:string=>"string"}>

p OptionalValue.new(string: 1.1)
# => #<OptionalValue:0x007f65ec1792e8 @attributes={:string=>"1.1"}>

Additionally, you can specify a default value for optional attributes:

class OptionalValueWithDefault < Reasonable::Value
  attribute :string, String, optional: true, default: 'foo'
end

p OptionalValueWithDefault.new
# => #<OptionalValueWithDefault:0x0055ecec2ae2c8 @attributes={:string=>"foo"}>

p OptionalValueWithDefault.new(string: nil)
# => #<OptionalValueWithDefault:0x007f65ec16c430 @attributes={:string=>"foo"}>

p OptionalValueWithDefault.new(string: 'string')
# => #<OptionalValueWithDefault:0x007f65ec174f18 @attributes={:string=>"string"}>

p OptionalValueWithDefault.new(string: 1.1)
# => #<OptionalValueWithDefault:0x007f65ec1792e8 @attributes={:string=>"1.1"}>

You are not limited to Integer or String, you can use any type you want:

class ValueWithCustomType < Reasonable::Value
  attribute :custom, StandardValue
end

p ValueWithCustomType.new(custom: StandardValue.new(integer: 1))
# => #<ValueWithCustomType:0x007f65ec18d540 @attributes={:custom=>#<StandardValue:0x007f65ec18d6a8 @attributes={:integer=>1}>}>

p ValueWithCustomType.new(custom: { integer: 1 })
# => #<ValueWithCustomType:0x007f65ec19f920 @attributes={:custom=>#<StandardValue:0x007f65ec19f358 @attributes={:integer=>1}>}>

You can pass a list of types if need be:

class TypeListValue < Reasonable::Value
  attribute :boolean, [TrueClass, FalseClass]
end

p TypeListValue.new(boolean: true)
# => #<TypeListValue:0x00560d002c7f50 @attributes={:boolean=>true}>
p TypeListValue.new(boolean: false)
# => #<TypeListValue:0x00560d002c7f50 @attributes={:boolean=>false}>
p TypeListValue.new(boolean: 'error')
# => TypeError: expected :boolean to be a [TrueClass, FalseClass] but was a String

If you define the appropriate method on the class of the attribute, Reasonable::Value will handle casting gracefully:

class CastableType
  def to_standard_value
    StandardValue.new(integer: 1)
  end
end

p StandardValue.new(CastableType.new)
# => #<StandardValue:0x005598945ba928 @attributes={:integer=>1}>

p ValueWithCustomType.new(custom: CastableType.new)
# => #<ValueWithCustomType:0x007f65ec1a6590 @attributes={:custom=>#<StandardValue:0x007f65ec1a5bb8 @attributes={:integer=>1}>}>

Equality is based on attributes, instead of identity:

p StandardValue.new(integer: 1) == StandardValue.new(integer: 1)
# => true
p StandardValue.new(integer: 1) == StandardValue.new(integer: 2)
# => false

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake spec to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and tags, and push the .gem file to rubygems.org.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/reasonable-value.

License

The gem is available as open source under the terms of the MIT License.

About

No description or website provided.

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published