Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Import tree_reject from salsify_kafka #1

Merged
merged 5 commits into from
Mar 28, 2019
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@
/pkg/
/spec/reports/
/tmp/
*.iml
5 changes: 1 addition & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,5 @@ All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).

## Unreleased

## 0.1.0 - 2019-03-28
### Added
## 1.0.0 - 2019-03-28
- Initial version
63 changes: 55 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,49 @@
# TreeReject
# tree_reject

Welcome to your new gem! In this directory, you'll find the files you need to be
able to package up your Ruby library into a gem. Put your Ruby code in the file
`lib/tree_reject`. To experiment with that code, run
`bin/console` for an interactive prompt.
[![Build Status](https://travis-ci.org/salsify/tree_reject.svg?branch=master)](https://travis-ci.org/salsify/tree_reject)

TODO: Delete this and the text above, and describe your gem
[![Gem Version](https://badge.fury.io/rb/tree_reject.svg)](https://badge.fury.io/rb/tree_reject)

`tree_reject` is a Ruby gem that removes deeply nested keys from Ruby Hashes or hash-like objects.

For example if you have the Hash:

```ruby
hash = {
a: {
aa: {
aaa: 'aaa',
aab: 'aab'
}
},
b: {
ba: 'ba'
}
}
```

and `tree_reject` the Hash:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The following would more clearly demonstrate how to call the function:

hash.tree_reject({
  a: {
    aa: :aaa
  }
})

```ruby
{
a: {
aa: :aaa
}
}
```

your new hash will be:
```ruby
{
a: {
aa: {
aab: 'aab'
}
},
b: {
ba: 'ba'
}
}
```

## Installation

Expand All @@ -25,7 +63,17 @@ Or install it yourself as:

## Usage

TODO: Write usage instructions here
`tree_reject` extends Ruby's built-in `Hash`:

```ruby
my_hash.tree_reject(ignored_keys)
```

It is also available for objects that support `to_h`.

```ruby
TreeReject.tree_reject(my_hash_like, ignored_keys)
```

## Development

Expand All @@ -48,4 +96,3 @@ https://github.com/salsify/tree_reject.## License

The gem is available as open source under the terms of the
[MIT License](http://opensource.org/licenses/MIT).

53 changes: 52 additions & 1 deletion lib/tree_reject.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,57 @@

require 'tree_reject/version'

# Top-level gem module
module TreeReject
extend self

module TreeRejectHash
def tree_reject(ignorned_keys)
TreeReject.tree_reject(self, ignorned_keys)
end
end

# returns a new map with the ignored keys removed
# ignored_keys must be an array of IKItems
# IKItem is the type <Symbol | Array<IkItem> | Hash<Symbol, IKItem>>
# keys are only removed if their path in the trees match
def tree_reject(map, ignored_keys)
ignored_leaves = extract_leaves(ignored_keys)
ignored_subtrees = extract_subtrees(ignored_keys, ignored_leaves)

map.to_h.each_with_object({}) do |(k, v), cleaned|
if ignored_leaves.include?(k)
next
elsif v.is_a?(Hash) || v.respond_to?(:attributes)
cleaned_v = tree_reject(v.to_h, ignored_subtrees[k])
cleaned[k] = cleaned_v unless cleaned_v == {}
elsif !v.nil?
cleaned[k] = v
end
end
end

private

# extract the top level leaves from the ignored_keys tree structure
def extract_leaves(ignored_keys)
[ignored_keys].flatten.reject { |ignored_key| ignored_key.is_a?(Hash) }
end

# extract the top level subtrees from the ignored_keys tree structure, skipping ones included ignored_leaves
def extract_subtrees(ignored_keys, ignored_leaves)
[ignored_keys].flatten.select { |ignored_key| ignored_key.is_a?(Hash) }.inject({}) do |h, ignored_subtree|
ignored_subtree.each_pair do |k, v|
next if ignored_leaves.include?(k)

if h.key?(k)
h[k] << v
else
h[k] = [v]
end
end
h
end
end
end

Hash.send(:include, TreeReject::TreeRejectHash)
2 changes: 1 addition & 1 deletion lib/tree_reject/version.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# frozen_string_literal: true

module TreeReject
VERSION = '0.1.0'
VERSION = '1.0.0'
end
104 changes: 104 additions & 0 deletions spec/tree_reject_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,108 @@
it "has a version number" do
expect(TreeReject::VERSION).not_to be nil
end

context "TreeReject.tree_reject" do

shared_examples "ignored keys in hash were removed" do
specify "hash as object" do
actual = TreeReject.tree_reject(original, ignored)
expect(actual).to eq expected
end

specify "hash extension" do
actual = original.tree_reject(ignored)
expect(actual).to eq expected
end
end

shared_examples "ignored keys in Virtus-like models were removed" do
specify do
actual = TreeReject.tree_reject(TestModel.new(original), ignored)
expect(actual).to eq expected
end
end

class TestModel < Struct.new(:a, :h, :v)
def initialize(**attributes)
attributes.each do |key, value|
send("#{key}=", value)
end
end

alias_method :to_hash, :to_h
end

context "removes top level key" do
let(:original) { { a: 1 } }
let(:expected) { {} }
let(:ignored) { :a }

it_behaves_like "ignored keys in hash were removed"
it_behaves_like "ignored keys in Virtus-like models were removed"
end

context "removed top level key with children" do
let(:original) { { h: { b: 1 } } }
let(:expected) { {} }
let(:ignored) { :h }

it_behaves_like "ignored keys in hash were removed"
it_behaves_like "ignored keys in Virtus-like models were removed"
end

context "removed child key" do
let(:original) { { h: { b: 1, c: 2 } } }
let(:expected) { { h: { c: 2 } } }
let(:ignored) { { h: :b } }

it_behaves_like "ignored keys in hash were removed"
it_behaves_like "ignored keys in Virtus-like models were removed"
end

context "non-existent top-level key ignored" do
let(:original) { { a: 1 } }
let(:expected) { { a: 1 } }
let(:ignored) { :b }

it_behaves_like "ignored keys in hash were removed"
it_behaves_like "ignored keys in Virtus-like models were removed"
end

context "non-existent child key ignored" do
let(:original) { { a: 1 } }
let(:expected) { { a: 1 } }
let(:ignored) { { b: :c } }

it_behaves_like "ignored keys in hash were removed"
it_behaves_like "ignored keys in Virtus-like models were removed"
end

context "sibling keys removed, array of hashes" do
let(:original) { { h: { b: 1, c: 2, d: 3 } } }
let(:expected) { { h: { d: 3 } } }
let(:ignored) { [{ h: :b }, { h: :c }] }

it_behaves_like "ignored keys in hash were removed"
it_behaves_like "ignored keys in Virtus-like models were removed"
end

context "sibling keys removed, hash of arrays" do
let(:original) { { h: { b: 1, c: 2, d: 3 } } }
let(:expected) { { h: { d: 3 } } }
let(:ignored) { { h: [:b, :c] } }

it_behaves_like "ignored keys in hash were removed"
it_behaves_like "ignored keys in Virtus-like models were removed"
end


context "removed Virtus object child key" do
let(:original) { { v: { a: 1 } } }
let(:expected) { {} }
let(:ignored) { { v: :a } }

it_behaves_like "ignored keys in Virtus-like models were removed"
end
end
end