values 1.4.0 → 1.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +13 -5
- data/VERSION +1 -1
- data/lib/values.rb +16 -22
- data/spec/values_spec.rb +25 -20
- data/values.gemspec +2 -2
- metadata +4 -4
data/README.md
CHANGED
@@ -4,7 +4,7 @@ These mostly look like classes created using Struct, but fix two problems with t
|
|
4
4
|
Struct constructors can take less than the default number of arguments and set other fields as nil:
|
5
5
|
|
6
6
|
```ruby
|
7
|
-
Point = Struct.new(:x
|
7
|
+
Point = Struct.new(:x, :y)
|
8
8
|
Point.new(1)
|
9
9
|
# => #<struct Point x=1, y=nil>
|
10
10
|
```
|
@@ -12,8 +12,8 @@ Point.new(1)
|
|
12
12
|
Structs are also mutable:
|
13
13
|
|
14
14
|
```ruby
|
15
|
-
Point = Struct.new(:x
|
16
|
-
p = Point.new(1,2)
|
15
|
+
Point = Struct.new(:x, :y)
|
16
|
+
p = Point.new(1, 2)
|
17
17
|
p.x = 2
|
18
18
|
p.x
|
19
19
|
# => 2
|
@@ -30,11 +30,19 @@ Point.new(1)
|
|
30
30
|
# from (irb):5
|
31
31
|
# from /usr/local/bin/irb:12:in `<main>
|
32
32
|
|
33
|
-
p = Point.new(1,2)
|
33
|
+
p = Point.new(1, 2)
|
34
34
|
p.x = 1
|
35
35
|
# => NoMethodError: undefined method x= for #<Point:0x00000100943788 @x=0, @y=1>
|
36
36
|
# from (irb):6
|
37
37
|
# from /usr/local/bin/irb:12:in <main>
|
38
38
|
```
|
39
39
|
|
40
|
-
|
40
|
+
Values also provides an alternative constructor which takes a hash:
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
p = Point.with(x: 3, y: 4)
|
44
|
+
p.x
|
45
|
+
# => 3
|
46
|
+
```
|
47
|
+
|
48
|
+
Values does NOT have all the features of Struct (nor is it meant to).
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.
|
1
|
+
1.5.0
|
data/lib/values.rb
CHANGED
@@ -1,49 +1,43 @@
|
|
1
1
|
class Value
|
2
2
|
def self.new(*fields)
|
3
|
-
|
3
|
+
Class.new do
|
4
4
|
attr_reader *fields
|
5
5
|
|
6
|
-
define_method(:initialize) do |*
|
7
|
-
raise ArgumentError.new("wrong number of arguments, #{
|
6
|
+
define_method(:initialize) do |*values|
|
7
|
+
raise ArgumentError.new("wrong number of arguments, #{values.size} for #{fields.size}") if fields.size != values.size
|
8
8
|
|
9
|
-
fields.
|
10
|
-
instance_variable_set(
|
9
|
+
fields.zip(values) do |field, value|
|
10
|
+
instance_variable_set(:"@#{field}", value)
|
11
11
|
end
|
12
|
-
|
12
|
+
|
13
|
+
freeze
|
13
14
|
end
|
14
15
|
|
15
16
|
const_set :VALUE_ATTRS, fields
|
16
17
|
|
17
18
|
def self.with(hash)
|
18
|
-
args = []
|
19
|
-
self::VALUE_ATTRS.each do |field|
|
20
|
-
args << hash.fetch(field)
|
21
|
-
end
|
22
|
-
|
23
19
|
unexpected_keys = hash.keys - self::VALUE_ATTRS
|
24
20
|
if unexpected_keys.any?
|
25
21
|
raise ArgumentError.new("Unexpected hash keys: #{unexpected_keys}")
|
26
22
|
end
|
27
|
-
|
23
|
+
|
24
|
+
new(*hash.values_at(*self::VALUE_ATTRS))
|
28
25
|
end
|
29
26
|
|
30
27
|
def ==(other)
|
31
|
-
|
28
|
+
eql?(other)
|
32
29
|
end
|
33
30
|
|
34
31
|
def eql?(other)
|
35
|
-
|
36
|
-
self.class::VALUE_ATTRS.all? do |field|
|
37
|
-
self.send(field) == other.send(field)
|
38
|
-
end
|
32
|
+
self.class == other.class && values == other.values
|
39
33
|
end
|
40
34
|
|
41
35
|
def hash
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
36
|
+
values.map(&:hash).inject(0, :+) + self.class.hash
|
37
|
+
end
|
38
|
+
|
39
|
+
def values
|
40
|
+
self.class::VALUE_ATTRS.map { |field| send(field) }
|
47
41
|
end
|
48
42
|
end
|
49
43
|
end
|
data/spec/values_spec.rb
CHANGED
@@ -1,12 +1,14 @@
|
|
1
1
|
require File.expand_path(File.dirname(__FILE__) + '/../lib/values')
|
2
2
|
describe 'values' do
|
3
|
+
Cell = Value.new(:alive)
|
4
|
+
|
3
5
|
it 'stores a single field' do
|
4
|
-
Cell = Value.new(:alive)
|
5
6
|
c = Cell.new(true)
|
6
7
|
c.alive.should == true
|
7
8
|
end
|
8
9
|
|
9
10
|
Point = Value.new(:x, :y)
|
11
|
+
|
10
12
|
it 'stores multiple values' do
|
11
13
|
p = Point.new(0,1)
|
12
14
|
p.x.should == 0
|
@@ -14,65 +16,62 @@ describe 'values' do
|
|
14
16
|
end
|
15
17
|
|
16
18
|
it 'raises argument errors if not given the right number of arguments' do
|
17
|
-
|
19
|
+
expect { Point.new }.to raise_error(ArgumentError, 'wrong number of arguments, 0 for 2')
|
18
20
|
end
|
19
21
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
"GraphPoint at #{@x},#{@y}"
|
24
|
-
end
|
22
|
+
class GraphPoint < Value.new(:x, :y)
|
23
|
+
def inspect
|
24
|
+
"GraphPoint at #{@x},#{@y}"
|
25
25
|
end
|
26
|
+
end
|
26
27
|
|
28
|
+
it 'can be inherited from to add methods' do
|
27
29
|
c = GraphPoint.new(0,0)
|
28
30
|
c.inspect.should == 'GraphPoint at 0,0'
|
29
31
|
end
|
30
32
|
|
31
33
|
it 'cannot be mutated' do
|
32
34
|
p = Point.new(0,1)
|
33
|
-
|
35
|
+
expect { p.x = 1 }.to raise_error
|
34
36
|
end
|
35
37
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
@color = new_color
|
40
|
-
end
|
38
|
+
class Cow < Value.new(:color)
|
39
|
+
def change_color(new_color)
|
40
|
+
@color = new_color
|
41
41
|
end
|
42
|
+
end
|
42
43
|
|
44
|
+
it 'cannot even be mutated inside a sublass with methods' do
|
43
45
|
c = Cow.new("red")
|
44
|
-
|
46
|
+
expect { c.change_color("blue") }.to raise_error
|
45
47
|
end
|
46
48
|
|
49
|
+
Money = Value.new(:amount, :denomination)
|
50
|
+
|
47
51
|
it 'cannot be mutated using #instance_variable_set' do
|
48
|
-
Money = Value.new(:amount, :denomination)
|
49
52
|
m = Money.new(1, 'USD')
|
50
|
-
|
53
|
+
expect { m.instance_variable_set('@amount', 2) }.to raise_error
|
51
54
|
end
|
52
55
|
|
53
56
|
it 'can be instantiated with a hash' do
|
54
|
-
Money = Value.new(:amount, :denomination)
|
55
57
|
one_dollar = Money.with(:amount => 1, :denomination => 'USD')
|
56
58
|
one_dollar.amount.should == 1
|
57
59
|
one_dollar.denomination.should == 'USD'
|
58
60
|
end
|
59
61
|
|
60
62
|
it 'errors if you instantiate it from a hash with unrecognised fields' do
|
61
|
-
Money = Value.new(:amount, :denomination)
|
62
63
|
expect do
|
63
64
|
Money.with(:unrecognized_field => 1, :amount => 2, :denomination => 'USD')
|
64
65
|
end.to raise_error
|
65
66
|
end
|
66
67
|
|
67
68
|
it 'errors if you instantiate it from a hash with missing fields' do
|
68
|
-
Money = Value.new(:amount, :denomination)
|
69
69
|
expect do
|
70
70
|
Money.with
|
71
71
|
end.to raise_error
|
72
72
|
end
|
73
73
|
|
74
74
|
it 'does not error when fields are explicitly nil' do
|
75
|
-
Money = Value.new(:amount, :denomination)
|
76
75
|
expect do
|
77
76
|
Money.with(:amount => 1, :denomination => nil)
|
78
77
|
end.not_to raise_error
|
@@ -108,4 +107,10 @@ describe 'values' do
|
|
108
107
|
Point.new(0,0).hash.should_not == Y.new(0,0).hash
|
109
108
|
end
|
110
109
|
end
|
110
|
+
|
111
|
+
describe '#values' do
|
112
|
+
it 'returns an array of field values' do
|
113
|
+
Point.new(10, 13).values.should == [10, 13]
|
114
|
+
end
|
115
|
+
end
|
111
116
|
end
|
data/values.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = "values"
|
8
|
-
s.version = "1.
|
8
|
+
s.version = "1.5.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Tom Crayford"]
|
12
|
-
s.date = "2013-02-
|
12
|
+
s.date = "2013-02-22"
|
13
13
|
s.description = "Simple immutable value objects for ruby.\n\n Make a new value class: Point = Value.new(:x, :y)\n And use it:\n p = Point.new(1,0)\n p.x\n => 1\n p.y\n => 0\n "
|
14
14
|
s.email = "tcrayford@googlemail.com"
|
15
15
|
s.extra_rdoc_files = [
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: values
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 3
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 1
|
8
|
-
-
|
8
|
+
- 5
|
9
9
|
- 0
|
10
|
-
version: 1.
|
10
|
+
version: 1.5.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Tom Crayford
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2013-02-
|
18
|
+
date: 2013-02-22 00:00:00 Z
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
21
21
|
version_requirements: &id001 !ruby/object:Gem::Requirement
|