value_struct 0.5.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.
@@ -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: []