hamsterdam 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +5 -0
- data/Gemfile +4 -0
- data/README.md +11 -0
- data/Rakefile +12 -0
- data/hamsterdam.gemspec +24 -0
- data/lib/hamsterdam.rb +86 -0
- data/rake_tasks/rspec.rake +25 -0
- data/spec/hamsterdam_spec.rb +133 -0
- data/spec/spec_helper.rb +26 -0
- data/spec/support/hamsterdam_helpers.rb +3 -0
- metadata +138 -0
data/CHANGELOG
ADDED
@@ -0,0 +1,5 @@
|
|
1
|
+
Hamsterdam v1.0.0
|
2
|
+
* Can define immutable record types via Hamsterdam::Struct.define(*field_names)
|
3
|
+
* Provides getters for all fields, and set_* transformers that return the updated version of the record.
|
4
|
+
* Handy merge function for updating one or more fields by name
|
5
|
+
* Wraps Hamaster's (https://github.com/harukizaemon/hamster) immutable Hamster::Hash class
|
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
# Hamsterdam #
|
2
|
+
|
3
|
+
Immutable Struct-like record structures based on Hamster's (https://github.com/harukizaemon/hamster) immutable Hashes. Convenient methods for updating record structures and returning new immutable instances.
|
4
|
+
|
5
|
+
# Example #
|
6
|
+
|
7
|
+
Person = Hamsterdam::Struct.define(:name, :address, :age)
|
8
|
+
david = Person.new(name: "David", age: true, address: "Coopersville")
|
9
|
+
david1 = david.set_address("East Grand Rapids")
|
10
|
+
david2 = david.merge(name: "Crosby", age: "increased")
|
11
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
require "bundler/gem_tasks"
|
3
|
+
HERE = File.dirname(__FILE__)
|
4
|
+
|
5
|
+
Dir[File.expand_path(HERE) + "/rake_tasks/*.rake"].each do |rake_file|
|
6
|
+
import rake_file
|
7
|
+
end
|
8
|
+
|
9
|
+
# desc 'Default: run specs and cucumber features'
|
10
|
+
# task :default => [ "spec", "cuc:features" ]
|
11
|
+
|
12
|
+
task :default => [ "spec" ]
|
data/hamsterdam.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$LOAD_PATH << File.expand_path(File.dirname(__FILE__) + "/lib")
|
3
|
+
require File.expand_path('../lib/hamsterdam', __FILE__)
|
4
|
+
|
5
|
+
Gem::Specification.new do |gem|
|
6
|
+
gem.authors = ["David Crosby"]
|
7
|
+
gem.email = ["david.crosby@atomicobject.com"]
|
8
|
+
gem.description = %q{Immutable Struct-like record structures based on Hamster.}
|
9
|
+
gem.summary = %q{Immutable Struct-like record structures based on Hamster.}
|
10
|
+
gem.homepage = "https://github.com/atomicobject/hamsterdam"
|
11
|
+
|
12
|
+
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
13
|
+
gem.files = `git ls-files`.split("\n") - [".gitignore", ".rspec", ".rvmrc", "NOTES.txt", "TODO"]
|
14
|
+
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
15
|
+
gem.name = "hamsterdam"
|
16
|
+
gem.require_paths = ["lib"]
|
17
|
+
gem.version = Hamsterdam::VERSION
|
18
|
+
|
19
|
+
gem.add_dependency "hamster"
|
20
|
+
gem.add_development_dependency "rake"
|
21
|
+
gem.add_development_dependency "rspec"
|
22
|
+
gem.add_development_dependency "simplecov"
|
23
|
+
gem.add_development_dependency "pry"
|
24
|
+
end
|
data/lib/hamsterdam.rb
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'hamster'
|
2
|
+
|
3
|
+
module Hamsterdam
|
4
|
+
VERSION = "1.0.0"
|
5
|
+
|
6
|
+
class Struct
|
7
|
+
def self.define(*field_names)
|
8
|
+
struct_class = Class.new(Hamsterdam::Struct) do
|
9
|
+
field_names.each do |fname|
|
10
|
+
define_method fname do
|
11
|
+
return @data[fname]
|
12
|
+
end
|
13
|
+
|
14
|
+
define_method "set_#{fname}" do |value|
|
15
|
+
self.class.new(@data.put(fname, value))
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
struct_class.instance_variable_set(:@field_names, Hamster.set(*field_names))
|
22
|
+
class << struct_class
|
23
|
+
def field_names
|
24
|
+
@field_names
|
25
|
+
end
|
26
|
+
end
|
27
|
+
struct_class
|
28
|
+
end
|
29
|
+
|
30
|
+
def initialize(values=Hamster.hash)
|
31
|
+
@data = flesh_out(ensure_hamster_hash(values))
|
32
|
+
validate_keys(@data)
|
33
|
+
end
|
34
|
+
|
35
|
+
def merge(values)
|
36
|
+
self.class.new(@data.merge(ensure_hamster_hash(values)))
|
37
|
+
end
|
38
|
+
|
39
|
+
def ==(other)
|
40
|
+
@data == other.to_hamster_hash
|
41
|
+
end
|
42
|
+
|
43
|
+
def eql?(other)
|
44
|
+
self.class == other.class && self == other
|
45
|
+
end
|
46
|
+
|
47
|
+
def hash
|
48
|
+
@data.hash
|
49
|
+
end
|
50
|
+
|
51
|
+
def to_hamster_hash
|
52
|
+
@data
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
def validate_keys(data)
|
57
|
+
valid_keys = self.class.field_names
|
58
|
+
bad_keys = data.keys - valid_keys
|
59
|
+
if bad_keys.any?
|
60
|
+
raise "#{self.class.name || "Anonymous Hamsterdam::Struct"} can't be constructed with #{bad_keys.inspect}. Valid keys: #{valid_keys.inspect}"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def ensure_hamster_hash(h)
|
65
|
+
case h
|
66
|
+
when Hash
|
67
|
+
Hamster.hash(h)
|
68
|
+
when Hamster::Hash
|
69
|
+
h
|
70
|
+
else
|
71
|
+
raise "Expected Hash or Hamster::Hash. Do not want: #{h.inspect}"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def flesh_out(data)
|
76
|
+
fnames = self.class.field_names
|
77
|
+
miss = fnames - data.keys
|
78
|
+
if miss.any?
|
79
|
+
return miss.inject(data) { |h,name| h.put(name,nil) }
|
80
|
+
else
|
81
|
+
return data
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
begin
|
2
|
+
require 'rspec/core/rake_task'
|
3
|
+
rescue Exception => e
|
4
|
+
"RSpec is not available. 'spec' task will not be defined."
|
5
|
+
end
|
6
|
+
|
7
|
+
begin # protect from missing rspec
|
8
|
+
desc "Run specs"
|
9
|
+
RSpec::Core::RakeTask.new do |t|
|
10
|
+
if ENV["file"]
|
11
|
+
t.pattern = ENV["file"]
|
12
|
+
end
|
13
|
+
t.rspec_opts = "--color --format documentation" # --tty
|
14
|
+
end
|
15
|
+
|
16
|
+
desc "Run individual spec"
|
17
|
+
task "spec:just" do
|
18
|
+
RSpec::Core::RakeTask.new("_tmp_rspec") do |t|
|
19
|
+
t.pattern = ENV["file"] || raise("Please supply 'file' argument")
|
20
|
+
t.rspec_opts = "--color"
|
21
|
+
end
|
22
|
+
Rake::Task["_tmp_rspec"].invoke
|
23
|
+
end
|
24
|
+
rescue Exception => e
|
25
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + "/spec_helper")
|
2
|
+
|
3
|
+
describe "Hamsterdam structures" do
|
4
|
+
def define_hamsterdam_struct(*field_names)
|
5
|
+
Hamsterdam::Struct.define(*field_names)
|
6
|
+
end
|
7
|
+
describe "Struct.define" do
|
8
|
+
let(:struct_class) { define_hamsterdam_struct(:top, :bottom) }
|
9
|
+
|
10
|
+
it "creates a structure class based on the given fields" do
|
11
|
+
struct = struct_class.new(top: 200, bottom: "all the way down")
|
12
|
+
struct.should be
|
13
|
+
struct.top.should == 200
|
14
|
+
struct.bottom.should == "all the way down"
|
15
|
+
end
|
16
|
+
|
17
|
+
it "can be built with Hamster hashes" do
|
18
|
+
struct = struct_class.new(Hamster.hash(top: 10, bottom: "low"))
|
19
|
+
struct.should be
|
20
|
+
struct.top.should == 10
|
21
|
+
struct.bottom.should == "low"
|
22
|
+
end
|
23
|
+
|
24
|
+
it "does not accept constructor inputs that are not defined" do
|
25
|
+
lambda do struct_class.new(oops: ":)", wups: ":(") end.should raise_error(/oops/)
|
26
|
+
end
|
27
|
+
|
28
|
+
it "allows omission of keys" do
|
29
|
+
struct = struct_class.new(top: 50)
|
30
|
+
struct.top.should == 50
|
31
|
+
struct.bottom.should be_nil
|
32
|
+
end
|
33
|
+
|
34
|
+
it "allows omission of all keys" do
|
35
|
+
struct = struct_class.new
|
36
|
+
struct.top.should be_nil
|
37
|
+
struct.bottom.should be_nil
|
38
|
+
end
|
39
|
+
|
40
|
+
it "provides access to the internal data as a Hamster.hash" do
|
41
|
+
s1 = struct_class.new(top: 50, bottom: 75)
|
42
|
+
s1.to_hamster_hash.should == Hamster.hash(top: 50, bottom: 75)
|
43
|
+
end
|
44
|
+
|
45
|
+
it "raises helpful error when constructed with invalid objects" do
|
46
|
+
lambda do struct_class.new("LAWDY") end.should raise_error /Do not want.*LAWDY/
|
47
|
+
end
|
48
|
+
|
49
|
+
describe "equality" do
|
50
|
+
it "considers two structs equal if they have the same field values" do
|
51
|
+
s1 = struct_class.new(top: 50, bottom: 75)
|
52
|
+
s2 = struct_class.new(top: 50, bottom: 75)
|
53
|
+
s1.eql?(s2).should == true
|
54
|
+
(s1 == s2).should == true
|
55
|
+
s1.should == s2
|
56
|
+
end
|
57
|
+
|
58
|
+
it "considers two structs NOT equal if they have the different field values" do
|
59
|
+
s1 = struct_class.new(top: 50, bottom: 75)
|
60
|
+
s2 = struct_class.new(top: 50, bottom: 74)
|
61
|
+
s3 = struct_class.new(top: 51, bottom: 75)
|
62
|
+
|
63
|
+
s1.eql?(s2).should_not == true
|
64
|
+
(s1 == s2).should_not == true
|
65
|
+
s1.should_not == s2
|
66
|
+
|
67
|
+
s1.eql?(s3).should_not == true
|
68
|
+
(s1 == s3).should_not == true
|
69
|
+
s1.should_not == s3
|
70
|
+
end
|
71
|
+
|
72
|
+
it "doesn't consider to structs eql? unless they are same class" do
|
73
|
+
s1 = struct_class.new(top: 50, bottom: 75)
|
74
|
+
s2 = define_hamsterdam_struct(:top, :bottom).new(top:50, bottom:75)
|
75
|
+
s1.eql?(s2).should == false
|
76
|
+
(s1 == s2).should == true # should still be ==
|
77
|
+
end
|
78
|
+
|
79
|
+
it "considers equal two structs if one has missing keys, and the other has nil values for those keys" do
|
80
|
+
s1 = struct_class.new(top: 50, bottom: nil)
|
81
|
+
s2 = struct_class.new(top: 50)
|
82
|
+
#binding.pry
|
83
|
+
s1.eql?(s2).should == true
|
84
|
+
(s1 == s2).should == true
|
85
|
+
s1.should == s2
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
it "uses the same #hash as Hamster::Hash" do
|
90
|
+
s1 = struct_class.new(top: 50, bottom: 75)
|
91
|
+
s1.hash.should == Hamster.hash(top:50, bottom:75).hash
|
92
|
+
end
|
93
|
+
|
94
|
+
describe "transformation" do
|
95
|
+
it "provides setters for individual fields that return an updated version of the struct" do
|
96
|
+
struct = struct_class.new(top: 10, bottom: 1)
|
97
|
+
struct2 = struct.set_top("woot")
|
98
|
+
struct2.top.should == "woot"
|
99
|
+
struct2.bottom.should == 1
|
100
|
+
|
101
|
+
struct.top.should == 10
|
102
|
+
struct.bottom.should == 1
|
103
|
+
end
|
104
|
+
|
105
|
+
it "provides a merge function" do
|
106
|
+
struct = struct_class.new(top: 10, bottom: 1)
|
107
|
+
|
108
|
+
struct2 = struct.merge(bottom: "new")
|
109
|
+
struct2.top.should == 10
|
110
|
+
struct2.bottom.should == "new"
|
111
|
+
|
112
|
+
struct.top.should == 10
|
113
|
+
struct.bottom.should == 1
|
114
|
+
|
115
|
+
struct3 = struct2.merge(top: "newer", bottom: "very")
|
116
|
+
struct3.top.should == "newer"
|
117
|
+
struct3.bottom.should == "very"
|
118
|
+
end
|
119
|
+
|
120
|
+
it "can merge-in Hamster::Hash" do
|
121
|
+
struct = struct_class.new(top: 10, bottom: 1)
|
122
|
+
|
123
|
+
struct2 = struct.merge(Hamster.hash(bottom: "newer", top: "newest"))
|
124
|
+
struct2.top.should == "newest"
|
125
|
+
struct2.bottom.should == "newer"
|
126
|
+
|
127
|
+
struct.top.should == 10
|
128
|
+
struct.bottom.should == 1
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|
133
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
PROJECT_ROOT = File.expand_path(File.dirname(__FILE__) + "/..")
|
2
|
+
|
3
|
+
$LOAD_PATH << "#{PROJECT_ROOT}/lib"
|
4
|
+
$LOAD_PATH << "#{PROJECT_ROOT}/spec"
|
5
|
+
|
6
|
+
require 'simplecov' # SimpleCov must come first!
|
7
|
+
# This start/config code could alternatively go in .simplecov in project root:
|
8
|
+
SimpleCov.start do
|
9
|
+
add_filter "/spec/"
|
10
|
+
end
|
11
|
+
|
12
|
+
#ENV["APP_ENV"] = "rspec"
|
13
|
+
|
14
|
+
require 'hamsterdam'
|
15
|
+
|
16
|
+
require 'pry'
|
17
|
+
|
18
|
+
# Load all support files
|
19
|
+
Dir["#{PROJECT_ROOT}/spec/support/*.rb"].each do |support|
|
20
|
+
require support
|
21
|
+
end
|
22
|
+
|
23
|
+
RSpec.configure do |config|
|
24
|
+
config.include HamsterdamHelpers
|
25
|
+
end
|
26
|
+
|
metadata
ADDED
@@ -0,0 +1,138 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: hamsterdam
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- David Crosby
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-01-18 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: hamster
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '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'
|
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'
|
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: '0'
|
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: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: simplecov
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
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'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: pry
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
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'
|
94
|
+
description: Immutable Struct-like record structures based on Hamster.
|
95
|
+
email:
|
96
|
+
- david.crosby@atomicobject.com
|
97
|
+
executables: []
|
98
|
+
extensions: []
|
99
|
+
extra_rdoc_files: []
|
100
|
+
files:
|
101
|
+
- CHANGELOG
|
102
|
+
- Gemfile
|
103
|
+
- README.md
|
104
|
+
- Rakefile
|
105
|
+
- hamsterdam.gemspec
|
106
|
+
- lib/hamsterdam.rb
|
107
|
+
- rake_tasks/rspec.rake
|
108
|
+
- spec/hamsterdam_spec.rb
|
109
|
+
- spec/spec_helper.rb
|
110
|
+
- spec/support/hamsterdam_helpers.rb
|
111
|
+
homepage: https://github.com/atomicobject/hamsterdam
|
112
|
+
licenses: []
|
113
|
+
post_install_message:
|
114
|
+
rdoc_options: []
|
115
|
+
require_paths:
|
116
|
+
- lib
|
117
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
118
|
+
none: false
|
119
|
+
requirements:
|
120
|
+
- - ! '>='
|
121
|
+
- !ruby/object:Gem::Version
|
122
|
+
version: '0'
|
123
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
124
|
+
none: false
|
125
|
+
requirements:
|
126
|
+
- - ! '>='
|
127
|
+
- !ruby/object:Gem::Version
|
128
|
+
version: '0'
|
129
|
+
requirements: []
|
130
|
+
rubyforge_project:
|
131
|
+
rubygems_version: 1.8.24
|
132
|
+
signing_key:
|
133
|
+
specification_version: 3
|
134
|
+
summary: Immutable Struct-like record structures based on Hamster.
|
135
|
+
test_files:
|
136
|
+
- spec/hamsterdam_spec.rb
|
137
|
+
- spec/spec_helper.rb
|
138
|
+
- spec/support/hamsterdam_helpers.rb
|