Created
November 15, 2016 01:42
-
-
Save eduramirezh/dbf74d84eefe747db376a247c45b9001 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
## | |
# 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 = [] | |
return unless include_param.is_a? String | |
@include_param = include_param.split(',') | |
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? | |
included_resources.any? | |
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.reject { |param| param.include? '*' } | |
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 | |
includes_hash = includes_strings_to_hash(included_resources) | |
result = [] | |
includes_hash.keys.each do |k| | |
parsed_key = parse_key(k, includes_hash[k]) | |
result.push(parsed_key) | |
end | |
result | |
end | |
## | |
# Converts an array of strings using JSONAPI representation to | |
# a standard Ruby hash object | |
# | |
# @example "foo.bar,foo.baz.bat" | |
# includes_strings_to_hash(['foo.bar','foo.baz.bat']) => | |
# {:foo=>{:bar=>{}, :baz=>{:bat=>{}}}} | |
# | |
# @return [Hash] a Hash with symbol keys and hash values | |
def includes_strings_to_hash(includes_strings) | |
model_hash = {} | |
includes_strings.each do |i_s| | |
current = model_hash | |
hierarchy = i_s.split('.').map(&:to_sym) | |
hierarchy.each do |val| | |
current[val] = {} unless current.key? val | |
current = current[val] | |
end | |
end | |
model_hash | |
end | |
## | |
# Constructs an ActiveRecord compatible hash, recursively | |
# | |
# @param [Symbol] key The original key | |
# | |
# @param [Hash] key_hash The original key's value | |
# | |
# @example ":foo, {bar: {}, baz: {bat: {}}" | |
# parse_key(:foo, {bar: {}, baz: {bat: {}}}) => | |
# {:foo=>[:bar, {:baz=>[:bat]}]} | |
# | |
# @return [Symbol, Hash] A symbol if the key's hash is empty, or a Hash. | |
def parse_key(key, key_hash) | |
return key if key_hash.keys.empty? | |
values = [] | |
key_hash.keys.each do |k| | |
this_key = parse_key(k, key_hash[k]) | |
values.push(this_key) | |
end | |
{ key => values } | |
end | |
private :includes_strings_to_hash, :parse_key | |
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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment