Skip to content

Commit

Permalink
Merge branch 'gh-27'
Browse files Browse the repository at this point in the history
  • Loading branch information
brianknapp committed Mar 26, 2013
2 parents fdc35b1 + 5b847bf commit d02cc46
Show file tree
Hide file tree
Showing 4 changed files with 153 additions and 0 deletions.
1 change: 1 addition & 0 deletions lib/obvious.rb
@@ -1,3 +1,4 @@
require 'obvious/version'
require 'obvious/contract'
require 'obvious/entity'
require_relative 'generators/application_generator'
74 changes: 74 additions & 0 deletions lib/obvious/entity.rb
@@ -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

75 changes: 75 additions & 0 deletions spec/entity_spec.rb
@@ -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
3 changes: 3 additions & 0 deletions spec/spec_helper.rb
@@ -0,0 +1,3 @@
RSpec.configure do |c|
c.mock_with :rspec
end

0 comments on commit d02cc46

Please sign in to comment.