value_struct 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,8 @@
1
+ ### 0.7.0
2
+ * Improve mixin system
3
+ * More logical class/model/inheritance logic
4
+ * Rename Clone mixin to NoClone
5
+
1
6
  ### 0.6.0
2
7
  * Fix specs
3
8
  * Fix #dup
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
- This gem also provides the following (optional) mixins, to make life easier when using immutable structs:
7
+ Additionally, this gem provides the following optional mixins to make life easier when using immutable structs:
8
8
 
9
- * __DupWithChanges__ #dup takes a optional hash for setting new values
10
- * __StrictArguments__ Value structs need to be initialized with the exact amount of arguments
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
- By default, only the DupWithChanges and ToH mixins get included.
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. StrictArguments
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
@@ -1,28 +1,40 @@
1
1
  require_relative 'value_struct/version'
2
- require_relative 'value_struct/core'
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/clone'
7
+ require_relative 'value_struct/no_clone'
7
8
  require_relative 'value_struct/freeze'
8
9
 
9
10
  class ValueStruct < Struct
10
- def self.build(*args, &block)
11
- struct_class = Struct.new(*args, &block)
12
- struct_class.send(:include, ValueStruct::Core)
13
- struct_class
14
- end
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
- def self.new_with_mixins(*args, mixins, &block)
17
- raise ArgumentError, 'mixin list (last paramater) must be an array' unless mixins.is_a? Array
18
- struct_class = build(*args, &block)
19
- mixins.each{ |mixin| struct_class.send(:include, mixin) }
20
- struct_class
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 self.new(*args, &block)
24
- mixins = [ValueStruct::DupWithChanges]
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
@@ -0,0 +1,8 @@
1
+ module ValueStruct::Immutable
2
+ def self.included(struct)
3
+ struct.send(:undef_method, :"[]=")
4
+ struct.members.each{ |member|
5
+ struct.send(:undef_method, :"#{member}=")
6
+ }
7
+ end
8
+ end
@@ -0,0 +1,5 @@
1
+ module ValueStruct::NoClone
2
+ def clone
3
+ self
4
+ end
5
+ end
@@ -1,3 +1,3 @@
1
1
  class ValueStruct < Struct
2
- VERSION = '0.6.0'
2
+ VERSION = '0.7.0'
3
3
  end
@@ -1,8 +1,8 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe ValueStruct::Clone do
3
+ describe ValueStruct::NoClone do
4
4
  subject do
5
- ValueStruct.new_with_mixins(:x, :y, [ValueStruct::Clone]).new(1,2)
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.6.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-03 00:00:00.000000000 Z
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:
@@ -1,5 +0,0 @@
1
- module ValueStruct::Clone
2
- def clone
3
- self
4
- end
5
- end
@@ -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
@@ -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