value_struct 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,3 @@
1
+ -
2
+ ChangeLog.md
3
+ LICENSE.txt
@@ -0,0 +1,4 @@
1
+ Gemfile.lock
2
+ doc/
3
+ pkg/
4
+ vendor/cache/*.gem
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --colour --format documentation
@@ -0,0 +1 @@
1
+ --markup markdown --title "read_only Documentation" --protected
@@ -0,0 +1,4 @@
1
+ ### 0.1.0 / 2012-11-01
2
+
3
+ * Initial release:
4
+
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source :rubygems
2
+
3
+ gemspec
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Jan Lelis
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,68 @@
1
+ # Value Struct
2
+
3
+ A value struct is a subclass of normal [Ruby struct](http://blog.grayproductions.net/articles/all_about_struct) that behaves almost the same. However, it has a major difference:
4
+
5
+ __Value structs are immutable, i.e. they don't have setters (although, not recursively*)__
6
+
7
+ This gem also provides the following (optional) mixins, to make life easier when using immutable structs:
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)
13
+
14
+ By default, only the DupWithChanges and ToH mixins get included.
15
+
16
+ ## Why?
17
+
18
+ Sometimes you want to eliminate state. See [this blog article] for more information.
19
+
20
+ ## Performance
21
+
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
+ ]
32
+
33
+ ## Example
34
+
35
+ require 'value_struct'
36
+
37
+ Point = ValueStruct.new(:x, :y) do # methods defined in the block will be available in your new value struct class
38
+ # ...
39
+ end
40
+
41
+ Please refer to the [documentation of Ruby's struct] for more details on usage.
42
+
43
+ ## *
44
+
45
+ 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:
46
+
47
+ Ru = ValueStruct.new(:by)
48
+ ruby = Ru.by([1,2,3])
49
+ ruby.by # => [1,2,3]
50
+
51
+ ruby.by = [1,2,3,4] # not possible
52
+ ruby.by << 4 # possible
53
+
54
+ ## Install
55
+
56
+ $ gem install value_struct
57
+
58
+ ## Todo
59
+
60
+ * Make test suite clean and useful
61
+
62
+ ## Influenced by / Thanks to
63
+
64
+ * Ruby Rogues #123
65
+ * Tom Crayford: [Values](https://github.com/tcrayford/Values)
66
+ * https://github.com/iconara/immutable_struct
67
+
68
+ ## J-_-L
@@ -0,0 +1,34 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+
5
+ begin
6
+ require 'bundler'
7
+ rescue LoadError => e
8
+ warn e.message
9
+ warn "Run `gem install bundler` to install Bundler."
10
+ exit -1
11
+ end
12
+
13
+ begin
14
+ Bundler.setup(:development)
15
+ rescue Bundler::BundlerError => e
16
+ warn e.message
17
+ warn "Run `bundle install` to install missing gems."
18
+ exit e.status_code
19
+ end
20
+
21
+ require 'rake'
22
+
23
+ require 'rubygems/tasks'
24
+ Gem::Tasks.new
25
+
26
+ require 'rspec/core/rake_task'
27
+ RSpec::Core::RakeTask.new
28
+
29
+ task :test => :spec
30
+ task :default => :spec
31
+
32
+ require 'yard'
33
+ YARD::Rake::YardocTask.new
34
+ task :doc => :yard
@@ -0,0 +1,18 @@
1
+ name: value_struct
2
+ summary: "Struct-like value objects"
3
+ license: MIT
4
+ authors: Jan Lelis
5
+ email: mail@janlelis.de
6
+ homepage: https://github.com/janlelis/value_struct
7
+
8
+ development_dependencies:
9
+ bundler: ~> 1.0
10
+ rake: ~> 0.8
11
+ rspec: ~> 2.4
12
+ rubygems-tasks: ~> 0.2
13
+ yard: ~> 0.8
14
+ kramdown: ~> 0.14
15
+ debugger: ~> 1.2.1
16
+ values: ~> 1.2.1
17
+ immutable_struct: ~> 1.0.2
18
+ hamster: ~> 0.4.3
@@ -0,0 +1,27 @@
1
+ require_relative 'value_struct/version'
2
+ require_relative 'value_struct/core'
3
+ require_relative 'value_struct/to_h'
4
+ require_relative 'value_struct/dup_with_changes'
5
+ require_relative 'value_struct/strict_arguments'
6
+ require_relative 'value_struct/freeze'
7
+
8
+ class ValueStruct < Struct
9
+ def self.build(*args, &block)
10
+ struct_class = Struct.new(*args, &block)
11
+ struct_class.send(:include, ValueStruct::Core)
12
+ struct_class
13
+ end
14
+
15
+ def self.new_with_mixins(*args, mixins, &block)
16
+ raise ArgumentError, 'mixin list (last paramater) must be an array' unless mixins.is_a? Array
17
+ struct_class = build(*args, &block)
18
+ mixins.each{ |mixin| struct_class.send(:include, mixin) }
19
+ struct_class
20
+ end
21
+
22
+ def self.new(*args, &block)
23
+ mixins = [ValueStruct::DupWithChanges]
24
+ mixins.unshift(ValueStruct::ToH) if RUBY_VERSION < "2.0"
25
+ new_with_mixins(*args, mixins, &block)
26
+ end
27
+ end
@@ -0,0 +1,12 @@
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
@@ -0,0 +1,20 @@
1
+ module ValueStruct::DupWithChanges
2
+ def dup(changes = {})
3
+ case changes
4
+ when {}
5
+ new(values)
6
+ when Hash
7
+ new(
8
+ members.zip(values).map{ |member, value|
9
+ if changes.has_key?(m)
10
+ changes[m]
11
+ else
12
+ value
13
+ end
14
+ }
15
+ )
16
+ else
17
+ raise ArgumentError, 'dup must be given a Hash or nothing'
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,6 @@
1
+ module ValueStruct::Freeze
2
+ def initialize(*args, &block)
3
+ super *args, &block
4
+ freeze
5
+ end
6
+ end
@@ -0,0 +1,8 @@
1
+ module ValueStruct::StrictArguments
2
+ def initialize(*args, &block)
3
+ super *args, &block
4
+ unless size == args.size + (args[0].is_a?(String) ? 1 : 0)
5
+ raise ArgumentError, "wrong number of arguments (#{args.size} for #{members.size})"
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,5 @@
1
+ module ValueStruct::ToH
2
+ def to_h
3
+ Hash[members.zip(values)]
4
+ end
5
+ end
@@ -0,0 +1,3 @@
1
+ class ValueStruct < Struct
2
+ VERSION = '0.5.0'
3
+ end
@@ -0,0 +1,44 @@
1
+ require_relative '../lib/value_struct'
2
+ require 'benchmark'
3
+ require 'immutable_struct'
4
+ require 'values'
5
+
6
+ COUNT = 200_000
7
+
8
+ def benchmark_for(struct_class)
9
+ puts "%20s: %s" % [
10
+ struct_class,
11
+ Benchmark.measure do
12
+ struct = struct_class.new(:a, :b, :c, :d)
13
+ COUNT.times{
14
+ n = struct.new(nil, nil, nil, nil)
15
+ s = struct.new([1,2,3,4], "value", 23432421412, n)
16
+ s.a == s.b
17
+ s.c == s.d
18
+ s == n
19
+ }
20
+ end
21
+ ]
22
+ end
23
+
24
+ def benchmark_for_hash
25
+ puts "%20s: %s" % [
26
+ "[Hash]",
27
+ Benchmark.measure do
28
+ COUNT.times{
29
+ n = { a: nil, b: nil, c: nil, d: nil }
30
+ s = { a: [1,2,3,4], b: "value", c: 23432421412, d: nil }
31
+ s[:a] == s[:b]
32
+ s[:c] == s[:d]
33
+ s == n
34
+ }
35
+ end
36
+ ]
37
+ end
38
+
39
+
40
+ benchmark_for(Struct)
41
+ benchmark_for(Value)
42
+ benchmark_for(ImmutableStruct)
43
+ benchmark_for(ValueStruct)
44
+ benchmark_for_hash
@@ -0,0 +1,2 @@
1
+ require 'rspec'
2
+ require 'value_struct'
@@ -0,0 +1,94 @@
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 'initialization' do
9
+ subject do
10
+ ValueStruct.new(:x, :y)
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 'raises argument errors if not given the right number of arguments' do
41
+ # lambda{
42
+ # Point.new
43
+ # }.should raise_error(ArgumentError, 'wrong number of arguments (0 for 2)')
44
+ # end
45
+
46
+ it 'can be inherited from to add methods' do
47
+ class GraphPoint < ValueStruct.new(:x, :y)
48
+ def inspect
49
+ "GraphPoint at #{x},#{y}"
50
+ end
51
+ end
52
+
53
+ c = GraphPoint.new(0,0)
54
+ c.inspect.should == 'GraphPoint at 0,0'
55
+ end
56
+ end
57
+
58
+ describe '#hash and equality' do
59
+ Y = ValueStruct.new(:x, :y)
60
+
61
+ it 'is equal to another value with the same fields' do
62
+ Point.new(0,0).should == Point.new(0,0)
63
+ end
64
+
65
+ it 'is not equal to an object with a different class' do
66
+ Point.new(0,0).should_not == Y.new(0,0)
67
+ end
68
+
69
+ it 'is not equal to another value with different fields' do
70
+ Point.new(0,0).should_not == Point.new(0,1)
71
+ Point.new(0,0).should_not == Point.new(1,0)
72
+ end
73
+
74
+ it 'has an equal hash if the fields are equal' do
75
+ p = Point.new(0,0)
76
+ p.hash.should == Point.new(0,0).hash
77
+ end
78
+
79
+ it 'has a non-equal hash if the fields are different' do
80
+ p = Point.new(0,0)
81
+ p.hash.should_not == Point.new(1,0).hash
82
+ end
83
+
84
+ it 'does not have an equal hash if the class is different' do
85
+ Point.new(0,0).hash.should_not == Y.new(0,0).hash
86
+ debugger
87
+ end
88
+ end
89
+
90
+ # describe '#clone and #dup return the same object' do
91
+ # it{ subject.object_id.should == subject.clone.object_id }
92
+ # it{ subject.object_id.should == subject.dup.object_id }
93
+ # end
94
+ end
@@ -0,0 +1,60 @@
1
+ # encoding: utf-8
2
+
3
+ require 'yaml'
4
+
5
+ Gem::Specification.new do |gem|
6
+ gemspec = YAML.load_file('gemspec.yml')
7
+
8
+ gem.name = gemspec.fetch('name')
9
+ gem.version = gemspec.fetch('version') do
10
+ lib_dir = File.join(File.dirname(__FILE__),'lib')
11
+ $LOAD_PATH << lib_dir unless $LOAD_PATH.include?(lib_dir)
12
+
13
+ require 'value_struct'
14
+ ValueStruct::VERSION
15
+ end
16
+
17
+ gem.summary = gemspec['summary']
18
+ gem.description = gemspec['description']
19
+ gem.licenses = Array(gemspec['license'])
20
+ gem.authors = Array(gemspec['authors'])
21
+ gem.email = gemspec['email']
22
+ gem.homepage = gemspec['homepage']
23
+
24
+ glob = lambda { |patterns| gem.files & Dir[*patterns] }
25
+
26
+ gem.files = `git ls-files`.split($/)
27
+ gem.files = glob[gemspec['files']] if gemspec['files']
28
+
29
+ gem.executables = gemspec.fetch('executables') do
30
+ glob['bin/*'].map { |path| File.basename(path) }
31
+ end
32
+ gem.default_executable = gem.executables.first if Gem::VERSION < '1.7.'
33
+
34
+ gem.extensions = glob[gemspec['extensions'] || 'ext/**/extconf.rb']
35
+ gem.test_files = glob[gemspec['test_files'] || '{test/{**/}*_test.rb']
36
+ gem.extra_rdoc_files = glob[gemspec['extra_doc_files'] || '*.{txt,md}']
37
+
38
+ gem.require_paths = Array(gemspec.fetch('require_paths') {
39
+ %w[ext lib].select { |dir| File.directory?(dir) }
40
+ })
41
+
42
+ gem.requirements = gemspec['requirements']
43
+ gem.required_ruby_version = gemspec['required_ruby_version']
44
+ gem.required_rubygems_version = gemspec['required_rubygems_version']
45
+ gem.post_install_message = gemspec['post_install_message']
46
+
47
+ split = lambda { |string| string.split(/,\s*/) }
48
+
49
+ if gemspec['dependencies']
50
+ gemspec['dependencies'].each do |name,versions|
51
+ gem.add_dependency(name,split[versions])
52
+ end
53
+ end
54
+
55
+ if gemspec['development_dependencies']
56
+ gemspec['development_dependencies'].each do |name,versions|
57
+ gem.add_development_dependency(name,split[versions])
58
+ end
59
+ end
60
+ end
metadata ADDED
@@ -0,0 +1,229 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: value_struct
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.5.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Jan Lelis
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-11-03 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bundler
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '1.0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '1.0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rake
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: '0.8'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: '0.8'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rspec
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: '2.4'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '2.4'
62
+ - !ruby/object:Gem::Dependency
63
+ name: rubygems-tasks
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ~>
68
+ - !ruby/object:Gem::Version
69
+ version: '0.2'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: '0.2'
78
+ - !ruby/object:Gem::Dependency
79
+ name: yard
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ~>
84
+ - !ruby/object:Gem::Version
85
+ version: '0.8'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ~>
92
+ - !ruby/object:Gem::Version
93
+ version: '0.8'
94
+ - !ruby/object:Gem::Dependency
95
+ name: kramdown
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ~>
100
+ - !ruby/object:Gem::Version
101
+ version: '0.14'
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ~>
108
+ - !ruby/object:Gem::Version
109
+ version: '0.14'
110
+ - !ruby/object:Gem::Dependency
111
+ name: debugger
112
+ requirement: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ~>
116
+ - !ruby/object:Gem::Version
117
+ version: 1.2.1
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ~>
124
+ - !ruby/object:Gem::Version
125
+ version: 1.2.1
126
+ - !ruby/object:Gem::Dependency
127
+ name: values
128
+ requirement: !ruby/object:Gem::Requirement
129
+ none: false
130
+ requirements:
131
+ - - ~>
132
+ - !ruby/object:Gem::Version
133
+ version: 1.2.1
134
+ type: :development
135
+ prerelease: false
136
+ version_requirements: !ruby/object:Gem::Requirement
137
+ none: false
138
+ requirements:
139
+ - - ~>
140
+ - !ruby/object:Gem::Version
141
+ version: 1.2.1
142
+ - !ruby/object:Gem::Dependency
143
+ name: immutable_struct
144
+ requirement: !ruby/object:Gem::Requirement
145
+ none: false
146
+ requirements:
147
+ - - ~>
148
+ - !ruby/object:Gem::Version
149
+ version: 1.0.2
150
+ type: :development
151
+ prerelease: false
152
+ version_requirements: !ruby/object:Gem::Requirement
153
+ none: false
154
+ requirements:
155
+ - - ~>
156
+ - !ruby/object:Gem::Version
157
+ version: 1.0.2
158
+ - !ruby/object:Gem::Dependency
159
+ name: hamster
160
+ requirement: !ruby/object:Gem::Requirement
161
+ none: false
162
+ requirements:
163
+ - - ~>
164
+ - !ruby/object:Gem::Version
165
+ version: 0.4.3
166
+ type: :development
167
+ prerelease: false
168
+ version_requirements: !ruby/object:Gem::Requirement
169
+ none: false
170
+ requirements:
171
+ - - ~>
172
+ - !ruby/object:Gem::Version
173
+ version: 0.4.3
174
+ description: ''
175
+ email: mail@janlelis.de
176
+ executables: []
177
+ extensions: []
178
+ extra_rdoc_files:
179
+ - ChangeLog.md
180
+ - LICENSE.txt
181
+ - README.md
182
+ files:
183
+ - .document
184
+ - .gitignore
185
+ - .rspec
186
+ - .yardopts
187
+ - ChangeLog.md
188
+ - Gemfile
189
+ - LICENSE.txt
190
+ - README.md
191
+ - Rakefile
192
+ - gemspec.yml
193
+ - lib/value_struct.rb
194
+ - lib/value_struct/core.rb
195
+ - lib/value_struct/dup_with_changes.rb
196
+ - lib/value_struct/freeze.rb
197
+ - lib/value_struct/strict_arguments.rb
198
+ - lib/value_struct/to_h.rb
199
+ - lib/value_struct/version.rb
200
+ - spec/benchmark.rb
201
+ - spec/spec_helper.rb
202
+ - spec/struct_value_spec.rb
203
+ - value_struct.gemspec
204
+ homepage: https://github.com/janlelis/value_struct
205
+ licenses:
206
+ - MIT
207
+ post_install_message:
208
+ rdoc_options: []
209
+ require_paths:
210
+ - lib
211
+ required_ruby_version: !ruby/object:Gem::Requirement
212
+ none: false
213
+ requirements:
214
+ - - ! '>='
215
+ - !ruby/object:Gem::Version
216
+ version: '0'
217
+ required_rubygems_version: !ruby/object:Gem::Requirement
218
+ none: false
219
+ requirements:
220
+ - - ! '>='
221
+ - !ruby/object:Gem::Version
222
+ version: '0'
223
+ requirements: []
224
+ rubyforge_project:
225
+ rubygems_version: 1.8.24
226
+ signing_key:
227
+ specification_version: 3
228
+ summary: Struct-like value objects
229
+ test_files: []