Last active
November 3, 2016 16:17
-
-
Save a-ayyash/8dcff722dde6fa258ca8d51169aca1fb to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class JSONTrie | |
def initialize() | |
@root = JSONTrieNode.new("", nil) | |
end | |
def add(entities) | |
list_of_entities = entities.split('.') | |
@root.addEntities(list_of_entities) | |
end | |
def read() | |
@root.traverseAndParse | |
end | |
private | |
class JSONTrieNode | |
attr_reader :children | |
def initialize(word, parent) | |
@word = word | |
@children = {} | |
@parent = parent | |
end | |
def traverseAndParse | |
result = [] | |
@children.each_pair do |k, v| | |
if v.children.count == 0 | |
result << v.to_sym_array | |
else | |
result << Hash[k.to_sym, v.readTillEnd] | |
end | |
end | |
result.flatten | |
end | |
def readTillEnd | |
if @children.keys.count > 1 | |
symbolize_items(@children.keys) | |
else | |
node = @children[@word] | |
if node | |
result = @children[@word].readTillEnd | |
if result.empty? | |
return to_sym_array | |
end | |
construct_response_with_result(result) | |
else | |
"" | |
end | |
end | |
end | |
def symbolize_items(list) | |
list.map { |e| e.to_sym } | |
end | |
def to_sym_array | |
[@word.to_sym] | |
end | |
def construct_response_with_result(result) | |
[Hash[@word.to_sym, result]] | |
end | |
def addEntities(entities) | |
if entities.empty? | |
return | |
end | |
@word = entities[0] | |
shorter_list = entities[1..-1] | |
if @children[@word].nil? | |
@children[@word] = JSONTrieNode.new(@word, self) | |
end | |
@children[@word].addEntities(shorter_list) | |
end | |
end | |
end | |
## | |
# The IncludedResourceParams class is responsible for parsing a string containing | |
# a comma separated list of associated resources to include with a request. See | |
# http://jsonapi.org/format/#fetching-includes for additional details although | |
# this is not required knowledge for the task at hand. | |
# | |
# Our API requires specific inclusion of related resourses - that is we do NOT | |
# want to support wildcard inclusion (e.g. `foo.*`) | |
# | |
# The IncludedResourceParams class has three public methods making up its API. | |
# | |
# [included_resources] | |
# returns an array of non-wildcard included elements. | |
# [has_included_resources?] | |
# Returns true if our supplied param has included resources, false otherwise. | |
# [model_includes] | |
# returns an array suitable to supply to ActiveRecord's `includes` method | |
# (http://guides.rubyonrails.org/active_record_querying.html#eager-loading-multiple-associations) | |
# The included_resources should be transformed as specified in the unit tests | |
# included herein. | |
# | |
# All three public methods have unit tests written below that must pass. You are | |
# free to add additional classes/modules as necessary and/or private methods | |
# within the IncludedResourceParams class. | |
# | |
# Feel free to use the Ruby standard libraries available on codepad in your | |
# solution. | |
# | |
# Create your solution as a private fork, and send us the URL. | |
# | |
class IncludedResourceParams | |
def initialize(include_param) | |
@include_param = sanitize_input(include_param) | |
@jsonTrie = JSONTrie.new | |
end | |
## | |
# Does our IncludedResourceParams instance actually have any valid included | |
# resources after parsing? | |
# | |
# @return [Boolean] whether this instance has included resources | |
def has_included_resources? | |
!@include_param.empty? | |
end | |
## | |
# Fetches the included resourcs as an Array containing only non-wildcard | |
# resource specifiers. | |
# | |
# @example nil | |
# IncludedResourceParams.new(nil).included_resources => [] | |
# | |
# @example "foo,foo.bar,baz.*" | |
# IncludedResourceParams.new("foo,bar,baz.*").included_resources => ["foo", "foo.bar"] | |
# | |
# @return [Array] an Array of Strings parsed from the include param with | |
# wildcard includes removed | |
def included_resources | |
@include_param | |
end | |
## | |
# Converts the resources to be included from their JSONAPI representation to | |
# a structure compatible with ActiveRecord's `includes` methods. This can/should | |
# be an Array in all cases. Does not do any verification that the resources | |
# specified for inclusion are actual ActiveRecord classes. | |
# | |
# @example nil | |
# IncludedResourceParams.new(nil).model_includes => [] | |
# | |
# @example "foo" | |
# IncludedResourceParams.new("foo").model_includes => [:foo] | |
# | |
# @see Following unit tests | |
# | |
# @return [Array] an Array of Symbols and/or Hashes compatible with ActiveRecord | |
# `includes` | |
def model_includes | |
@include_param.each {|e| @jsonTrie.add(e)} | |
@jsonTrie.read | |
end | |
private | |
def sanitize_input(input_str) | |
unless input_str.nil? | |
splitted_arr = input_str.split(",") | |
splitted_arr = splitted_arr.map { |str| str unless str.index("*") != nil } | |
splitted_arr.compact | |
else | |
[] | |
end | |
end | |
end | |
require 'test/unit' | |
class TestIncludedResourceParams < Test::Unit::TestCase | |
# Tests for #has_included_resources? | |
def test_has_included_resources_is_false_when_nil | |
r = IncludedResourceParams.new(nil) | |
assert r.has_included_resources? == false | |
end | |
def test_has_included_resources_is_false_when_only_wildcards | |
include_string = 'foo.**' | |
r = IncludedResourceParams.new(include_string) | |
assert r.has_included_resources? == false | |
end | |
def test_has_included_resources_is_true_with_non_wildcard_params | |
include_string = 'foo' | |
r = IncludedResourceParams.new(include_string) | |
assert r.has_included_resources? | |
end | |
def test_has_included_resources_is_true_with_both_wildcard_and_non_params | |
include_string = 'foo,bar.**' | |
r = IncludedResourceParams.new(include_string) | |
assert r.has_included_resources? | |
end | |
# Tests for #included_resources | |
def test_included_resources_always_returns_array | |
r = IncludedResourceParams.new(nil) | |
assert r.included_resources == [] | |
end | |
def test_included_resources_returns_only_non_wildcards | |
r = IncludedResourceParams.new('foo,foo.bar,baz.*,bat.**') | |
assert r.included_resources == ['foo', 'foo.bar'] | |
end | |
# Tests for #model_includes | |
def test_model_includes_when_params_nil | |
assert IncludedResourceParams.new(nil).model_includes == [] | |
end | |
def test_model_includes_one_single_level_resource | |
assert IncludedResourceParams.new('foo').model_includes == [:foo] | |
end | |
def test_model_includes_multiple_single_level_resources | |
assert IncludedResourceParams.new('foo,bar').model_includes == [:foo, :bar] | |
end | |
def test_model_includes_single_two_level_resource | |
assert IncludedResourceParams.new('foo.bar').model_includes == [{:foo => [:bar]}] | |
end | |
def test_model_includes_multiple_two_level_resources | |
assert IncludedResourceParams.new('foo.bar,foo.bat').model_includes == [{:foo => [:bar, :bat]}] | |
assert IncludedResourceParams.new('foo.bar,baz.bat').model_includes == [{:foo => [:bar]}, {:baz => [:bat]}] | |
end | |
def test_model_includes_three_level_resources | |
assert IncludedResourceParams.new('foo.bar.baz').model_includes == [{:foo => [{:bar => [:baz]}]}] | |
end | |
def test_model_includes_multiple_three_level_resources | |
assert IncludedResourceParams.new('foo.bar.baz,foo,foo.bar.bat,bar').model_includes == [{:foo => [{:bar => [:baz, :bat]}]}, :bar] | |
end | |
end | |
class TestJSONTrie < Test::Unit::TestCase | |
attr_reader :trie | |
def setup | |
@trie = JSONTrie.new | |
end | |
def test_only_one_element | |
@trie.add("onlyOneElement") | |
assert @trie.read ==[:onlyOneElement] | |
end | |
def test_shorter_adds_dont_change | |
@trie.add("first.second.third.forth") | |
@trie.add("first") | |
@trie.add("first.second") | |
@trie.add("first.second.third") | |
assert @trie.read ==[{:first=>[{:second=>[{:third=>[:forth]}]}]}] | |
end | |
def test_changing_the_last_aggregates | |
@trie.add("first.second.third.forth") | |
@trie.add("first.second.third.fifth") | |
assert @trie.read == [{:first=>[{:second=>[{:third=>[:forth, :fifth]}]}]}] | |
end | |
def test_two_different_roots | |
@trie.add("first.second.third.forth") | |
@trie.add("A.B.C") | |
assert @trie.read == [{:first=>[{:second=>[{:third=>[:forth]}]}]}, {:A=>[{:B=>[:C]}]}] | |
end | |
def test_two_elements | |
@trie.add("A.B") | |
assert @trie.read == [{:A=>[:B]}] | |
end | |
def test_mix | |
@trie.add("foo.bar.baz") | |
@trie.add("foo") | |
@trie.add("foo.bar.bat") | |
@trie.add("bar") | |
assert @trie.read == [{:foo => [{:bar => [:baz, :bat]}]}, :bar] | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment