render 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.
- data/.gitignore +20 -0
- data/.rspec +1 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +49 -0
- data/LICENSE.txt +22 -0
- data/extensions/boolean.rb +3 -0
- data/extensions/dottable_hash.rb +113 -0
- data/extensions/enumerable.rb +36 -0
- data/extensions/hash.rb +52 -0
- data/initialize.rb +7 -0
- data/lib/render/attribute.rb +99 -0
- data/lib/render/errors.rb +64 -0
- data/lib/render/generator.rb +14 -0
- data/lib/render/graph.rb +106 -0
- data/lib/render/schema.rb +114 -0
- data/lib/render/version.rb +3 -0
- data/lib/render.rb +42 -0
- data/rakefile.rb +11 -0
- data/readme.md +90 -0
- data/render.gemspec +30 -0
- data/spec/functional/representation/attribute_spec.rb +29 -0
- data/spec/functional/representation/nested_schemas_spec.rb +103 -0
- data/spec/functional/representation/schema_spec.rb +86 -0
- data/spec/integration/nested_graph_spec.rb +77 -0
- data/spec/integration/single_graph_spec.rb +75 -0
- data/spec/schemas/film.json +8 -0
- data/spec/schemas/films.json +13 -0
- data/spec/support/helpers.rb +3 -0
- data/spec/support.rb +7 -0
- data/spec/unit/extensions/boolean_spec.rb +7 -0
- data/spec/unit/render/attribute_spec.rb +128 -0
- data/spec/unit/render/generator_spec.rb +39 -0
- data/spec/unit/render/graph_spec.rb +205 -0
- data/spec/unit/render/schema_spec.rb +250 -0
- data/spec/unit/render_spec.rb +81 -0
- metadata +217 -0
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color -I spec -I . -r support -r initialize
|
data/.ruby-gemset
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
render
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ruby-1.9.3
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
render (0.0.1)
|
5
|
+
uuid (= 2.3.7)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: http://rubygems.org/
|
9
|
+
specs:
|
10
|
+
addressable (2.3.5)
|
11
|
+
columnize (0.3.6)
|
12
|
+
crack (0.4.1)
|
13
|
+
safe_yaml (~> 0.9.0)
|
14
|
+
debugger (1.5.0)
|
15
|
+
columnize (>= 0.3.1)
|
16
|
+
debugger-linecache (~> 1.2.0)
|
17
|
+
debugger-ruby_core_source (~> 1.2.0)
|
18
|
+
debugger-linecache (1.2.0)
|
19
|
+
debugger-ruby_core_source (1.2.3)
|
20
|
+
diff-lcs (1.2.4)
|
21
|
+
macaddr (1.6.1)
|
22
|
+
systemu (~> 2.5.0)
|
23
|
+
rake (10.1.0)
|
24
|
+
rspec (2.14.1)
|
25
|
+
rspec-core (~> 2.14.0)
|
26
|
+
rspec-expectations (~> 2.14.0)
|
27
|
+
rspec-mocks (~> 2.14.0)
|
28
|
+
rspec-core (2.14.4)
|
29
|
+
rspec-expectations (2.14.0)
|
30
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
31
|
+
rspec-mocks (2.14.1)
|
32
|
+
safe_yaml (0.9.4)
|
33
|
+
systemu (2.5.2)
|
34
|
+
uuid (2.3.7)
|
35
|
+
macaddr (~> 1.0)
|
36
|
+
webmock (1.13.0)
|
37
|
+
addressable (>= 2.2.7)
|
38
|
+
crack (>= 0.3.2)
|
39
|
+
|
40
|
+
PLATFORMS
|
41
|
+
ruby
|
42
|
+
|
43
|
+
DEPENDENCIES
|
44
|
+
bundler (~> 1.3)
|
45
|
+
debugger
|
46
|
+
rake
|
47
|
+
render!
|
48
|
+
rspec
|
49
|
+
webmock
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Steve Weber
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
@@ -0,0 +1,113 @@
|
|
1
|
+
class DottableHash < Hash
|
2
|
+
class << self
|
3
|
+
def new(element_to_hash = {})
|
4
|
+
hash = super().merge!(element_to_hash.symbolize_keys)
|
5
|
+
hash.each do |key, value|
|
6
|
+
hash[key] = initialize_element(value)
|
7
|
+
end
|
8
|
+
hash
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize_element(value)
|
12
|
+
case value
|
13
|
+
when Hash
|
14
|
+
new(value)
|
15
|
+
when Array
|
16
|
+
value.collect { |v| initialize_element(v) }
|
17
|
+
else
|
18
|
+
value
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def []=(key, value)
|
24
|
+
key = key.to_sym
|
25
|
+
value = self.class.initialize_element(value)
|
26
|
+
super
|
27
|
+
end
|
28
|
+
|
29
|
+
def [](key)
|
30
|
+
key = key.to_sym
|
31
|
+
super
|
32
|
+
end
|
33
|
+
|
34
|
+
def delete(key)
|
35
|
+
key = key.to_sym
|
36
|
+
super
|
37
|
+
end
|
38
|
+
|
39
|
+
def has_key?(key)
|
40
|
+
super(key.to_sym)
|
41
|
+
end
|
42
|
+
|
43
|
+
def merge!(other_hash)
|
44
|
+
super(other_hash.symbolize_keys)
|
45
|
+
end
|
46
|
+
|
47
|
+
def merge(other_hash)
|
48
|
+
super(other_hash.symbolize_keys)
|
49
|
+
self
|
50
|
+
end
|
51
|
+
|
52
|
+
def method_missing(method, *arguments)
|
53
|
+
if method.match(/\=$/)
|
54
|
+
self[method.to_sym.chop] = arguments.first
|
55
|
+
elsif has_key?(method)
|
56
|
+
self[method]
|
57
|
+
else
|
58
|
+
super
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def fetch(key)
|
63
|
+
key = key.to_sym
|
64
|
+
raise KeyError.new("Invalid key: #{key}") unless has_key?(key)
|
65
|
+
super
|
66
|
+
end
|
67
|
+
|
68
|
+
def fetch_path(full_path)
|
69
|
+
begin
|
70
|
+
fetch_path!(full_path)
|
71
|
+
rescue KeyError
|
72
|
+
nil
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def fetch_path!(full_path)
|
77
|
+
full_path.split(".").inject(self) do |hash, path|
|
78
|
+
raise(KeyError) unless hash.is_a?(Hash)
|
79
|
+
|
80
|
+
hash.fetch(path)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def set_path(full_path, value)
|
85
|
+
self.dup.set_path!(full_path, value)
|
86
|
+
end
|
87
|
+
|
88
|
+
def set_path!(full_path, value)
|
89
|
+
built_hash_for_value = full_path.split(".").reverse.inject({}) do |cumulative, path_to_symource|
|
90
|
+
if cumulative.empty?
|
91
|
+
{ path_to_symource.to_sym => value }
|
92
|
+
else
|
93
|
+
{ path_to_symource.to_sym => cumulative }
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
deep_merge!(built_hash_for_value)
|
98
|
+
end
|
99
|
+
|
100
|
+
protected
|
101
|
+
|
102
|
+
def deep_merge(other_hash)
|
103
|
+
merge(other_hash) do |key, oldval, newval|
|
104
|
+
oldval = oldval.to_hash if oldval.respond_to?(:to_hash)
|
105
|
+
newval = newval.to_hash if newval.respond_to?(:to_hash)
|
106
|
+
oldval.is_a?(Hash) && newval.is_a?(Hash) ? self.class.new(oldval).deep_merge(newval) : self.class.new(newval)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def deep_merge!(other_hash)
|
111
|
+
replace(deep_merge(other_hash))
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
::Enumerable.module_eval do
|
2
|
+
# This is named the same as its Hash counterpart for a reason. I'm not
|
3
|
+
# going to tell you why, consider it my riddle for you.
|
4
|
+
def recursive_symbolize_keys!
|
5
|
+
each do |item|
|
6
|
+
item.recursive_symbolize_keys! if item.respond_to?(:recursive_symbolize_keys!)
|
7
|
+
end
|
8
|
+
self
|
9
|
+
end
|
10
|
+
|
11
|
+
def recursive_stringify_keys!
|
12
|
+
each do |item|
|
13
|
+
item.recursive_stringify_keys! if item.respond_to?(:recursive_stringify_keys!)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def hardcode(enumerable)
|
18
|
+
dup.hardcode!(enumerable)
|
19
|
+
end
|
20
|
+
|
21
|
+
def hardcode!(enumerable)
|
22
|
+
if self.empty? && enumerable.any?
|
23
|
+
enumerable
|
24
|
+
else
|
25
|
+
each_with_index do |a, index|
|
26
|
+
if a.is_a?(Hash)
|
27
|
+
self[index] = a.hardcode(enumerable[index]) if enumerable[index]
|
28
|
+
elsif a.is_a?(Enumerable)
|
29
|
+
self[index] = a.collect { |v| a.hardcode(enumerable[index]) }
|
30
|
+
else
|
31
|
+
self[index] = enumerable[index] if enumerable[index]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/extensions/hash.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
class ::Hash
|
2
|
+
def stringify_keys!
|
3
|
+
keys.each do |key|
|
4
|
+
self[key.to_s] = delete(key)
|
5
|
+
end
|
6
|
+
self
|
7
|
+
end
|
8
|
+
|
9
|
+
def stringify_keys
|
10
|
+
dup.stringify_keys!
|
11
|
+
end
|
12
|
+
|
13
|
+
def recursive_stringify_keys!
|
14
|
+
stringify_keys!
|
15
|
+
values.each do |value|
|
16
|
+
value.recursive_stringify_keys! if value.respond_to?(:recursive_stringify_keys!)
|
17
|
+
end
|
18
|
+
self
|
19
|
+
end
|
20
|
+
|
21
|
+
def symbolize_keys!
|
22
|
+
keys.each do |key|
|
23
|
+
self[(key.to_sym rescue key) || key] = delete(key)
|
24
|
+
end
|
25
|
+
self
|
26
|
+
end
|
27
|
+
|
28
|
+
def recursive_symbolize_keys!
|
29
|
+
symbolize_keys!
|
30
|
+
values.each do |value|
|
31
|
+
value.recursive_symbolize_keys! if value.respond_to?(:recursive_symbolize_keys!)
|
32
|
+
end
|
33
|
+
self
|
34
|
+
end
|
35
|
+
|
36
|
+
def symbolize_keys
|
37
|
+
dup.symbolize_keys!
|
38
|
+
end
|
39
|
+
|
40
|
+
def hardcode(other_hash)
|
41
|
+
dup.hardcode!(other_hash)
|
42
|
+
end
|
43
|
+
|
44
|
+
def hardcode!(other_hash)
|
45
|
+
other_hash.each_pair do |k,v|
|
46
|
+
tv = self[k]
|
47
|
+
self[k] = tv.respond_to?(:hardcode) && v.respond_to?(:hardcode) ? tv.hardcode(v) : v
|
48
|
+
end
|
49
|
+
self
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
data/initialize.rb
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
# An Attribute represents a specific key and value as part of a Schema.
|
2
|
+
# It is responsible for casting its value and generating sample (default) data when not in live-mode.
|
3
|
+
|
4
|
+
require "uuid"
|
5
|
+
|
6
|
+
module Render
|
7
|
+
class Attribute
|
8
|
+
attr_accessor :name, :type, :schema, :archetype, :enums
|
9
|
+
|
10
|
+
# Initialize take a few different Hashes
|
11
|
+
# { name: { type: UUID } } for standard Hashes to be aligned
|
12
|
+
# { type: UUID } for elements in an array to be parsed
|
13
|
+
# { name: { type: Object, attributes { ... } } for nested schemas
|
14
|
+
def initialize(options = {})
|
15
|
+
self.name = options.keys.first
|
16
|
+
if (options.keys.first == :type && !options[options.keys.first].is_a?(Hash)) # todo there has to be a better way to do this
|
17
|
+
initialize_as_archetype(options)
|
18
|
+
else
|
19
|
+
self.type = Render.parse_type(options[name][:type])
|
20
|
+
self.enums = options[name][:enum]
|
21
|
+
initialize_schema!(options) if schema_value?(options)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def initialize_as_archetype(options)
|
26
|
+
self.type = Render.parse_type(options[:type])
|
27
|
+
self.enums = options[:enum]
|
28
|
+
self.archetype = true
|
29
|
+
end
|
30
|
+
|
31
|
+
def initialize_schema!(options)
|
32
|
+
schema_options = {
|
33
|
+
title: name,
|
34
|
+
type: type
|
35
|
+
}
|
36
|
+
|
37
|
+
definition = options[name]
|
38
|
+
if definition.keys.include?(:attributes)
|
39
|
+
schema_options.merge!({ attributes: definition[:attributes] })
|
40
|
+
else
|
41
|
+
schema_options.merge!({ elements: definition[:elements] })
|
42
|
+
end
|
43
|
+
|
44
|
+
self.schema = Schema.new(schema_options)
|
45
|
+
end
|
46
|
+
|
47
|
+
def serialize(explicit_value)
|
48
|
+
if archetype
|
49
|
+
explicit_value || default_value
|
50
|
+
else
|
51
|
+
to_hash(explicit_value)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def to_hash(explicit_value = nil)
|
56
|
+
value = if schema_value?
|
57
|
+
schema.serialize(explicit_value)
|
58
|
+
else
|
59
|
+
# TODO guarantee type for explicit value
|
60
|
+
(explicit_value || default_value)
|
61
|
+
end
|
62
|
+
|
63
|
+
{ name.to_sym => value }
|
64
|
+
end
|
65
|
+
|
66
|
+
def default_value
|
67
|
+
Render.live ? nil : faux_value
|
68
|
+
end
|
69
|
+
|
70
|
+
def schema_value?(options = {})
|
71
|
+
return true if schema
|
72
|
+
options[name].is_a?(Hash) && (options[name][:attributes] || options[name][:elements])
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def faux_value
|
78
|
+
# TODO implement better #faux_value
|
79
|
+
return enums.sample if enums
|
80
|
+
return generator_value if generator_value # todo optimize generator_value call
|
81
|
+
|
82
|
+
case(type.name)
|
83
|
+
when("String") then "A String"
|
84
|
+
when("Integer") then rand(1000)
|
85
|
+
when("UUID") then UUID.generate
|
86
|
+
when("Boolean") then [true, false].sample
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def generator_value
|
91
|
+
generator = Render.generators.detect do |generator|
|
92
|
+
generator.type == type && name.match(generator.matcher)
|
93
|
+
end
|
94
|
+
generator.algorithm.call if generator
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Render
|
2
|
+
module Errors
|
3
|
+
class Generator
|
4
|
+
class MalformedAlgorithm < StandardError
|
5
|
+
attr_accessor :algorithm
|
6
|
+
|
7
|
+
def initialize(algorithm)
|
8
|
+
self.algorithm = algorithm
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_s
|
12
|
+
"Algorithms must respond to #call, which #{algorithm.inspect} does not."
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class Graph
|
18
|
+
class EndpointKeyNotFound < StandardError
|
19
|
+
attr_accessor :config_key
|
20
|
+
|
21
|
+
def initialize(config_key)
|
22
|
+
self.config_key = config_key
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_s
|
26
|
+
"No value for key #{config_key} found in config or parental_attributes."
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class Schema
|
32
|
+
class NotFound < StandardError
|
33
|
+
attr_accessor :title
|
34
|
+
|
35
|
+
def initialize(title)
|
36
|
+
self.title = title
|
37
|
+
end
|
38
|
+
|
39
|
+
def to_s
|
40
|
+
"Schema with title #{title} is not loaded"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class RequestError < StandardError
|
45
|
+
attr_accessor :endpoint, :response
|
46
|
+
|
47
|
+
def initialize(endpoint, response)
|
48
|
+
self.endpoint = endpoint
|
49
|
+
self.response = response
|
50
|
+
end
|
51
|
+
|
52
|
+
def to_s
|
53
|
+
"Could not reach #{endpoint} because #{response}."
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
class InvalidResponse < RequestError
|
58
|
+
def to_s
|
59
|
+
"Could not parse #{response.inspect} from #{endpoint}"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require "render/errors"
|
2
|
+
|
3
|
+
module Render
|
4
|
+
class Generator
|
5
|
+
attr_accessor :type, :matcher, :algorithm
|
6
|
+
|
7
|
+
def initialize(attributes = {})
|
8
|
+
attributes.symbolize_keys!
|
9
|
+
%w(type matcher algorithm).each { |attribute| self.__send__("#{attribute}=", attributes[attribute.to_sym]) }
|
10
|
+
raise Errors::Generator::MalformedAlgorithm.new(algorithm) if !algorithm.respond_to?(:call)
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
end
|
data/lib/render/graph.rb
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
# The Graph is the top-level model that defines a Schema and correlating Graphs.
|
2
|
+
# It also includes particular metadata:
|
3
|
+
# - Endpoint to query for its schema's data
|
4
|
+
# - Config for this endpoint, e.g. an access token
|
5
|
+
# - Relationships between it and a Graph that includes it
|
6
|
+
|
7
|
+
require "render/schema"
|
8
|
+
require "render/errors"
|
9
|
+
|
10
|
+
module Render
|
11
|
+
class Graph
|
12
|
+
PARAM = %r{:(?<param>[\w_]+)}
|
13
|
+
PARAMS = %r{#{PARAM}[\/\;\&]?}
|
14
|
+
|
15
|
+
attr_accessor :schema,
|
16
|
+
:raw_endpoint,
|
17
|
+
:relationships,
|
18
|
+
:graphs,
|
19
|
+
:params,
|
20
|
+
:parental_params, # TODO rename this to inherited_params
|
21
|
+
:config
|
22
|
+
|
23
|
+
def initialize(schema, attributes = {})
|
24
|
+
self.schema = if schema.is_a?(Symbol)
|
25
|
+
Schema.new(schema)
|
26
|
+
else
|
27
|
+
schema
|
28
|
+
end
|
29
|
+
|
30
|
+
self.relationships = (attributes.delete(:relationships) || {})
|
31
|
+
self.raw_endpoint = (attributes.delete(:endpoint) || "")
|
32
|
+
self.graphs = (attributes.delete(:graphs) || [])
|
33
|
+
self.config = attributes
|
34
|
+
self.parental_params = {}
|
35
|
+
|
36
|
+
initialize_params!
|
37
|
+
end
|
38
|
+
|
39
|
+
def endpoint
|
40
|
+
uri = URI(raw_endpoint)
|
41
|
+
|
42
|
+
uri.path.gsub!(PARAMS) do |param|
|
43
|
+
key = param_key(param)
|
44
|
+
param.gsub(PARAM, param_value(key))
|
45
|
+
end
|
46
|
+
|
47
|
+
if uri.query
|
48
|
+
uri.query.gsub!(PARAMS) do |param|
|
49
|
+
key = param_key(param)
|
50
|
+
"#{key}=#{param_value(key)}&"
|
51
|
+
end.chop!
|
52
|
+
end
|
53
|
+
|
54
|
+
uri.to_s
|
55
|
+
end
|
56
|
+
|
57
|
+
def pull(inherited_attributes = {})
|
58
|
+
calculate_parental_params!(inherited_attributes)
|
59
|
+
graph_attributes = schema.pull(inherited_attributes.merge(parental_params.merge({ endpoint: endpoint })))
|
60
|
+
|
61
|
+
graphs.inject(graph_attributes) do |attributes, nested_graph|
|
62
|
+
threads = []
|
63
|
+
# TODO threading should be configured so people may also think about Thread.abort_on_transaction!
|
64
|
+
threads << Thread.new do
|
65
|
+
title = schema.title.to_sym
|
66
|
+
parent_data = attributes[title]
|
67
|
+
nested_graph_data = if parent_data.is_a?(Array)
|
68
|
+
data = parent_data.collect do |element|
|
69
|
+
nested_graph.pull(element)
|
70
|
+
end
|
71
|
+
key = data.first.keys.first
|
72
|
+
attributes[title] = data.collect { |d| d[key] }
|
73
|
+
else
|
74
|
+
data = nested_graph.pull(parent_data)
|
75
|
+
parent_data.merge!(data)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
threads.collect(&:join)
|
79
|
+
DottableHash.new(attributes)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
def initialize_params!
|
86
|
+
self.params = raw_endpoint.scan(PARAMS).flatten.inject({}) do |params, param|
|
87
|
+
params.merge({ param.to_sym => nil })
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def calculate_parental_params!(inherited)
|
92
|
+
self.parental_params = relationships.inject(inherited) do |attributes, (parent_key, child_key)|
|
93
|
+
attributes.merge({ child_key => inherited[parent_key] })
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def param_key(string)
|
98
|
+
string.match(PARAM)[:param].to_sym
|
99
|
+
end
|
100
|
+
|
101
|
+
def param_value(key)
|
102
|
+
parental_params[key] || config[key] || raise(Errors::Graph::EndpointKeyNotFound.new(key))
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
end
|