better_struct 0.1.5 → 0.2.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 +4 -4
- data/.travis.yml +5 -0
- data/README.md +38 -4
- data/better_struct.gemspec +3 -1
- data/lib/better_struct.rb +52 -26
- data/lib/better_struct/version.rb +1 -1
- data/test/better_struct_test.rb +72 -43
- metadata +31 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 97a1b4ed42f6ff0493a116d85f4126719ef77483
|
4
|
+
data.tar.gz: f074b953c21dbc3f369980fda77cba90a97d4f9c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 47f5073d2d96c0dafa123fd270fc134f7e5cb8c4db66ed6cd8b87ef54052f5f8213198f515f3b0816f390f60257e9d51790137c2e4d0d47f222826354dbf4ef2
|
7
|
+
data.tar.gz: e40dfdbb67e4f26060550cd009da3e9e079e102fbeabeeb2fadeb3ab5bb18e17ea364c65f84ca9a6bb80b5b9699530930ab7a0653f1cbdbe6b5c556461892fa6
|
data/.travis.yml
ADDED
data/README.md
CHANGED
@@ -1,14 +1,29 @@
|
|
1
1
|
# BetterStruct
|
2
2
|
|
3
|
-
|
4
|
-
|
3
|
+
[](https://travis-ci.org/exAspArk/better_struct)
|
4
|
+
|
5
|
+
**BetterStruct** is a data structure which allows you to use your data without pain.
|
6
|
+
|
7
|
+
It behaves like an OpenStruct on steroids with monad.
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
hash = { "FooBar1" => { foo_bar2: "Hello World!" } }
|
11
|
+
|
12
|
+
# Instead of this:
|
13
|
+
if hash["FooBar1"] && hash["FooBar1"][:foo_bar2]
|
14
|
+
puts hash["FooBar1"][:foo_bar2]
|
15
|
+
end
|
16
|
+
|
17
|
+
# Simply use:
|
18
|
+
puts BetterStruct.new(hash).foo_bar1.foo_bar2.value
|
19
|
+
```
|
5
20
|
|
6
21
|
## Installation
|
7
22
|
|
8
23
|
Add this line to your application's Gemfile:
|
9
24
|
|
10
25
|
```ruby
|
11
|
-
gem
|
26
|
+
gem "better_struct"
|
12
27
|
```
|
13
28
|
|
14
29
|
And then execute:
|
@@ -68,9 +83,28 @@ better_struct.gsub("foo", "super-").value == "super-foo" # => true
|
|
68
83
|
|
69
84
|
$ ruby -Ilib:test test/better_struct_test.rb
|
70
85
|
|
86
|
+
## Benchmarking
|
87
|
+
|
88
|
+
It is as fast as an OpenStruct:
|
89
|
+
|
90
|
+
```
|
91
|
+
$ ruby scripts/benchmark.rb
|
92
|
+
|
93
|
+
Calculating -------------------------------------
|
94
|
+
OpenStruct 6.562k i/100ms
|
95
|
+
BetterStruct 7.155k i/100ms
|
96
|
+
-------------------------------------------------
|
97
|
+
OpenStruct 75.194k (± 7.4%) i/s - 374.034k
|
98
|
+
BetterStruct 76.987k (± 6.7%) i/s - 386.370k
|
99
|
+
|
100
|
+
Comparison:
|
101
|
+
BetterStruct: 76986.7 i/s
|
102
|
+
OpenStruct: 75193.6 i/s - 1.02x slower
|
103
|
+
```
|
104
|
+
|
71
105
|
## Contributing
|
72
106
|
|
73
|
-
1. Fork it ( https://github.com/
|
107
|
+
1. Fork it ( https://github.com/exAspArk/better_struct/fork )
|
74
108
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
75
109
|
3. Commit your changes (`git commit -am 'Add some feature'`)
|
76
110
|
4. Push to the branch (`git push origin my-new-feature`)
|
data/better_struct.gemspec
CHANGED
@@ -13,7 +13,7 @@ Gem::Specification.new do |spec|
|
|
13
13
|
spec.homepage = "https://github.com/exAspArk/better_struct"
|
14
14
|
spec.license = "MIT"
|
15
15
|
|
16
|
-
spec.files = `git ls-files -
|
16
|
+
spec.files = `git ls-files | grep -v script`.split("\n")
|
17
17
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ["lib"]
|
@@ -24,4 +24,6 @@ Gem::Specification.new do |spec|
|
|
24
24
|
spec.add_development_dependency "rake", "~> 10.0"
|
25
25
|
spec.add_development_dependency "minitest", "~> 5.5"
|
26
26
|
spec.add_development_dependency "pry-byebug", "~> 2.0"
|
27
|
+
spec.add_development_dependency "benchmark-ips", "~> 2.1"
|
28
|
+
spec.add_development_dependency "stackprof"
|
27
29
|
end
|
data/lib/better_struct.rb
CHANGED
@@ -1,20 +1,15 @@
|
|
1
1
|
require "better_struct/version"
|
2
|
-
require "forwardable"
|
3
2
|
require "active_support/inflector"
|
4
3
|
|
5
4
|
class BetterStruct
|
6
|
-
|
5
|
+
PARAMETERIZE_SEPARATOR = "_".freeze
|
6
|
+
EQUAL_SIGN = "=".freeze
|
7
|
+
MAP_METHOD_NAMES = %i(map map!).to_set.freeze
|
7
8
|
|
8
9
|
attr_reader :value
|
9
|
-
def_delegator :value, :to_s
|
10
10
|
|
11
|
-
def initialize(value)
|
11
|
+
def initialize(value = nil)
|
12
12
|
@value = value
|
13
|
-
@defined_methods = {}
|
14
|
-
|
15
|
-
if value && value.respond_to?(:each_pair)
|
16
|
-
value.each_pair { |key, value| @defined_methods[methodize(key.to_s)] = value }
|
17
|
-
end
|
18
13
|
end
|
19
14
|
|
20
15
|
def ==(other)
|
@@ -22,49 +17,80 @@ class BetterStruct
|
|
22
17
|
end
|
23
18
|
|
24
19
|
def inspect
|
25
|
-
"#{ self.class }<#{
|
20
|
+
"#{ self.class }<#{ value.inspect }>"
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_s
|
24
|
+
value.to_s
|
26
25
|
end
|
27
26
|
|
28
27
|
private
|
29
28
|
|
30
29
|
def methodize(string)
|
31
|
-
ActiveSupport::Inflector.
|
30
|
+
result = ActiveSupport::Inflector.underscore(string)
|
31
|
+
|
32
|
+
if result =~ /[^\w]/
|
33
|
+
ActiveSupport::Inflector.parameterize(result, PARAMETERIZE_SEPARATOR)
|
34
|
+
else
|
35
|
+
result
|
36
|
+
end
|
32
37
|
end
|
33
38
|
|
34
39
|
def wrap(value)
|
35
40
|
value.is_a?(self.class) ? self : self.class.new(value)
|
36
41
|
end
|
37
42
|
|
38
|
-
def
|
43
|
+
def wrap_block_args(*args, &block)
|
39
44
|
return if block.nil?
|
40
45
|
|
41
46
|
Proc.new do |*args|
|
42
|
-
wrapped_arguments = args.map { |arg| wrap(arg) }
|
47
|
+
wrapped_arguments = args.map! { |arg| wrap(arg) }
|
43
48
|
block.call(*wrapped_arguments)
|
44
49
|
end
|
45
50
|
end
|
46
51
|
|
47
|
-
def method_missing(*args, &block)
|
48
|
-
method_name = args.first.to_s
|
49
|
-
|
52
|
+
def method_missing(method_name, *args, &block)
|
50
53
|
if value.respond_to?(method_name)
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
+
delegate_method(method_name, *args, &block)
|
55
|
+
elsif assignment?(method_name, *args, &block) && defined_methods
|
56
|
+
attribute = methodize(method_name[0...-1])
|
57
|
+
@defined_methods[attribute] = args.first
|
54
58
|
else
|
55
|
-
wrap(
|
59
|
+
wrap(defined_methods[method_name.to_s])
|
56
60
|
end
|
57
61
|
end
|
58
62
|
|
59
|
-
def
|
60
|
-
|
63
|
+
def assignment?(method_name, *args, &block)
|
64
|
+
method_name[-1] == EQUAL_SIGN && args.size == 1 && block.nil?
|
65
|
+
end
|
61
66
|
|
62
|
-
|
63
|
-
|
67
|
+
def defined_methods
|
68
|
+
@defined_methods ||= begin
|
69
|
+
result = {}
|
70
|
+
|
71
|
+
if value && value.respond_to?(:each_pair)
|
72
|
+
value.each_pair { |key, value| result[methodize(key.to_s)] = value }
|
73
|
+
end
|
74
|
+
|
75
|
+
result
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def delegate_method(method_name, *args, &block)
|
80
|
+
result = wrap(value.public_send(method_name, *unwrap_items(args), &wrap_block_args(*args, &block)))
|
81
|
+
|
82
|
+
if MAP_METHOD_NAMES.include?(method_name)
|
83
|
+
wrap(unwrap_items(result.value))
|
84
|
+
else
|
85
|
+
result
|
86
|
+
end
|
87
|
+
end
|
64
88
|
|
65
|
-
|
89
|
+
def unwrap_items(items)
|
90
|
+
if items.is_a?(Array)
|
91
|
+
items.map! { |item| item.is_a?(self.class) ? item.value : item }
|
66
92
|
else
|
67
|
-
|
93
|
+
items
|
68
94
|
end
|
69
95
|
end
|
70
96
|
end
|
data/test/better_struct_test.rb
CHANGED
@@ -2,69 +2,98 @@ require "better_struct"
|
|
2
2
|
require "minitest/autorun"
|
3
3
|
require "pry"
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
5
|
+
module BetterStructTest
|
6
|
+
class MaybeMonad < Minitest::Test
|
7
|
+
def test_maybe_monad
|
8
|
+
better_struct = BetterStruct.new(nil)
|
8
9
|
|
9
|
-
|
10
|
+
assert better_struct.some_method == better_struct
|
11
|
+
end
|
10
12
|
end
|
11
13
|
|
12
|
-
|
13
|
-
|
14
|
-
|
14
|
+
class Wrapping < Minitest::Test
|
15
|
+
def test_wrapping
|
16
|
+
assert BetterStruct.new("foobar")[0..2] == BetterStruct.new("foo")
|
17
|
+
end
|
15
18
|
|
16
|
-
|
17
|
-
|
19
|
+
def test_equality
|
20
|
+
assert BetterStruct.new(1) == BetterStruct.new(1.0)
|
21
|
+
refute BetterStruct.new(1) == 1
|
22
|
+
end
|
18
23
|
|
19
|
-
|
20
|
-
|
24
|
+
def test_getting_value
|
25
|
+
better_struct = BetterStruct.new("foobar")
|
21
26
|
|
22
|
-
|
23
|
-
|
27
|
+
assert better_struct.value == "foobar"
|
28
|
+
end
|
24
29
|
|
25
|
-
|
26
|
-
|
30
|
+
def test_unwrapped_to_s_method
|
31
|
+
better_struct = BetterStruct.new(1)
|
27
32
|
|
28
|
-
|
29
|
-
|
33
|
+
assert better_struct.to_s == "1"
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_block_argument_wrapping
|
37
|
+
better_struct = BetterStruct.new([3, 2, 1])
|
30
38
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
39
|
+
better_struct.each_with_index do |val, i|
|
40
|
+
assert val.is_a?(BetterStruct)
|
41
|
+
assert val.value.is_a?(Integer)
|
42
|
+
assert i.is_a?(BetterStruct)
|
43
|
+
assert i.value.is_a?(Integer)
|
44
|
+
end
|
36
45
|
end
|
37
46
|
end
|
38
47
|
|
39
|
-
|
40
|
-
|
48
|
+
class LikeOpenStruct < Minitest::Test
|
49
|
+
def test_hash_methods
|
50
|
+
better_struct = BetterStruct.new({ "FooBar1" => { foo_bar2: "Hello World!" } })
|
41
51
|
|
42
|
-
|
43
|
-
|
52
|
+
assert better_struct.foo_bar1.foo_bar2 == BetterStruct.new("Hello World!")
|
53
|
+
end
|
44
54
|
|
45
|
-
|
46
|
-
|
55
|
+
def test_map_result_unwrapping
|
56
|
+
better_struct = BetterStruct.new([1, 2, 3])
|
47
57
|
|
48
|
-
|
49
|
-
|
50
|
-
i
|
58
|
+
mapped = better_struct.map { |i| i }
|
59
|
+
|
60
|
+
mapped.value.each { |i| refute i.is_a?(BetterStruct) }
|
51
61
|
end
|
52
62
|
|
53
|
-
|
54
|
-
|
63
|
+
def test_operators
|
64
|
+
better_struct = BetterStruct.new([1, 2, 3])
|
65
|
+
|
66
|
+
assert better_struct.map { |i| i * i }.value == [1, 4, 9]
|
55
67
|
end
|
56
|
-
end
|
57
68
|
|
58
|
-
|
59
|
-
|
60
|
-
assert BetterStruct.new(1) != 1
|
61
|
-
end
|
69
|
+
def test_underscoring_methods
|
70
|
+
better_struct = BetterStruct.new({ "Word (With-Space)" => 1 })
|
62
71
|
|
63
|
-
|
64
|
-
|
65
|
-
|
72
|
+
assert better_struct.word_with_space.value == 1
|
73
|
+
end
|
74
|
+
|
75
|
+
def test_transliteration
|
76
|
+
better_struct = BetterStruct.new({ "Título" => 1 })
|
77
|
+
|
78
|
+
assert better_struct.titulo.value == 1
|
79
|
+
end
|
80
|
+
|
81
|
+
def test_new_assignment
|
82
|
+
better_struct = BetterStruct.new
|
66
83
|
|
67
|
-
|
68
|
-
|
84
|
+
better_struct.head = [1, 2, 3]
|
85
|
+
|
86
|
+
assert better_struct.head == BetterStruct.new([1, 2, 3])
|
87
|
+
end
|
88
|
+
|
89
|
+
def test_input_args_immutability
|
90
|
+
better_struct = BetterStruct.new(head: [1, 2, 3])
|
91
|
+
tail = [4, 5, 6]
|
92
|
+
|
93
|
+
better_struct.head.concat(tail)
|
94
|
+
|
95
|
+
assert better_struct.head == BetterStruct.new([1, 2, 3, 4, 5, 6])
|
96
|
+
assert tail == [4, 5, 6]
|
97
|
+
end
|
69
98
|
end
|
70
99
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: better_struct
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Evgeny Li
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-03-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -80,6 +80,34 @@ dependencies:
|
|
80
80
|
- - ~>
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '2.0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: benchmark-ips
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ~>
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '2.1'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ~>
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '2.1'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: stackprof
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ! '>='
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ! '>='
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
83
111
|
description: Use your data without pain
|
84
112
|
email:
|
85
113
|
- exaspark@gmail.com
|
@@ -87,6 +115,7 @@ executables: []
|
|
87
115
|
extensions: []
|
88
116
|
extra_rdoc_files: []
|
89
117
|
files:
|
118
|
+
- .travis.yml
|
90
119
|
- Gemfile
|
91
120
|
- LICENSE.txt
|
92
121
|
- README.md
|