better_struct 0.1.5 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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