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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
- metadata.gz: 7bd4c7340e753f2f1a1a45cfa3645974ba42e904
4
- data.tar.gz: 7e5dfdc186cd437b4f7ecb3817347620f9d41180
3
+ metadata.gz: 97a1b4ed42f6ff0493a116d85f4126719ef77483
4
+ data.tar.gz: f074b953c21dbc3f369980fda77cba90a97d4f9c
5
5
  SHA512:
6
- metadata.gz: b5f82d591147bc6982821210e2739dec1b627545bce0d8c2646b42b5fe7ad85149cfad7ae1d40ea7d11ef8b408fc8305f79bead4a8868a7cf0962874f4ebd5b7
7
- data.tar.gz: 5de5763b76b662d79a3a38970a37962d8af2db38d4614fd6a53735f9846465298e2c470718bab23449caa712bcfee3995cf20ab21484963a24f2cd8d478b0dcf
6
+ metadata.gz: 47f5073d2d96c0dafa123fd270fc134f7e5cb8c4db66ed6cd8b87ef54052f5f8213198f515f3b0816f390f60257e9d51790137c2e4d0d47f222826354dbf4ef2
7
+ data.tar.gz: e40dfdbb67e4f26060550cd009da3e9e079e102fbeabeeb2fadeb3ab5bb18e17ea364c65f84ca9a6bb80b5b9699530930ab7a0653f1cbdbe6b5c556461892fa6
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.1
4
+ install: "bundle install"
5
+ script: "ruby -Ilib:test test/better_struct_test.rb"
data/README.md CHANGED
@@ -1,14 +1,29 @@
1
1
  # BetterStruct
2
2
 
3
- Use your data without pain.
4
- It behaves like OpenStruct with monads. Look at the examples below.
3
+ [![Build Status](https://travis-ci.org/exAspArk/better_struct.svg)](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 'better_struct'
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/[my-github-username]/better_struct/fork )
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`)
@@ -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 -z`.split("\x0")
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
- extend Forwardable
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 }<#{ @value.inspect }>"
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.parameterize(ActiveSupport::Inflector.underscore(string), "_")
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 wrap_block_arguments(*args, &block)
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
- result = wrap(value.public_send(*args, &wrap_block_arguments(*args, &block)))
52
-
53
- method_name == "map" ? unwrap_items(result) : result
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(@defined_methods[method_name])
59
+ wrap(defined_methods[method_name.to_s])
56
60
  end
57
61
  end
58
62
 
59
- def unwrap_items(wrapped_array)
60
- array = wrapped_array.value
63
+ def assignment?(method_name, *args, &block)
64
+ method_name[-1] == EQUAL_SIGN && args.size == 1 && block.nil?
65
+ end
61
66
 
62
- if array
63
- array_with_unwrapped_items = array.map { |i| i.is_a?(self.class) ? i.value : i }
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
- wrap(array_with_unwrapped_items)
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
- wrap(nil)
93
+ items
68
94
  end
69
95
  end
70
96
  end
@@ -1,3 +1,3 @@
1
1
  class BetterStruct
2
- VERSION = "0.1.5"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -2,69 +2,98 @@ require "better_struct"
2
2
  require "minitest/autorun"
3
3
  require "pry"
4
4
 
5
- class BetterStructTest < Minitest::Test
6
- def test_maybe_monad
7
- better_struct = BetterStruct.new(nil)
5
+ module BetterStructTest
6
+ class MaybeMonad < Minitest::Test
7
+ def test_maybe_monad
8
+ better_struct = BetterStruct.new(nil)
8
9
 
9
- assert_equal better_struct.some_method, better_struct
10
+ assert better_struct.some_method == better_struct
11
+ end
10
12
  end
11
13
 
12
- def test_result_wrapping
13
- assert_equal BetterStruct.new("foobar")[0..2], BetterStruct.new("foo")
14
- end
14
+ class Wrapping < Minitest::Test
15
+ def test_wrapping
16
+ assert BetterStruct.new("foobar")[0..2] == BetterStruct.new("foo")
17
+ end
15
18
 
16
- def test_getting_value
17
- better_struct = BetterStruct.new("foobar")
19
+ def test_equality
20
+ assert BetterStruct.new(1) == BetterStruct.new(1.0)
21
+ refute BetterStruct.new(1) == 1
22
+ end
18
23
 
19
- assert_equal better_struct.value, "foobar"
20
- end
24
+ def test_getting_value
25
+ better_struct = BetterStruct.new("foobar")
21
26
 
22
- def test_unwrapped_to_s_method
23
- better_struct = BetterStruct.new(1)
27
+ assert better_struct.value == "foobar"
28
+ end
24
29
 
25
- assert_equal better_struct.to_s, "1"
26
- end
30
+ def test_unwrapped_to_s_method
31
+ better_struct = BetterStruct.new(1)
27
32
 
28
- def test_block_argument_wrapping
29
- better_struct = BetterStruct.new([3, 2, 1])
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
- better_struct.each_with_index do |val, i|
32
- assert val.is_a?(BetterStruct)
33
- assert val.value.is_a?(Integer)
34
- assert i.is_a?(BetterStruct)
35
- assert i.value.is_a?(Integer)
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
- def test_hash_methods
40
- better_struct = BetterStruct.new({ "FooBar1" => { foo_bar2: "Hello World!" } })
48
+ class LikeOpenStruct < Minitest::Test
49
+ def test_hash_methods
50
+ better_struct = BetterStruct.new({ "FooBar1" => { foo_bar2: "Hello World!" } })
41
51
 
42
- assert_equal better_struct.foo_bar1.foo_bar2, BetterStruct.new("Hello World!")
43
- end
52
+ assert better_struct.foo_bar1.foo_bar2 == BetterStruct.new("Hello World!")
53
+ end
44
54
 
45
- def test_map_result
46
- better_struct = BetterStruct.new([1, 2, 3])
55
+ def test_map_result_unwrapping
56
+ better_struct = BetterStruct.new([1, 2, 3])
47
57
 
48
- mapped = better_struct.map do |i|
49
- assert i.is_a?(BetterStruct)
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
- mapped.value.each do |i|
54
- assert !i.is_a?(BetterStruct)
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
- def test_equality
59
- assert BetterStruct.new(1) == BetterStruct.new(1.0)
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
- def test_underscoring_methods
64
- assert BetterStruct.new({ "Word (With-Space)" => 1 }).word_with_space.value == 1
65
- end
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
- def test_transliteration
68
- assert BetterStruct.new({ "Título" => 1 }).titulo.value == 1
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.1.5
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-02-26 00:00:00.000000000 Z
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