Strict
Strict provides a means to strictly validate instantiation of values, instantiation and attribute assignment of objects, and method calls at runtime.
Installation
Install the gem and add to the application's Gemfile by executing:
$ bundle add strict
If bundler is not being used to manage dependencies, install the gem by executing:
$ gem install strict
Usage
Strict::Value
class Money
include Strict::Value
attributes do
amount_in_cents Integer
currency AnyOf("USD", "CAD"), default: "USD"
end
end
Money.new(amount_in_cents: 100_00)
# => #<Money amount_in_cents=100_00 currency="USD">
Money.new(amount_in_cents: 100_00, currency: "CAD")
# => #<Money amount_in_cents=100_00 currency="CAD">
Money.new(amount_in_cents: 100.00)
# => Strict::InitializationError
Money.new(amount_in_cents: 100_00).with(amount_in_cents: 200_00)
# => #<Money amount_in_cents=200_00 currency="USD">
Money.new(amount_in_cents: 100_00).amount_in_cents = 50_00
# => NoMethodError
Money.new(amount_in_cents: 100_00) == Money.new(amount_in_cents: 100_00)
# => true
Strict::Object
class Stateful
include Strict::Object
attributes do
some_state String
dependency Anything(), default: nil
end
end
Stateful.new(some_state: "123")
# => #<Stateful some_state="123" dependency=nil>
Stateful.new(some_state: "123").with(some_state: "456")
# => NoMethodError
Stateful.new(some_state: "123").some_state = "456"
# => "456"
# => #<Stateful some_state="456" dependency=nil>
Stateful.new(some_state: "123").some_state = 456
# => Strict::AssignmentError
Stateful.new(some_state: "123") == Stateful.new(some_state: "123")
# => false
Strict::Method
class UpdateEmail
extend Strict::Method
sig do
user_id String, coerce: ->(value) { value.to_s }
email String
returns AnyOf(true, nil)
end
def call(user_id:, email:)
# contrived logic
user_id == email
end
end
UpdateEmail.new.call(user_id: 123, email: "123")
# => true
UpdateEmail.new.call(user_id: "123", email: "123")
# => true
UpdateEmail.new.call(user_id: "123", email: 123)
# => Strict::MethodCallError
UpdateEmail.new.call(user_id: "123", email: "456")
# => Strict::MethodReturnError
Strict::Interface
class Storage
extend Strict::Interface
expose(:write) do
key String
contents String
returns Boolean()
end
expose(:read) do
key String
returns AnyOf(String, nil)
end
end
module Storages
class Memory
def initialize
@storage = {}
end
def write(key:, contents:)
storage[key] = contents
true
end
def read(key:)
storage[key]
end
private
attr_reader :storage
end
end
storage = Storage.new(Storages::Memory.new)
# => #<Storage implementation=#<Storages::Memory>>
storage.write(key: "some/path/to/file.rb", contents: "Hello")
# => true
storage.write(key: "some/path/to/file.rb", contents: {})
# => Strict::MethodCallError
storage.read(key: "some/path/to/file.rb")
# => "Hello"
storage.read(key: "some/path/to/other.rb")
# => nil
module Storages
class Wat
def write(key:)
end
end
end
storage = Storage.new(Storages::Wat.new)
# => Strict::ImplementationDoesNotConformError
Configuration
Strict exposes some configuration options which can be configured globally via Strict.configure { ... }
or overridden
within a block via Strict.with_overrides(...) { ... }
.
Example
# Globally
Strict.configure do |c|
c.sample_rate = 0.75 # run validation ~75% of the time
end
Strict.configure do |c|
c.sample_rate = 0 # disable validation (Strict becomes Lenient)
end
Strict.configure do |c|
c.sample_rate = 0 # always run validation
end
# Locally within the block (only applies to the current thread)
Strict.with_overrides(sample_rate: 0) do
# Use Strict as you normally would
Strict.with_overrides(sample_rate: 0.5) do
# Overrides can be nested
end
end
Strict.configuration.random
The instance of a Random::Formatter
that Strict uses in tandom with the sample_rate
to determine when validation
should be checked.
Default: Random.new
Strict.configuration.sample_rate
The rate of samples Strict will consider when validating attributes, parameters, and return values. A rate of 0.25 will
validate roughly 25% of the time, a rate of 0 will disable validation entirely, and a rate of 1 will always
run validations. The sample_rate
is used in tandem with random
to determine whether validation should be run.
Default: 1
Development
After checking out the repo, run bin/setup
to install dependencies. Then, run rake test
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 the created tag, and push the .gem
file to rubygems.org.
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/kylekthompson/strict. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.
License
The gem is available as open source under the terms of the MIT License.
Code of Conduct
Everyone interacting in the Strict project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.
Credit
I can't thank Tom Dalling enough for his excellent ValueSemantics gem. Strict is heavily inspired and influenced by Tom's work and has some borrowed concepts and code.