value_struct 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/ChangeLog.md +5 -0
- data/README.md +26 -18
- data/lib/value_struct.rb +28 -16
- data/lib/value_struct/immutable.rb +8 -0
- data/lib/value_struct/no_clone.rb +5 -0
- data/lib/value_struct/version.rb +1 -1
- data/spec/{clone_spec.rb → no_clone_spec.rb} +2 -2
- data/spec/value_struct_spec.rb +143 -0
- metadata +6 -6
- data/lib/value_struct/clone.rb +0 -5
- data/lib/value_struct/core.rb +0 -12
- data/spec/struct_value_spec.rb +0 -83
data/ChangeLog.md
CHANGED
data/README.md
CHANGED
@@ -4,14 +4,16 @@ A value struct is a subclass of normal [Ruby struct](http://blog.grayproductions
|
|
4
4
|
|
5
5
|
__Value structs are immutable, i.e. they don't have setters (although, not recursively*)__
|
6
6
|
|
7
|
-
|
7
|
+
Additionally, this gem provides the following optional mixins to make life easier when using immutable structs:
|
8
8
|
|
9
|
-
*
|
10
|
-
*
|
11
|
-
* __Freeze__ Automatically freeze new instances
|
12
|
-
* __ToH__ #to_h for converting into a hash (if Ruby version below < 2.0)
|
9
|
+
* __:dup_with_changes__ #dup takes a optional hash for setting new values in the duplicate
|
10
|
+
* __:to_h__ #to_h for converting into a valuestruct into a hash (if Ruby version below < 2.0)
|
13
11
|
|
14
|
-
|
12
|
+
* __:strict_arguments__ Value structs need to be initialized with the exact amount of arguments
|
13
|
+
* __:freeze__ Automatically freezes new instances
|
14
|
+
* __:no_clone__ #clone does not clone, but return the same object
|
15
|
+
|
16
|
+
By default, only :dup_with_changes and :to_h get included.
|
15
17
|
|
16
18
|
## Why?
|
17
19
|
|
@@ -19,16 +21,7 @@ Sometimes you want to eliminate state. See [this blog article] for more informat
|
|
19
21
|
|
20
22
|
## Performance
|
21
23
|
|
22
|
-
Without mixins, ValueStructs are as fast as normal structs. Some (optional) mixins add noticable overhead, e.g.
|
23
|
-
|
24
|
-
## How to use structs with mixins
|
25
|
-
|
26
|
-
Point = ValueStruct.new_with_mixins :x, :y, [
|
27
|
-
ValueStruct::DupWithChanges,
|
28
|
-
ValueStruct::ToH,
|
29
|
-
ValueStruct::StrictArguments,
|
30
|
-
ValueStruct::Freeze,
|
31
|
-
]
|
24
|
+
Without mixins, ValueStructs are as fast as normal structs. Some (optional) mixins add noticable overhead, e.g. strict_arguments
|
32
25
|
|
33
26
|
## Example
|
34
27
|
|
@@ -40,6 +33,21 @@ Without mixins, ValueStructs are as fast as normal structs. Some (optional) mixi
|
|
40
33
|
|
41
34
|
Please refer to the [documentation of Ruby's struct] for more details on usage.
|
42
35
|
|
36
|
+
## How to use structs with mixins
|
37
|
+
|
38
|
+
Point = ValueStruct.new_with_mixins :x, :y, [
|
39
|
+
ValueStruct::ToH,
|
40
|
+
ValueStruct::Freeze,
|
41
|
+
ValueStruct::DupWithChanges,
|
42
|
+
ValueStruct::StrictArguments,
|
43
|
+
]
|
44
|
+
|
45
|
+
p = Point.new(1,2)
|
46
|
+
p.to_h #=> { :x => 1, :y => 2 }
|
47
|
+
p.frozen? #=> true
|
48
|
+
p.dup(x: 0) #=> #<ValueStruct Point x=0, y=2>
|
49
|
+
Point.new(1) # ArgumentError
|
50
|
+
|
43
51
|
## *
|
44
52
|
|
45
53
|
Because of the nature of Ruby, most things are not really immutable. So if you have an attribute `:by` and initialize it with an array, you cannot change the value struct anymore, but still the array:
|
@@ -57,8 +65,8 @@ Because of the nature of Ruby, most things are not really immutable. So if you h
|
|
57
65
|
|
58
66
|
## Influenced by / Thanks to
|
59
67
|
|
60
|
-
* Ruby Rogues #123
|
61
68
|
* Tom Crayford: [Values](https://github.com/tcrayford/Values)
|
62
|
-
* https://github.com/iconara/immutable_struct
|
69
|
+
* Theo Hultberg: [ImmutableStruct](https://github.com/iconara/immutable_struct)
|
70
|
+
* Ruby Rogues
|
63
71
|
|
64
72
|
## J-_-L
|
data/lib/value_struct.rb
CHANGED
@@ -1,28 +1,40 @@
|
|
1
1
|
require_relative 'value_struct/version'
|
2
|
-
|
2
|
+
|
3
|
+
require_relative 'value_struct/immutable'
|
3
4
|
require_relative 'value_struct/to_h'
|
4
5
|
require_relative 'value_struct/dup_with_changes'
|
5
6
|
require_relative 'value_struct/strict_arguments'
|
6
|
-
require_relative 'value_struct/
|
7
|
+
require_relative 'value_struct/no_clone'
|
7
8
|
require_relative 'value_struct/freeze'
|
8
9
|
|
9
10
|
class ValueStruct < Struct
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
11
|
+
class << self
|
12
|
+
alias build new
|
13
|
+
|
14
|
+
def new_with_mixins(*args, mixins, &block)
|
15
|
+
raise ArgumentError, 'mixin list (last paramater) must be an array' unless mixins.is_a? Array
|
16
|
+
|
17
|
+
struct_class = build(*args, &block)
|
18
|
+
struct_class.send(:include, ValueStruct::Immutable)
|
19
|
+
|
20
|
+
mixins.each{ |mixin|
|
21
|
+
if mixin.is_a?(Symbol) # convenient including bundled mixins ala :dup_with_changes
|
22
|
+
mixin = ValueStruct.const_get(mixin.to_s.gsub(/(?:^|_)([a-z])/){ $1.upcase })
|
23
|
+
end
|
24
|
+
struct_class.send(:include, mixin)
|
25
|
+
}
|
26
|
+
|
27
|
+
struct_class
|
28
|
+
end
|
15
29
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
30
|
+
def new(*args, &block)
|
31
|
+
mixins = [ValueStruct::DupWithChanges]
|
32
|
+
mixins.unshift(ValueStruct::ToH) if RUBY_VERSION < "2.0"
|
33
|
+
new_with_mixins(*args, mixins, &block)
|
34
|
+
end
|
21
35
|
end
|
22
36
|
|
23
|
-
def
|
24
|
-
|
25
|
-
mixins.unshift(ValueStruct::ToH) if RUBY_VERSION < "2.0"
|
26
|
-
new_with_mixins(*args, mixins, &block)
|
37
|
+
def inspect
|
38
|
+
super.to_s.sub('struct', 'ValueStruct')
|
27
39
|
end
|
28
40
|
end
|
data/lib/value_struct/version.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
describe ValueStruct::
|
3
|
+
describe ValueStruct::NoClone do
|
4
4
|
subject do
|
5
|
-
ValueStruct.new_with_mixins(:x, :y, [ValueStruct::
|
5
|
+
ValueStruct.new_with_mixins(:x, :y, [ValueStruct::NoClone]).new(1,2)
|
6
6
|
end
|
7
7
|
|
8
8
|
it{ subject.object_id.should == subject.clone.object_id }
|
@@ -0,0 +1,143 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ValueStruct do
|
4
|
+
it "should have a VERSION constant" do
|
5
|
+
ValueStruct.const_get('VERSION').should_not be_empty
|
6
|
+
end
|
7
|
+
|
8
|
+
describe 'struct class initialization and mixins' do
|
9
|
+
subject do
|
10
|
+
ValueStruct
|
11
|
+
end
|
12
|
+
|
13
|
+
describe '.new' do
|
14
|
+
it 'calls .new_with_mixins and adds default mixins' do
|
15
|
+
subject.should_receive(:new_with_mixins).with(
|
16
|
+
:x, :y, [ValueStruct::ToH, ValueStruct::DupWithChanges]
|
17
|
+
)
|
18
|
+
subject.new(:x, :y)
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'adds the ValueStruct::DupWithChanges mixin' do
|
22
|
+
subject.new(:x, :y).new(1,2).should be_a ValueStruct::DupWithChanges
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'adds ValueStruct::ToH if ruby version is below 2.0' do
|
26
|
+
if RUBY_VERSION < "2.0"
|
27
|
+
subject.new(:x, :y).new(1,2).should be_a ValueStruct::ToH
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe '.new_with_mixins' do
|
33
|
+
it 'must be initialized with an array as last parameter' do
|
34
|
+
expect{
|
35
|
+
subject.new_with_mixins(:x, :y)
|
36
|
+
}.to raise_error(ArgumentError)
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'calls .build without last argument' do
|
41
|
+
mock = subject.build(:x, :y)
|
42
|
+
subject.should_receive(:build).with(
|
43
|
+
:x, :y
|
44
|
+
).and_return(mock)
|
45
|
+
subject.new_with_mixins(:x, :y, [])
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'always includes ValueStruct::Immutable in a new struct' do
|
49
|
+
subject.new_with_mixins(:x, :y, []).new(1,2).should be_a ValueStruct::Immutable
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'includes all mixins that are given in the last paramater array' do
|
53
|
+
mixins = [
|
54
|
+
ValueStruct::DupWithChanges,
|
55
|
+
ValueStruct::ToH,
|
56
|
+
ValueStruct::StrictArguments,
|
57
|
+
ValueStruct::Freeze,
|
58
|
+
]
|
59
|
+
res = subject.new_with_mixins(:x, :y, mixins).new(1,2)
|
60
|
+
mixins.each{ |mixin|
|
61
|
+
res.should be_a mixin
|
62
|
+
}
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'converts mixins given as symbols to sub-modules of ValueStruct' do
|
66
|
+
subject.new_with_mixins(
|
67
|
+
:x, :y, [:dup_with_changes]
|
68
|
+
).should include(ValueStruct::DupWithChanges)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
describe '.build' do
|
73
|
+
it 'creates a class that creates value structs' do
|
74
|
+
subject.build(:x, :y).new(1,2).should be_a ValueStruct
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
describe 'instance (value) struct behavior' do
|
80
|
+
subject do
|
81
|
+
Point = ValueStruct.new(:x, :y)
|
82
|
+
Point.new(1,2)
|
83
|
+
end
|
84
|
+
|
85
|
+
it { should be_a Struct }
|
86
|
+
it { should be_a ValueStruct::Immutable }
|
87
|
+
|
88
|
+
it 'stores values accessible by readers' do
|
89
|
+
subject.x.should == 1
|
90
|
+
subject.y.should == 2
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'does not define setters' do
|
94
|
+
expect{ subject.x = 5 }.to raise_error(NoMethodError)
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'does not allow mutatation using []= syntax' do
|
98
|
+
expect{ subject[:x] = 5 }.to raise_error(NoMethodError)
|
99
|
+
end
|
100
|
+
|
101
|
+
it 'can be inherited from to add methods' do
|
102
|
+
class GraphPoint < ValueStruct.new(:x, :y)
|
103
|
+
def inspect
|
104
|
+
"GraphPoint at #{x},#{y}"
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
c = GraphPoint.new(0,0)
|
109
|
+
c.inspect.should == 'GraphPoint at 0,0'
|
110
|
+
end
|
111
|
+
|
112
|
+
describe '#hash and equality' do
|
113
|
+
Y = ValueStruct.new(:x, :y)
|
114
|
+
|
115
|
+
it 'is equal to another value with the same fields' do
|
116
|
+
Point.new(0,0).should == Point.new(0,0)
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'is not equal to an object with a different class' do
|
120
|
+
Point.new(0,0).should_not == Y.new(0,0)
|
121
|
+
end
|
122
|
+
|
123
|
+
it 'is not equal to another value with different fields' do
|
124
|
+
Point.new(0,0).should_not == Point.new(0,1)
|
125
|
+
Point.new(0,0).should_not == Point.new(1,0)
|
126
|
+
end
|
127
|
+
|
128
|
+
it 'has an equal hash if the fields are equal' do
|
129
|
+
p = Point.new(0,0)
|
130
|
+
p.hash.should == Point.new(0,0).hash
|
131
|
+
end
|
132
|
+
|
133
|
+
it 'has a non-equal hash if the fields are different' do
|
134
|
+
p = Point.new(0,0)
|
135
|
+
p.hash.should_not == Point.new(1,0).hash
|
136
|
+
end
|
137
|
+
|
138
|
+
it 'does not have an equal hash if the class is different' do
|
139
|
+
Point.new(0,0).hash.should_not == Y.new(0,0).hash
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: value_struct
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-11-
|
12
|
+
date: 2012-11-04 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|
@@ -191,21 +191,21 @@ files:
|
|
191
191
|
- Rakefile
|
192
192
|
- gemspec.yml
|
193
193
|
- lib/value_struct.rb
|
194
|
-
- lib/value_struct/clone.rb
|
195
|
-
- lib/value_struct/core.rb
|
196
194
|
- lib/value_struct/dup_with_changes.rb
|
197
195
|
- lib/value_struct/freeze.rb
|
196
|
+
- lib/value_struct/immutable.rb
|
197
|
+
- lib/value_struct/no_clone.rb
|
198
198
|
- lib/value_struct/strict_arguments.rb
|
199
199
|
- lib/value_struct/to_h.rb
|
200
200
|
- lib/value_struct/version.rb
|
201
201
|
- spec/benchmark.rb
|
202
|
-
- spec/clone_spec.rb
|
203
202
|
- spec/dup_with_changes_spec.rb
|
204
203
|
- spec/freeze_spec.rb
|
204
|
+
- spec/no_clone_spec.rb
|
205
205
|
- spec/spec_helper.rb
|
206
206
|
- spec/strict_arguments_spec.rb
|
207
|
-
- spec/struct_value_spec.rb
|
208
207
|
- spec/to_h_spec.rb
|
208
|
+
- spec/value_struct_spec.rb
|
209
209
|
- value_struct.gemspec
|
210
210
|
homepage: https://github.com/janlelis/value_struct
|
211
211
|
licenses:
|
data/lib/value_struct/clone.rb
DELETED
data/lib/value_struct/core.rb
DELETED
@@ -1,12 +0,0 @@
|
|
1
|
-
module ValueStruct::Core
|
2
|
-
def inspect
|
3
|
-
super.to_s.sub('struct', 'ValueStruct')
|
4
|
-
end
|
5
|
-
|
6
|
-
def self.included(struct)
|
7
|
-
struct.send(:undef_method, "[]=".to_sym)
|
8
|
-
struct.members.each do |member|
|
9
|
-
struct.send(:undef_method, "#{member}=".to_sym)
|
10
|
-
end
|
11
|
-
end
|
12
|
-
end
|
data/spec/struct_value_spec.rb
DELETED
@@ -1,83 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
describe ValueStruct do
|
4
|
-
describe 'general behavior' do
|
5
|
-
subject do
|
6
|
-
ValueStruct.new(:x, :y)
|
7
|
-
end
|
8
|
-
|
9
|
-
it "should have a VERSION constant" do
|
10
|
-
ValueStruct.const_get('VERSION').should_not be_empty
|
11
|
-
end
|
12
|
-
|
13
|
-
it 'creates Class instances' do
|
14
|
-
should be_instance_of Class
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
describe 'instance' do
|
19
|
-
subject do
|
20
|
-
Point = ValueStruct.new(:x, :y)
|
21
|
-
Point.new(1,2)
|
22
|
-
end
|
23
|
-
|
24
|
-
it { should be_a Struct }
|
25
|
-
it { should be_a ValueStruct::Core }
|
26
|
-
|
27
|
-
it 'stores values accessible by readers' do
|
28
|
-
subject.x.should == 1
|
29
|
-
subject.y.should == 2
|
30
|
-
end
|
31
|
-
|
32
|
-
it 'does not define setters' do
|
33
|
-
expect{ subject.x = 5 }.to raise_error(NoMethodError)
|
34
|
-
end
|
35
|
-
|
36
|
-
it 'does not allow mutatation using []= syntax' do
|
37
|
-
expect{ subject[:x] = 5 }.to raise_error(NoMethodError)
|
38
|
-
end
|
39
|
-
|
40
|
-
it 'can be inherited from to add methods' do
|
41
|
-
class GraphPoint < ValueStruct.new(:x, :y)
|
42
|
-
def inspect
|
43
|
-
"GraphPoint at #{x},#{y}"
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
c = GraphPoint.new(0,0)
|
48
|
-
c.inspect.should == 'GraphPoint at 0,0'
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
describe '#hash and equality' do
|
53
|
-
Y = ValueStruct.new(:x, :y)
|
54
|
-
|
55
|
-
it 'is equal to another value with the same fields' do
|
56
|
-
Point.new(0,0).should == Point.new(0,0)
|
57
|
-
end
|
58
|
-
|
59
|
-
it 'is not equal to an object with a different class' do
|
60
|
-
Point.new(0,0).should_not == Y.new(0,0)
|
61
|
-
end
|
62
|
-
|
63
|
-
it 'is not equal to another value with different fields' do
|
64
|
-
Point.new(0,0).should_not == Point.new(0,1)
|
65
|
-
Point.new(0,0).should_not == Point.new(1,0)
|
66
|
-
end
|
67
|
-
|
68
|
-
it 'has an equal hash if the fields are equal' do
|
69
|
-
p = Point.new(0,0)
|
70
|
-
p.hash.should == Point.new(0,0).hash
|
71
|
-
end
|
72
|
-
|
73
|
-
it 'has a non-equal hash if the fields are different' do
|
74
|
-
p = Point.new(0,0)
|
75
|
-
p.hash.should_not == Point.new(1,0).hash
|
76
|
-
end
|
77
|
-
|
78
|
-
it 'does not have an equal hash if the class is different' do
|
79
|
-
Point.new(0,0).hash.should_not == Y.new(0,0).hash
|
80
|
-
debugger
|
81
|
-
end
|
82
|
-
end
|
83
|
-
end
|