Skip to content

Commit 408a8bf

Browse files
committed
Merge pull request #2 from esebastian/include-module
Use the module with 'include' instead of 'extend'
2 parents 005b932 + 1e84a36 commit 408a8bf

5 files changed

Lines changed: 128 additions & 103 deletions

File tree

.gitignore

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,6 @@
1-
*.gem
1+
## build artifacts
2+
*.gem
3+
4+
## environment normalisation
5+
/.bundle/
6+
/vendor/bundle

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ A simple module to provide value objects semantics to a class.
1616
require 'value_object'
1717

1818
class Point
19-
extend ValueObject
19+
include ValueObject
2020
fields :x, :y
2121
end
2222

@@ -39,7 +39,7 @@ point.x = 3
3939
require 'value_object'
4040

4141
class Point
42-
extend ValueObject
42+
include ValueObject
4343
fields :x, :y
4444
end
4545

@@ -71,7 +71,7 @@ a_point.eql?(a_different_point)
7171
require 'value_object'
7272

7373
class Point
74-
extend ValueObject
74+
include ValueObject
7575
fields :x, :y
7676
end
7777

@@ -99,7 +99,7 @@ You can declare invariants to restrict field values on initialization
9999
require 'value_object'
100100

101101
class Point
102-
extend ValueObject
102+
include ValueObject
103103
fields :x, :y
104104
invariants :x_less_than_y, :inside_first_quadrant
105105

lib/exceptions.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ def initialize(fields)
2424
end
2525

2626
class WrongNumberOfArguments < Exception
27-
def initialize fields_number, arguments_number
27+
def initialize(fields_number, arguments_number)
2828
super "Declared #{fields_number} fields but passing #{arguments_number}"
2929
end
3030
end

lib/value_object.rb

Lines changed: 81 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,89 +1,108 @@
11
require 'exceptions'
22

33
module ValueObject
4-
def fields(*names)
5-
raise NotDeclaredFields.new() if names.empty?()
64

7-
attr_reader(*names)
5+
def self.included(base)
6+
base.extend(ClassMethods)
7+
end
88

9-
define_method(:initialize) do |*values|
10-
check_fields_are_initialized(values)
11-
set_instance_variables(values)
12-
check_invariants()
13-
end
9+
def initialize(*values)
10+
@values = values
11+
check_fields_are_initialized
12+
set_instance_variables
13+
check_invariants
14+
end
1415

15-
define_method(:eql?) do |other|
16-
self.class == other.class && values == other.values
17-
end
16+
def eql?(other)
17+
self.class == other.class && values == other.values
18+
end
1819

19-
define_method(:==) do |other|
20-
eql?(other)
21-
end
20+
def ==(other)
21+
eql?(other)
22+
end
2223

23-
define_method(:hash) do
24-
self.class.hash ^ values.hash
25-
end
24+
def hash
25+
self.class.hash ^ values.hash
26+
end
2627

27-
define_method(:values) do
28-
names.map { |field| send(field) }
29-
end
30-
protected(:values)
28+
protected
3129

32-
define_method(:check_invariants) do
33-
end
34-
private(:check_invariants)
30+
def values
31+
@values
32+
end
3533

36-
define_method(:check_fields_are_initialized) do |values|
37-
raise WrongNumberOfArguments.new(names.length, values.length) unless arguments_number_is_right?(values)
38-
raise FieldWithoutValue.new(uninitialized_fields_names(values)) unless all_fields_initialized?(values)
39-
end
40-
private(:check_fields_are_initialized)
34+
private
35+
36+
def names
37+
self.class.field_names
38+
end
39+
40+
def predicates
41+
self.class.predicate_symbols
42+
end
4143

42-
define_method(:arguments_number_is_right?) do |values|
43-
fields_number = names.length
44-
arguments_number = values.length
45-
values.length == names.length
44+
def check_fields_are_initialized
45+
raise WrongNumberOfArguments.new(names.length, values.length) unless arguments_number_is_right?
46+
raise FieldWithoutValue.new(uninitialized_field_names) unless all_fields_initialized?
47+
end
48+
49+
def set_instance_variables
50+
names.zip(values) do |name, value|
51+
instance_variable_set(:"@#{name}", value)
4652
end
47-
private(:arguments_number_is_right?)
53+
end
54+
55+
def arguments_number_is_right?
56+
values.length == names.length
57+
end
58+
59+
def uninitialized_fields
60+
names.zip(values).select { |name, value| value.nil? }
61+
end
62+
63+
def all_fields_initialized?
64+
uninitialized_fields.empty?
65+
end
66+
67+
def uninitialized_field_names
68+
uninitialized_fields.map { |field| field.first }
69+
end
70+
71+
def check_invariants
72+
return if predicates.nil?
4873

49-
define_method(:uninitialized_fields) do |values|
50-
names.zip(values).select { |name, value| value.nil? }
74+
predicates.each do |predicate|
75+
valid = invariant_holds?(predicate)
76+
raise ViolatedInvariant.new(predicate, values) unless valid
5177
end
52-
private(:uninitialized_fields)
78+
end
5379

54-
define_method(:all_fields_initialized?) do |values|
55-
uninitialized_fields(values).empty?
80+
def invariant_holds?(predicate_symbol)
81+
begin
82+
valid = send(predicate_symbol)
83+
rescue
84+
raise NotImplementedInvariant.new(predicate_symbol)
5685
end
57-
private(:all_fields_initialized?)
86+
end
5887

59-
define_method(:uninitialized_fields_names) do |values|
60-
uninitialized_fields(values).map { |field| field.first }
88+
module ClassMethods
89+
def field_names
90+
@field_names
6191
end
62-
private(:uninitialized_fields_names)
6392

64-
define_method(:set_instance_variables) do |values|
65-
names.zip(values) do |name, value|
66-
instance_variable_set(:"@#{name}", value)
67-
end
93+
def predicate_symbols
94+
@predicate_symbols
6895
end
69-
private(:set_instance_variables)
70-
end
7196

72-
def invariants(*predicate_symbols)
73-
define_method(:check_invariants) do
74-
predicate_symbols.each do |predicate_symbol|
75-
valid = invariant_holds?(predicate_symbol)
76-
raise ViolatedInvariant.new(predicate_symbol, self.values) unless valid
77-
end
97+
def fields(*names)
98+
raise NotDeclaredFields.new if names.empty?
99+
100+
attr_reader(*names)
101+
@field_names = names
78102
end
79103

80-
define_method(:invariant_holds?) do |predicate_symbol|
81-
begin
82-
valid = send(predicate_symbol)
83-
rescue
84-
raise NotImplementedInvariant.new(predicate_symbol)
85-
end
104+
def invariants(*predicate_symbols)
105+
@predicate_symbols = predicate_symbols
86106
end
87-
private(:invariant_holds?)
88107
end
89108
end

spec/value_object_spec.rb

Lines changed: 36 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
require 'spec_helper'
2-
require "./lib/value_object"
2+
require './lib/value_object'
33

44
describe "ValueObject" do
55

66
describe "standard behavior" do
77
class Point
8-
extend ValueObject
8+
include ValueObject
99
fields :x, :y
1010
end
1111

@@ -38,80 +38,81 @@ class Point
3838
describe "restrictions" do
3939
describe "on declaration" do
4040
it "must at least have one field" do
41-
expect do
42-
class DummyWithNoFieldsUsingFieldsMethod
43-
extend ValueObject
41+
expect {
42+
class Point
43+
include ValueObject
4444
fields
4545
end
46-
end.to raise_error(ValueObject::NotDeclaredFields)
46+
}.to raise_error(ValueObject::NotDeclaredFields)
4747
end
4848
end
4949

5050
describe "on initialization" do
5151
it "must not have any field initialized to nil" do
52-
class DummyWithDeclaredFieldsWithoutValue
53-
extend ValueObject
52+
class Point
53+
include ValueObject
5454
fields :x, :y
5555
end
5656

57-
expect{
58-
DummyWithDeclaredFieldsWithoutValue.new 1, nil
59-
}.to raise_error(ValueObject::FieldWithoutValue, "Declared fields [:y] must have value")
60-
57+
expect {
58+
Point.new 1, nil
59+
}.to raise_error(ValueObject::FieldWithoutValue, "Declared fields [:y] must have value")
60+
6161
end
62-
62+
6363
it "must have number of values equal to number of fields" do
6464
class Point
65-
extend ValueObject
65+
include ValueObject
6666
fields :x, :y
6767
end
68-
69-
expect{
68+
69+
expect {
7070
Point.new(1)
71-
}.to raise_error(ValueObject::WrongNumberOfArguments)
72-
71+
}.to raise_error(ValueObject::WrongNumberOfArguments)
7372

74-
expect{ Point.new(1, 2, 3) }.to raise_error(ValueObject::WrongNumberOfArguments, "Declared 2 fields but passing 3")
73+
expect {
74+
Point.new(1, 2, 3)
75+
}.to raise_error(ValueObject::WrongNumberOfArguments, "Declared 2 fields but passing 3")
7576
end
7677
end
7778
end
7879

7980
describe "forcing invariants" do
8081
it "forces declared invariants" do
8182
class Point
82-
extend ValueObject
83+
include ValueObject
8384
fields :x, :y
8485
invariants :x_less_than_y, :inside_first_quadrant
8586

8687
private
87-
def inside_first_quadrant
88-
x > 0 && y > 0
89-
end
90-
9188
def x_less_than_y
9289
x < y
9390
end
91+
92+
def inside_first_quadrant
93+
x > 0 && y > 0
94+
end
9495
end
9596

96-
expect{ Point.new(-5, 3) }.to raise_error(
97-
ValueObject::ViolatedInvariant, "Fields values [-5, 3] violate invariant: inside_first_quadrant"
98-
)
97+
expect {
98+
Point.new(6, 3)
99+
}.to raise_error(ValueObject::ViolatedInvariant, "Fields values [6, 3] violate invariant: x_less_than_y")
99100

100-
expect{ Point.new(6, 3) }.to raise_error(
101-
ValueObject::ViolatedInvariant, "Fields values [6, 3] violate invariant: x_less_than_y"
102-
)
101+
expect {
102+
Point.new(-5, 3)
103+
}.to raise_error(ValueObject::ViolatedInvariant, "Fields values [-5, 3] violate invariant: inside_first_quadrant")
103104
end
104105

105106
it "raises an exception when a declared invariant has not been implemented" do
106-
class PairOfIntegers
107-
extend ValueObject
107+
class Point
108+
include ValueObject
108109
fields :x, :y
109110
invariants :integers
110111
end
111112

112-
expect{ PairOfIntegers.new(5, 2) }.to raise_error(
113-
ValueObject::NotImplementedInvariant, "Invariant integers needs to be implemented"
114-
)
113+
expect {
114+
Point.new(5, 2)
115+
}.to raise_error(ValueObject::NotImplementedInvariant, "Invariant integers needs to be implemented")
115116
end
116117
end
117118
end

0 commit comments

Comments
 (0)