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