hash_sieve 0.0.1

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