hash_initialized_struct 1.0.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.
- checksums.yaml +7 -0
- data/.gitignore +7 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +7 -0
- data/README.md +70 -0
- data/Rakefile +8 -0
- data/hash_initialized_struct.gemspec +24 -0
- data/lib/hash_initialized_struct/version.rb +3 -0
- data/lib/hash_initialized_struct.rb +51 -0
- data/spec/hash_initialized_struct_spec.rb +57 -0
- metadata +98 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 28eb0a537f9077e0834c71e8bc6ca52d2038c67e
|
4
|
+
data.tar.gz: f55ba4273ae166f51743a855fda5f9dcec7fece1
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3d4498296eb6deaa2e57b3bf64629657a24d9db6d108549804012e6e4128f68922cf0ff7b76e72d3124c847d9c37a7fcd725489e4dd9dd22dfcdcf1f9bc5f3f9
|
7
|
+
data.tar.gz: 1d98179af8ca82f5d29c54929f66c4f9110a2079e488f415854aaec6d70720325dfad205a08cfb7e838e35486141cc7a66ad1d9a7fa184bde79a555a7f1f2420
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
Copyright (c) 2015 Rob Howard
|
2
|
+
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
|
4
|
+
|
5
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
6
|
+
|
7
|
+
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
|
data/README.md
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
# Hash-Initialized Struct
|
2
|
+
|
3
|
+
Halfway between Struct and OpenStruct. (Or: Struct, except it takes a Hash on object initialization.)
|
4
|
+
|
5
|
+
Ignore if you already use [Virtus](https://github.com/solnic/virtus) or [Hashie::Dash](https://github.com/intridea/hashie).
|
6
|
+
|
7
|
+
|
8
|
+
## Usage
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
class Point < HashInitializedStruct.new(:x, :y); end
|
12
|
+
# Or: Point = HashInitializedStruct.new(:x, :y)
|
13
|
+
|
14
|
+
point = Point.new(x: 1, y: 2)
|
15
|
+
point.x # => 1
|
16
|
+
|
17
|
+
unknowns = Point.new(x: 1, y: 2, z: 3)
|
18
|
+
# => raises ArgumentError, "Unrecognised keys: :z"
|
19
|
+
|
20
|
+
missing = Point.new(x: 1)
|
21
|
+
# => raises ArgumentError, "Missing keys: :y"
|
22
|
+
```
|
23
|
+
|
24
|
+
### Fancy Usage
|
25
|
+
|
26
|
+
```ruby
|
27
|
+
class Point < HashInitializedStruct.new(:x, :y)
|
28
|
+
def initialize(attrs)
|
29
|
+
super
|
30
|
+
[x, y].each do |attr|
|
31
|
+
raise ArgumentError, "#{attr} must be a number" unless attr.kind_of?(Numeric)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
good = Point.new(x: 1, y: 2)
|
37
|
+
good.x # => 1
|
38
|
+
|
39
|
+
bad = Point.new(x: "1", y: nil)
|
40
|
+
# => raises ArgumentError, "x must ..."
|
41
|
+
```
|
42
|
+
|
43
|
+
|
44
|
+
### Fancier Usage
|
45
|
+
|
46
|
+
Stop. Go use [Virtus](https://github.com/solnic/virtus).
|
47
|
+
|
48
|
+
|
49
|
+
## Installation
|
50
|
+
|
51
|
+
Add the following to your application's Gemfile:
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
gem 'hash_initialized_struct'
|
55
|
+
```
|
56
|
+
|
57
|
+
And then run `bundle install`.
|
58
|
+
|
59
|
+
|
60
|
+
## Contributing
|
61
|
+
|
62
|
+
1. Fork it ( https://github.com/damncabbage/hash_initialized_struct/fork )
|
63
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
64
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
65
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
66
|
+
5. Create a new Pull Request
|
67
|
+
|
68
|
+
## License
|
69
|
+
|
70
|
+
See LICENSE.txt
|
data/Rakefile
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'hash_initialized_struct/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "hash_initialized_struct"
|
8
|
+
spec.version = HashInitializedStruct::VERSION
|
9
|
+
spec.authors = ["Rob Howard"]
|
10
|
+
spec.email = ["rob@robhoward.id.au"]
|
11
|
+
spec.summary = %Q{Struct, except it takes a Hash on object initialization.}
|
12
|
+
spec.description = %Q{Struct, except it takes a Hash on object initialization.\nclass Point < HashInitializedStruct.new(:x, :y); end; Point.new(x: 1, y: 2)}
|
13
|
+
spec.homepage = "https://github.com/damncabbage/hash_initialized_struct"
|
14
|
+
spec.license = "Apache 2.0"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.6"
|
22
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
23
|
+
spec.add_development_dependency "rspec", "~> 3.2"
|
24
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require "hash_initialized_struct/version"
|
2
|
+
|
3
|
+
# class Point < HashInitializedStruct(:x, :y); end
|
4
|
+
# Or: Point = HashInitializedStruct(:x, :y)
|
5
|
+
#
|
6
|
+
# point = Point.new(x: 1, y: 2)
|
7
|
+
# point.x # => 1
|
8
|
+
#
|
9
|
+
# point2 = Point.new(x: 1, y: 2, nonsense: "foobar")
|
10
|
+
# # => raises ArgumentError
|
11
|
+
class HashInitializedStruct
|
12
|
+
class << self
|
13
|
+
alias :subclass_new :new
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.new(*attrs)
|
17
|
+
symbol_attrs = attrs.map do |a|
|
18
|
+
case a
|
19
|
+
when Symbol
|
20
|
+
a
|
21
|
+
else
|
22
|
+
a.to_sym.tap do |sym|
|
23
|
+
raise TypeError, "Could not coerce #{a.inspect} to symbol" unless sym.kind_of?(Symbol)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
Class.new self do
|
29
|
+
const_set :STRUCT_ATTRS, symbol_attrs
|
30
|
+
attr_accessor *symbol_attrs
|
31
|
+
|
32
|
+
def self.new(*args, &block)
|
33
|
+
self.subclass_new(*args, &block)
|
34
|
+
end
|
35
|
+
|
36
|
+
def initialize(attrs)
|
37
|
+
provided = attrs.keys
|
38
|
+
needed = self.class::STRUCT_ATTRS
|
39
|
+
(provided - needed).tap do |unknown|
|
40
|
+
raise ArgumentError, "Unrecognised keys: #{unknown.map(&:inspect).join(', ')}" unless unknown.empty?
|
41
|
+
end
|
42
|
+
(needed - provided).tap do |missing|
|
43
|
+
raise ArgumentError, "Missing keys: #{missing.map(&:inspect).join(', ')}" unless missing.empty?
|
44
|
+
end
|
45
|
+
attrs.each do |attr,value|
|
46
|
+
instance_variable_set("@#{attr}", value)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'hash_initialized_struct'
|
2
|
+
|
3
|
+
describe "HashInitializedStruct" do
|
4
|
+
let(:klass) { HashInitializedStruct.new(:x, :y) }
|
5
|
+
|
6
|
+
it "creates a struct class that accepts initializing values via a Hash" do
|
7
|
+
point = klass.new(x: 1, y: 2)
|
8
|
+
expect(point.x).to eq 1
|
9
|
+
expect(point.y).to eq 2
|
10
|
+
end
|
11
|
+
|
12
|
+
it "creates a struct class that extends from HashInitializedStruct" do
|
13
|
+
expect(klass < HashInitializedStruct).to eq true
|
14
|
+
end
|
15
|
+
|
16
|
+
it "allows mutation of attributes" do
|
17
|
+
point = klass.new(x: 1, y: 2)
|
18
|
+
expect(point.x).to eq 1
|
19
|
+
point.x = 5
|
20
|
+
expect(point.x).to eq 5
|
21
|
+
end
|
22
|
+
|
23
|
+
it "disallows unrecognised keys" do
|
24
|
+
expect {
|
25
|
+
point = klass.new(x: 1, y: 2, z: 3)
|
26
|
+
}.to raise_error(ArgumentError, "Unrecognised keys: :z")
|
27
|
+
expect {
|
28
|
+
point = klass.new(x: 1, y: 2, self => "u wot")
|
29
|
+
}.to raise_error(ArgumentError, /Unrecognised keys: #<RSpec.*>/)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "disallows creation with missing keys" do
|
33
|
+
expect {
|
34
|
+
point = klass.new(x: 1)
|
35
|
+
}.to raise_error(ArgumentError, "Missing keys: :y")
|
36
|
+
end
|
37
|
+
|
38
|
+
it "allows for overriding the constructor to add additional checks" do
|
39
|
+
# Could do this with an anonymous class and define_method, but we're trying to emulate
|
40
|
+
# how it might actually be used.
|
41
|
+
class My3DPoint < HashInitializedStruct.new(:x, :y, :z)
|
42
|
+
def initialize(attrs)
|
43
|
+
super
|
44
|
+
[x, y, z].each do |attr|
|
45
|
+
raise ArgumentError, "#{attr} must be a number" unless attr.kind_of?(Numeric)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
good = My3DPoint.new(x: 1, y: 2, z: 9)
|
51
|
+
expect(good.x).to eq 1
|
52
|
+
|
53
|
+
expect {
|
54
|
+
My3DPoint.new(x: "1", y: nil, z: 9)
|
55
|
+
}.to raise_error(ArgumentError)
|
56
|
+
end
|
57
|
+
end
|
metadata
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: hash_initialized_struct
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Rob Howard
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-02-04 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.6'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.6'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.2'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.2'
|
55
|
+
description: |-
|
56
|
+
Struct, except it takes a Hash on object initialization.
|
57
|
+
class Point < HashInitializedStruct.new(:x, :y); end; Point.new(x: 1, y: 2)
|
58
|
+
email:
|
59
|
+
- rob@robhoward.id.au
|
60
|
+
executables: []
|
61
|
+
extensions: []
|
62
|
+
extra_rdoc_files: []
|
63
|
+
files:
|
64
|
+
- .gitignore
|
65
|
+
- Gemfile
|
66
|
+
- LICENSE.txt
|
67
|
+
- README.md
|
68
|
+
- Rakefile
|
69
|
+
- hash_initialized_struct.gemspec
|
70
|
+
- lib/hash_initialized_struct.rb
|
71
|
+
- lib/hash_initialized_struct/version.rb
|
72
|
+
- spec/hash_initialized_struct_spec.rb
|
73
|
+
homepage: https://github.com/damncabbage/hash_initialized_struct
|
74
|
+
licenses:
|
75
|
+
- Apache 2.0
|
76
|
+
metadata: {}
|
77
|
+
post_install_message:
|
78
|
+
rdoc_options: []
|
79
|
+
require_paths:
|
80
|
+
- lib
|
81
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
82
|
+
requirements:
|
83
|
+
- - '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - '>='
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '0'
|
91
|
+
requirements: []
|
92
|
+
rubyforge_project:
|
93
|
+
rubygems_version: 2.0.14
|
94
|
+
signing_key:
|
95
|
+
specification_version: 4
|
96
|
+
summary: Struct, except it takes a Hash on object initialization.
|
97
|
+
test_files:
|
98
|
+
- spec/hash_initialized_struct_spec.rb
|