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.
@@ -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