hash_sieve 0.0.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 2d32f4ec95c7b09da473fa9993d2ea66b508c97d
4
+ data.tar.gz: 0c06ef99d20431c7e7d13bc60c1c969b5070c1f9
5
+ SHA512:
6
+ metadata.gz: 142608847969ca13b7e51181f5e366dbe83154d1baecdb2cf9220b4b1e58695165e0332c261980a87ca935788660054c26c0136625270da04f228cd832caf538
7
+ data.tar.gz: e10e2aa65b1046d4645d13bec577550c99a290a26fd3d47fce99e237ffe95918231201d898532346422601603189aa7d24620ce0989f3f7dab82530087e09aeb
@@ -0,0 +1,43 @@
1
+ module HashSieve
2
+
3
+ class Sieve
4
+
5
+ def initialize(target_data)
6
+ @attribute_template = HashSieve::TemplateExtractor.extract(target_data)
7
+ end
8
+
9
+ def strain(actual_data)
10
+ strain_value(actual_data, @attribute_template)
11
+ end
12
+
13
+ private
14
+
15
+ def strain_value(value, attribute_template)
16
+ if attribute_template
17
+ class_name = value.class.name.underscore.downcase
18
+ strain_method = "strain_#{class_name}_value".to_sym
19
+ self.respond_to?(strain_method, true) ? self.send(strain_method, value, attribute_template) : value
20
+ else
21
+ nil
22
+ end
23
+ end
24
+
25
+ def strain_array_value(value, attribute_template)
26
+ value.map { |entry| strain_value(entry, attribute_template) }
27
+ end
28
+
29
+ def strain_set_value(value, attribute_template)
30
+ Set.new(strain_array_value(value, attribute_template))
31
+ end
32
+
33
+ def strain_hash_value(value, attribute_template)
34
+ value.reduce({}) do |strained_hash, entry|
35
+ key, value = entry
36
+ strained_hash[key] = strain_value(value, attribute_template[key]) if attribute_template.include?(key)
37
+ strained_hash
38
+ end
39
+ end
40
+
41
+ end
42
+
43
+ end
@@ -0,0 +1,19 @@
1
+ module HashSieve
2
+ module TemplateExtractor
3
+
4
+ class Array
5
+
6
+ def self.extract(array)
7
+ array.reduce({}) do |attributes, element|
8
+ HashSieve::TemplateExtractor.extract(element).each do |candidate_key, candidate_value|
9
+ has_no_candidate_key = attributes[candidate_key].nil?
10
+ attributes[candidate_key] = candidate_value if !candidate_value.empty? || has_no_candidate_key
11
+ end
12
+ attributes
13
+ end
14
+ end
15
+
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,17 @@
1
+ module HashSieve
2
+ module TemplateExtractor
3
+
4
+ class Hash
5
+
6
+ def self.extract(hash)
7
+ hash.reduce({}) do |attributes, entry|
8
+ key, value = entry
9
+ attributes[key] = HashSieve::TemplateExtractor.extract(value)
10
+ attributes
11
+ end
12
+ end
13
+
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1 @@
1
+ HashSieve::TemplateExtractor::Set = HashSieve::TemplateExtractor::Array
@@ -0,0 +1,29 @@
1
+ module HashSieve
2
+
3
+ module TemplateExtractor
4
+
5
+ class << self
6
+
7
+ def extract(data)
8
+ extractor = extractor_for(data)
9
+ extractor ? extractor.extract(data) : {}
10
+ end
11
+
12
+ private
13
+
14
+ def extractor_for(data)
15
+ extractors = class_hierarchy_of(data.class).map do |candidate_class|
16
+ "HashSieve::TemplateExtractor::#{candidate_class.name}".constantize rescue nil
17
+ end
18
+ extractors.empty? ? nil : extractors.first
19
+ end
20
+
21
+ def class_hierarchy_of(a_class)
22
+ a_class ? [ a_class ] + class_hierarchy_of(a_class.superclass) : []
23
+ end
24
+
25
+ end
26
+
27
+ end
28
+
29
+ end
@@ -0,0 +1,3 @@
1
+ module HashSieve
2
+ VERSION = "0.0.1".freeze
3
+ end
data/lib/hash_sieve.rb ADDED
@@ -0,0 +1,15 @@
1
+ require 'active_support/core_ext/string/inflections'
2
+
3
+ require 'hash_sieve/template_extractor/array'
4
+ require 'hash_sieve/template_extractor/set'
5
+ require 'hash_sieve/template_extractor/hash'
6
+ require 'hash_sieve/template_extractor'
7
+ require 'hash_sieve/sieve'
8
+
9
+ module HashSieve
10
+
11
+ def self.strain(actual_data, template_args)
12
+ HashSieve::Sieve.new(template_args[:template]).strain(actual_data)
13
+ end
14
+
15
+ end
@@ -0,0 +1,156 @@
1
+ describe HashSieve::Sieve do
2
+
3
+ let(:target_data) { {} }
4
+
5
+ let(:sieve) { described_class.new(target_data) }
6
+
7
+ describe "constructor" do
8
+
9
+ it "extracts a template of the attributes within the target data" do
10
+ expect(HashSieve::TemplateExtractor).to receive(:extract).with(target_data)
11
+
12
+ sieve
13
+ end
14
+
15
+ end
16
+
17
+ context "#strain" do
18
+
19
+ subject { sieve.strain(actual_data) }
20
+
21
+ before(:each) { allow(HashSieve::TemplateExtractor).to receive(:extract).and_return(attribute_template) }
22
+
23
+ context "when the template data structure has no attributes" do
24
+
25
+ let(:attribute_template) { {} }
26
+
27
+ context "and an array of actual data is provided" do
28
+
29
+ let(:actual_data) { %w{ first second third } }
30
+
31
+ it "returns the actual data" do
32
+ expect(subject).to eql(actual_data)
33
+ end
34
+
35
+ end
36
+
37
+ context "and a set of actual data is provided" do
38
+
39
+ let(:actual_data) { Set.new(%w{ first second third }) }
40
+
41
+ it "returns the actual data" do
42
+ expect(subject).to eql(actual_data)
43
+ end
44
+
45
+ end
46
+
47
+ context "and a hash of actual data is provided" do
48
+
49
+ let(:actual_data) { { key_1: "value_1", key_2: "value_2", key_3: "value_3" } }
50
+
51
+ it "returns an empty hash" do
52
+ expect(subject).to eql({})
53
+ end
54
+
55
+ end
56
+
57
+ [ 1, "two", Object.new ].each do |data|
58
+
59
+ context "and actual data of type #{data.class.name.underscore.downcase} is provided" do
60
+
61
+ let(:actual_data) { data }
62
+
63
+ it "returns the actual data" do
64
+ expect(subject).to eql(actual_data)
65
+ end
66
+
67
+ end
68
+
69
+ end
70
+
71
+ end
72
+
73
+ context "when the target data structure has attributes" do
74
+
75
+ context "that are shallow" do
76
+
77
+ let(:attribute_template) { { :key => {}, :another_key => {} } }
78
+
79
+ context "and a hash of actual data whose attributes exactly match the templated attributes is provided" do
80
+
81
+ let(:actual_data) do
82
+ {
83
+ :key => "value",
84
+ :another_key => "another value"
85
+ }
86
+ end
87
+
88
+ it "returns the actual data" do
89
+ expect(subject).to eql(actual_data)
90
+ end
91
+
92
+ end
93
+
94
+ context "and a hash of data whose attributes are a superset of the templated attributes is provided" do
95
+
96
+ let(:actual_data) do
97
+ {
98
+ :key => "value",
99
+ :another_key => "another value",
100
+ :yet_another_key => "yet another value"
101
+ }
102
+ end
103
+
104
+ it "returns a hash containing only those attributes and values that are templated" do
105
+ expected_data = {
106
+ :key => "value",
107
+ :another_key => "another value"
108
+ }
109
+
110
+ expect(subject).to eql(expected_data)
111
+ end
112
+
113
+ end
114
+
115
+ end
116
+
117
+ context "that are nested" do
118
+
119
+ let(:attribute_template) { { :key => { :nested_key => {}, :another_nested_key => {} } } }
120
+
121
+ context "when the nested types match" do
122
+
123
+ let(:actual_data) do
124
+ {
125
+ :key => { :nested_key => [ 1, 2, 3 ], :another_nested_key => Set.new([ 4, 5, 6 ]) },
126
+ :another_key => "another value"
127
+ }
128
+ end
129
+
130
+ it "returns the templated attributes" do
131
+ expected_data = {
132
+ :key => { :nested_key => [ 1, 2, 3 ], :another_nested_key => Set.new([ 4, 5, 6 ]) }
133
+ }
134
+
135
+ expect(subject).to eql(expected_data)
136
+ end
137
+
138
+ end
139
+
140
+ context "when the nested types do not match" do
141
+
142
+ let(:actual_data) { { :key => "different type" } }
143
+
144
+ it "returns the actual data" do
145
+ expect(subject).to eql(actual_data)
146
+ end
147
+
148
+ end
149
+
150
+ end
151
+
152
+ end
153
+
154
+ end
155
+
156
+ end
@@ -0,0 +1,113 @@
1
+ describe HashSieve::TemplateExtractor::Array do
2
+
3
+ describe "::extract" do
4
+
5
+ let(:array) { [] }
6
+
7
+ subject { described_class.extract(array) }
8
+
9
+ it "returns a hash" do
10
+ expect(subject).to be_a(Hash)
11
+ end
12
+
13
+ context "when provided an array of strings" do
14
+
15
+ let(:array) { %w{ thing_one thing_two thing_three } }
16
+
17
+ it "returns an empty hash" do
18
+ expect(subject.empty?).to be(true)
19
+ end
20
+
21
+ end
22
+
23
+ context "when provided an array of hashes" do
24
+
25
+ context "that have identical attributes" do
26
+
27
+ let(:array) do
28
+ [
29
+ { :id => 1, :name => "Travis" },
30
+ { :id => 2, :name => "Tom" },
31
+ { :id => 3, :name => "Mark" }
32
+ ]
33
+ end
34
+
35
+ it "returns a hash containing all of the attributes" do
36
+ expect(subject).to eql({ :id => {}, :name => {} })
37
+ end
38
+
39
+ end
40
+
41
+ context "that have different attributes" do
42
+
43
+ let(:array) do
44
+ [
45
+ { :id => 1 },
46
+ { :name => "Travis" },
47
+ { :type => "Drummer" }
48
+ ]
49
+ end
50
+
51
+ it "returns a hash combining attributes from all the hashes" do
52
+ expect(subject).to eql({ :id => {}, :name => {}, :type => {} })
53
+ end
54
+
55
+ end
56
+
57
+ context "that contains nested hashes" do
58
+
59
+ let(:array) do
60
+ [
61
+ {
62
+ :id => 1,
63
+ :person =>
64
+ {
65
+ :name => "Travis",
66
+ :type => "Drummer"
67
+ }
68
+ },
69
+ {
70
+ :id => 2,
71
+ :person =>
72
+ {
73
+ :name => "Tom",
74
+ :type => "Guitar"
75
+ }
76
+ },
77
+ {
78
+ :id => 3,
79
+ :person =>
80
+ {
81
+ :name => "Mark",
82
+ :type => "Bass"
83
+ }
84
+ }
85
+ ]
86
+ end
87
+
88
+ it "returns a hash containing hashes with the nested attributes" do
89
+ expect(subject).to eql({ :id => {}, :person => { :name => {}, :type => {} } })
90
+ end
91
+
92
+ end
93
+
94
+ end
95
+
96
+ context "when provided an array of arrays" do
97
+
98
+ let(:array) do
99
+ [
100
+ [ 1, 2, 3 ],
101
+ [ :one, :two, :three ]
102
+ ]
103
+ end
104
+
105
+ it "returns an empty hash" do
106
+ expect(subject).to eql({})
107
+ end
108
+
109
+ end
110
+
111
+ end
112
+
113
+ end
@@ -0,0 +1,69 @@
1
+ describe HashSieve::TemplateExtractor::Hash do
2
+
3
+ describe "::extract" do
4
+
5
+ let(:hash) { {} }
6
+
7
+ subject { described_class.extract(hash) }
8
+
9
+ it "returns a hash" do
10
+ expect(subject).to be_a(Hash)
11
+ end
12
+
13
+ context "when provided a hash with simple attributes" do
14
+
15
+ let(:hash) do
16
+ {
17
+ :id => 1,
18
+ :name => "Peter",
19
+ :type => "Drummer"
20
+ }
21
+ end
22
+
23
+ it "returns a hash containing the attributes" do
24
+ expect(subject).to eql({ :id => {}, :name => {}, :type => {} })
25
+ end
26
+
27
+ end
28
+
29
+ context "when provided a hash with nested hashes" do
30
+
31
+ let(:hash) do
32
+ {
33
+ :id => 1,
34
+ :person =>
35
+ {
36
+ :name => "Travis",
37
+ :type => "Drummer"
38
+ }
39
+ }
40
+ end
41
+
42
+ it "returns a hash containing hashes with nested attributes" do
43
+ expect(subject).to eql({ :id => {}, :person => { :name => {}, :type => {} } })
44
+ end
45
+
46
+ end
47
+
48
+ context "when provided a hash with nested arrays" do
49
+
50
+ let(:hash) do
51
+ {
52
+ :name => "Orchestra",
53
+ :instruments => [
54
+ { :name => "Drum" },
55
+ { :name => "Guitar", type: "Bass" },
56
+ { :name => "Violin" }
57
+ ]
58
+ }
59
+ end
60
+
61
+ it "returns a hash containing a hash with attributes in the nested array" do
62
+ expect(subject).to eql({ :name => {}, :instruments => { :name => {}, :type => {} } })
63
+ end
64
+
65
+ end
66
+
67
+ end
68
+
69
+ end
@@ -0,0 +1,37 @@
1
+ describe HashSieve::TemplateExtractor::Set do
2
+
3
+ it "is identical to the array extractor" do
4
+ expect(described_class).to eql(HashSieve::TemplateExtractor::Array)
5
+ end
6
+
7
+ describe "::extract" do
8
+
9
+ let(:set) { ::Set.new }
10
+
11
+ subject { described_class.extract(set) }
12
+
13
+ it "returns a hash" do
14
+ expect(subject).to be_a(Hash)
15
+ end
16
+
17
+ context "when provided an array of hashes" do
18
+
19
+ let(:set) do
20
+ ::Set.new(
21
+ [
22
+ { :id => 1, :name => "Travis" },
23
+ { :id => 2, :name => "Tom" },
24
+ { :id => 3, :name => "Mark" }
25
+ ]
26
+ )
27
+ end
28
+
29
+ it "returns a hash containing all of the attributes" do
30
+ expect(subject).to eql({ :id => {}, :name => {} })
31
+ end
32
+
33
+ end
34
+
35
+ end
36
+
37
+ end
@@ -0,0 +1,44 @@
1
+ describe HashSieve::TemplateExtractor do
2
+
3
+ describe "::extract" do
4
+
5
+ subject { described_class.extract(data) }
6
+
7
+ { Array => HashSieve::TemplateExtractor::Array,
8
+ Set => HashSieve::TemplateExtractor::Set,
9
+ Hash => HashSieve::TemplateExtractor::Hash }.each do |data_type, extractor|
10
+
11
+ context "when provided data that is a #{data_type.name}" do
12
+
13
+ let(:data) { data_type.new }
14
+
15
+ it "extracts the attribute template using the extractor for that type" do
16
+ expect(extractor).to receive(:extract).with(data)
17
+
18
+ subject
19
+ end
20
+
21
+ it "returns the response from the extractor" do
22
+ extract_response = { :key => "value" }
23
+ allow(extractor).to receive(:extract).and_return(extract_response)
24
+
25
+ expect(subject).to eql(extract_response)
26
+ end
27
+
28
+ end
29
+
30
+ end
31
+
32
+ context "when provided data that is another type" do
33
+
34
+ let(:data) { "another type" }
35
+
36
+ it "returns an empty hash" do
37
+ expect(subject).to eql({})
38
+ end
39
+
40
+ end
41
+
42
+ end
43
+
44
+ end
@@ -0,0 +1,33 @@
1
+ describe HashSieve do
2
+
3
+ describe "::strain" do
4
+
5
+ let(:actual_data) { { actual_key: "value" } }
6
+ let(:strained_data) { { strained_key: "value" } }
7
+ let(:sieve) { instance_double(HashSieve::Sieve, strain: strained_data) }
8
+
9
+ let(:template_data) { { template_key: "value" } }
10
+
11
+ before(:example) { allow(HashSieve::Sieve).to receive(:new).and_return(sieve) }
12
+
13
+ subject { described_class.strain(actual_data, template: template_data) }
14
+
15
+ it "creates a sieve for the template data" do
16
+ expect(HashSieve::Sieve).to receive(:new).with(template_data)
17
+
18
+ subject
19
+ end
20
+
21
+ it "strains the actual data through the sieve" do
22
+ expect(sieve).to receive(:strain).with(actual_data)
23
+
24
+ subject
25
+ end
26
+
27
+ it "returns the strained data" do
28
+ expect(subject).to eql(strained_data)
29
+ end
30
+
31
+ end
32
+
33
+ end
@@ -0,0 +1,12 @@
1
+ require 'bundler'
2
+ Bundler.require(:development)
3
+
4
+ CodeClimate::TestReporter.start
5
+
6
+ SimpleCov.start do
7
+ add_filter "/spec/"
8
+ minimum_coverage 100
9
+ refuse_coverage_drop
10
+ end if ENV["coverage"]
11
+
12
+ require 'hash_sieve'
metadata ADDED
@@ -0,0 +1,164 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hash_sieve
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Andrew King
8
+ - Matthew Ueckerman
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2016-01-07 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rake
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: '10.4'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: '10.4'
28
+ - !ruby/object:Gem::Dependency
29
+ name: activesupport
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: '4.2'
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: '4.2'
42
+ - !ruby/object:Gem::Dependency
43
+ name: travis-lint
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - "~>"
47
+ - !ruby/object:Gem::Version
48
+ version: '2.0'
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - "~>"
54
+ - !ruby/object:Gem::Version
55
+ version: '2.0'
56
+ - !ruby/object:Gem::Dependency
57
+ name: rspec
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - "~>"
61
+ - !ruby/object:Gem::Version
62
+ version: '3.4'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - "~>"
68
+ - !ruby/object:Gem::Version
69
+ version: '3.4'
70
+ - !ruby/object:Gem::Dependency
71
+ name: simplecov
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - "~>"
75
+ - !ruby/object:Gem::Version
76
+ version: '0.11'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - "~>"
82
+ - !ruby/object:Gem::Version
83
+ version: '0.11'
84
+ - !ruby/object:Gem::Dependency
85
+ name: metric_fu
86
+ requirement: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - "~>"
89
+ - !ruby/object:Gem::Version
90
+ version: '4.12'
91
+ type: :development
92
+ prerelease: false
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - "~>"
96
+ - !ruby/object:Gem::Version
97
+ version: '4.12'
98
+ - !ruby/object:Gem::Dependency
99
+ name: codeclimate-test-reporter
100
+ requirement: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - "~>"
103
+ - !ruby/object:Gem::Version
104
+ version: '0.4'
105
+ type: :development
106
+ prerelease: false
107
+ version_requirements: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - "~>"
110
+ - !ruby/object:Gem::Version
111
+ version: '0.4'
112
+ description: Strains unwanted hash attributes from enumerable objects, a template
113
+ object forms the Sieve
114
+ email: andrew.king@myob.com
115
+ executables: []
116
+ extensions: []
117
+ extra_rdoc_files: []
118
+ files:
119
+ - "./lib/hash_sieve.rb"
120
+ - "./lib/hash_sieve/sieve.rb"
121
+ - "./lib/hash_sieve/template_extractor.rb"
122
+ - "./lib/hash_sieve/template_extractor/array.rb"
123
+ - "./lib/hash_sieve/template_extractor/hash.rb"
124
+ - "./lib/hash_sieve/template_extractor/set.rb"
125
+ - "./lib/hash_sieve/version.rb"
126
+ - "./spec/lib/hash_sieve/sieve_spec.rb"
127
+ - "./spec/lib/hash_sieve/template_extractor/array_spec.rb"
128
+ - "./spec/lib/hash_sieve/template_extractor/hash_spec.rb"
129
+ - "./spec/lib/hash_sieve/template_extractor/set_spec.rb"
130
+ - "./spec/lib/hash_sieve/template_extractor_spec.rb"
131
+ - "./spec/lib/hash_sieve_spec.rb"
132
+ - "./spec/spec_helper.rb"
133
+ homepage: http://github.com/MYOB-Technology/hash_sieve
134
+ licenses:
135
+ - MIT
136
+ metadata: {}
137
+ post_install_message:
138
+ rdoc_options: []
139
+ require_paths:
140
+ - lib
141
+ required_ruby_version: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: 1.9.3
146
+ required_rubygems_version: !ruby/object:Gem::Requirement
147
+ requirements:
148
+ - - ">="
149
+ - !ruby/object:Gem::Version
150
+ version: '0'
151
+ requirements: []
152
+ rubyforge_project:
153
+ rubygems_version: 2.4.6
154
+ signing_key:
155
+ specification_version: 4
156
+ summary: Strains unwanted hash attributes from enumerable objects
157
+ test_files:
158
+ - "./spec/lib/hash_sieve/sieve_spec.rb"
159
+ - "./spec/lib/hash_sieve/template_extractor/array_spec.rb"
160
+ - "./spec/lib/hash_sieve/template_extractor/hash_spec.rb"
161
+ - "./spec/lib/hash_sieve/template_extractor/set_spec.rb"
162
+ - "./spec/lib/hash_sieve/template_extractor_spec.rb"
163
+ - "./spec/lib/hash_sieve_spec.rb"
164
+ - "./spec/spec_helper.rb"