Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
153 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
require 'obvious/version' | ||
require 'obvious/contract' | ||
require 'obvious/entity' | ||
require_relative 'generators/application_generator' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
|
||
module Obvious | ||
|
||
module EntityMixin | ||
class << self | ||
def included(base) | ||
base.extend ClassMethods | ||
end | ||
end | ||
|
||
module ClassMethods | ||
attr_reader :shape | ||
attr_reader :validations | ||
|
||
def value name, type | ||
name = name.to_sym | ||
@shape ||= {} | ||
@shape[name] = type | ||
define_method(name) { @values[name] } | ||
end | ||
|
||
def validation name, method | ||
name = "#{name}_validation".to_sym | ||
@validations ||= [] | ||
@validations << name | ||
define_method(name) { instance_exec &method } | ||
end | ||
end | ||
end | ||
|
||
class ShapeError < StandardError; end | ||
class TypeError < StandardError; end | ||
class ValidationError < StandardError; end | ||
|
||
class Entity | ||
include EntityMixin | ||
|
||
def initialize input | ||
shape = self.class.shape | ||
validations = self.class.validations || [] | ||
|
||
self.class.shape.freeze | ||
self.class.validations.freeze | ||
|
||
@values = {} | ||
input.each do |k, v| | ||
unless shape[k] | ||
raise Obvious::ShapeError.new "Invalid input field: #{k}" | ||
else | ||
@values[k] = v | ||
end | ||
end | ||
|
||
@values.freeze # this might need to be recursive? | ||
|
||
shape.each do |k, v| | ||
unless @values[k].class == v | ||
msg = "Validation Error: Invalid value for #{k}, should be a #{v}" | ||
raise Obvious::TypeError.new msg | ||
end | ||
end | ||
|
||
validations.each { |method| send method } | ||
|
||
freeze | ||
end | ||
|
||
def to_hash | ||
{}.tap {|h| @values.each { |k, v| h[k] = v } } | ||
end | ||
|
||
end | ||
end | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
require 'obvious' | ||
|
||
class Thing < Obvious::Entity | ||
value :id, Fixnum | ||
value :name, String | ||
end | ||
|
||
class Thing2 < Obvious::Entity | ||
value :foo , String | ||
|
||
validation :something, Proc.new { | ||
if foo != "hello world" | ||
msg = "Validation Error: Invalid value for foo, should be 'hello world'" | ||
raise Obvious::ValidationError.new msg | ||
end | ||
} | ||
|
||
def modify_foo | ||
@values[:foo] = 100 | ||
end | ||
|
||
end | ||
|
||
class Thing3 < Obvious::Entity | ||
value :foo , String | ||
|
||
validation :something, Proc.new { | ||
@values[:foo] = 12 | ||
} | ||
|
||
end | ||
|
||
# To test the entity, we are going to use classes that inherit from it instead | ||
# of poking at it directly. In this case, I think that makes the most sense. | ||
describe Thing do | ||
it 'should create a valid object with valid input' do | ||
input = { name: 'Thing', id: 1 } | ||
t = Thing.new input | ||
t.name.should eq 'Thing' | ||
t.id.should eq 1 | ||
end | ||
|
||
it 'should raise an error with invalid input types' do | ||
input = { name: nil, id: nil } | ||
expect { Thing.new input }.to raise_error Obvious::TypeError | ||
end | ||
|
||
it 'should raise an error with extra fields in input' do | ||
input = { name: 'Thing', id: 1, extra: 'should explode' } | ||
expect { Thing.new input }.to raise_error Obvious::ShapeError | ||
end | ||
|
||
it 'should raise an error when a method tries to modify a value' do | ||
t = Thing2.new foo: 'hello world' | ||
expect { t.modify_foo }.to raise_error RuntimeError | ||
end | ||
|
||
describe '#to_hash' do | ||
it 'should return a hash representation of the object' do | ||
input = { name: 'Thing', id: 1 } | ||
t = Thing.new input | ||
t.to_hash.should eq input | ||
end | ||
end | ||
|
||
describe 'validation' do | ||
it 'should raise a validation error on a failed validation' do | ||
expect { Thing2.new foo: 'not valid I promise!' }.to raise_error Obvious::ValidationError | ||
end | ||
|
||
it 'should raise an error when trying to modify an object in a validation' do | ||
expect { Thing3.new foo: 'hello world' }.to raise_error RuntimeError | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
RSpec.configure do |c| | ||
c.mock_with :rspec | ||
end |