render 0.0.1 → 0.0.2
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/initialize.rb +0 -2
- data/{extensions → lib/extensions}/boolean.rb +0 -1
- data/lib/extensions/enumerable.rb +16 -0
- data/{extensions → lib/extensions}/hash.rb +0 -13
- data/lib/render.rb +4 -0
- data/lib/render/dottable_hash.rb +114 -0
- data/lib/render/graph.rb +8 -6
- data/lib/render/schema.rb +1 -1
- data/lib/render/version.rb +1 -1
- data/readme.md +19 -60
- data/spec/functional/representation/nested_schemas_spec.rb +2 -2
- data/spec/functional/representation/schema_spec.rb +4 -4
- data/spec/integration/nested_graph_spec.rb +2 -2
- data/spec/integration/single_graph_spec.rb +4 -4
- data/spec/unit/render/dottable_hash_spec.rb +234 -0
- data/spec/unit/render/graph_spec.rb +20 -11
- data/spec/unit/render/schema_spec.rb +6 -6
- metadata +9 -7
- data/extensions/dottable_hash.rb +0 -113
- data/extensions/enumerable.rb +0 -36
data/initialize.rb
CHANGED
@@ -0,0 +1,16 @@
|
|
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
|
+
end
|
@@ -36,17 +36,4 @@ class ::Hash
|
|
36
36
|
def symbolize_keys
|
37
37
|
dup.symbolize_keys!
|
38
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
39
|
end
|
data/lib/render.rb
CHANGED
@@ -3,6 +3,10 @@
|
|
3
3
|
# - Query its endpoint to construct a hash for its Schema
|
4
4
|
# - Add nested Graphs by interpreting/sending data they need
|
5
5
|
|
6
|
+
require "extensions/enumerable"
|
7
|
+
require "extensions/boolean"
|
8
|
+
require "extensions/hash"
|
9
|
+
|
6
10
|
require "render/version"
|
7
11
|
require "render/graph"
|
8
12
|
require "render/generator"
|
@@ -0,0 +1,114 @@
|
|
1
|
+
module Render
|
2
|
+
class DottableHash < Hash
|
3
|
+
class << self
|
4
|
+
def new(element_to_hash = {})
|
5
|
+
hash = super().merge!(element_to_hash.symbolize_keys)
|
6
|
+
hash.each do |key, value|
|
7
|
+
hash[key] = initialize_element(value)
|
8
|
+
end
|
9
|
+
hash
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize_element(value)
|
13
|
+
case value
|
14
|
+
when Hash
|
15
|
+
new(value)
|
16
|
+
when Array
|
17
|
+
value.collect { |v| initialize_element(v) }
|
18
|
+
else
|
19
|
+
value
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def []=(key, value)
|
25
|
+
key = key.to_sym
|
26
|
+
value = self.class.initialize_element(value)
|
27
|
+
super
|
28
|
+
end
|
29
|
+
|
30
|
+
def [](key)
|
31
|
+
key = key.to_sym
|
32
|
+
super
|
33
|
+
end
|
34
|
+
|
35
|
+
def delete(key)
|
36
|
+
key = key.to_sym
|
37
|
+
super
|
38
|
+
end
|
39
|
+
|
40
|
+
def has_key?(key)
|
41
|
+
super(key.to_sym)
|
42
|
+
end
|
43
|
+
|
44
|
+
def merge!(other_hash)
|
45
|
+
super(other_hash.symbolize_keys)
|
46
|
+
end
|
47
|
+
|
48
|
+
def merge(other_hash)
|
49
|
+
super(other_hash.symbolize_keys)
|
50
|
+
end
|
51
|
+
|
52
|
+
def method_missing(method, *arguments)
|
53
|
+
if method.match(/\=$/)
|
54
|
+
self[method.to_s.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_source|
|
90
|
+
if cumulative.empty?
|
91
|
+
{ path_to_source.to_sym => value }
|
92
|
+
else
|
93
|
+
{ path_to_source.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
|
114
|
+
end
|
data/lib/render/graph.rb
CHANGED
@@ -6,6 +6,7 @@
|
|
6
6
|
|
7
7
|
require "render/schema"
|
8
8
|
require "render/errors"
|
9
|
+
require "render/dottable_hash"
|
9
10
|
|
10
11
|
module Render
|
11
12
|
class Graph
|
@@ -54,11 +55,11 @@ module Render
|
|
54
55
|
uri.to_s
|
55
56
|
end
|
56
57
|
|
57
|
-
def
|
58
|
+
def render(inherited_attributes = {})
|
58
59
|
calculate_parental_params!(inherited_attributes)
|
59
|
-
graph_attributes = schema.
|
60
|
+
graph_attributes = schema.render(inherited_attributes.merge(parental_params.merge({ endpoint: endpoint })))
|
60
61
|
|
61
|
-
graphs.inject(graph_attributes) do |attributes, nested_graph|
|
62
|
+
graph = graphs.inject(graph_attributes) do |attributes, nested_graph|
|
62
63
|
threads = []
|
63
64
|
# TODO threading should be configured so people may also think about Thread.abort_on_transaction!
|
64
65
|
threads << Thread.new do
|
@@ -66,18 +67,19 @@ module Render
|
|
66
67
|
parent_data = attributes[title]
|
67
68
|
nested_graph_data = if parent_data.is_a?(Array)
|
68
69
|
data = parent_data.collect do |element|
|
69
|
-
nested_graph.
|
70
|
+
nested_graph.render(element)
|
70
71
|
end
|
71
72
|
key = data.first.keys.first
|
72
73
|
attributes[title] = data.collect { |d| d[key] }
|
73
74
|
else
|
74
|
-
data = nested_graph.
|
75
|
+
data = nested_graph.render(parent_data)
|
75
76
|
parent_data.merge!(data)
|
76
77
|
end
|
77
78
|
end
|
78
79
|
threads.collect(&:join)
|
79
|
-
|
80
|
+
attributes
|
80
81
|
end
|
82
|
+
DottableHash.new(graph)
|
81
83
|
end
|
82
84
|
|
83
85
|
private
|
data/lib/render/schema.rb
CHANGED
data/lib/render/version.rb
CHANGED
data/readme.md
CHANGED
@@ -3,84 +3,43 @@
|
|
3
3
|
Create and test API requests simply with schemas.
|
4
4
|
|
5
5
|
```ruby
|
6
|
-
|
7
|
-
|
6
|
+
Render.load_schemas!("spec/schemas") # JSON schema directory
|
7
|
+
Render::Graph.new(:film, { endpoint: "http://films.local/films" }).render
|
8
8
|
# or stub out schema-specific data
|
9
|
-
|
10
|
-
|
9
|
+
Render.live = false
|
10
|
+
Render::Graph.new(:film).render
|
11
11
|
```
|
12
12
|
|
13
|
-
*Use with caution* (
|
13
|
+
*Use with caution* (Render is under initial development) by updating your Gemfile:
|
14
14
|
|
15
|
-
gem "
|
15
|
+
gem "render"
|
16
16
|
|
17
|
-
##
|
18
|
-
|
19
|
-
Try out examples with `Representation.live = false`.
|
20
|
-
|
21
|
-
*Simple*
|
17
|
+
## Caveats
|
22
18
|
|
23
|
-
|
24
|
-
schema = Representation::Schema.new({
|
25
|
-
title: :film,
|
26
|
-
type: Object,
|
27
|
-
attributes: {
|
28
|
-
id: { type: UUID },
|
29
|
-
title: { type: String }
|
30
|
-
}
|
31
|
-
})
|
32
|
-
|
33
|
-
options = {
|
34
|
-
endpoint: "http://films.local/films/:id"
|
35
|
-
}
|
36
|
-
|
37
|
-
Representation::Graph.new(schema, options).pull({ id: "4cb6b490-d706-0130-2a93-7c6d628f9b06" })
|
38
|
-
```
|
19
|
+
- Render will modify ::Hash and ::Enumerable to provide symbolize/stringify keys methods.
|
39
20
|
|
40
|
-
|
21
|
+
## Usage
|
41
22
|
|
42
|
-
```ruby
|
43
|
-
film_schema = Representation::Schema.new({
|
44
|
-
title: :film,
|
45
|
-
type: Object,
|
46
|
-
attributes: {
|
47
|
-
id: { type: UUID },
|
48
|
-
title: { type: String }
|
49
|
-
}
|
50
|
-
})
|
51
|
-
|
52
|
-
films_schema = Representation::Schema.new({
|
53
|
-
title: :films,
|
54
|
-
type: Array,
|
55
|
-
elements: {
|
56
|
-
title: :film,
|
57
|
-
type: Object,
|
58
|
-
attributes: {
|
59
|
-
id: { type: UUID }
|
60
|
-
}
|
61
|
-
}
|
62
|
-
})
|
63
|
-
|
64
|
-
films_graph = Representation::Graph.new(films_schema, { endpoint: "http://films.local/films" })
|
65
|
-
film_graph = Representation::Graph.new(film_schema, { endpoint: "http://films.local/films/:id", relationships: { id: :id } })
|
66
|
-
films_graph.graphs << film_graph
|
67
|
-
films_graph.pull
|
68
|
-
```
|
69
23
|
*Autoload schemas*
|
70
24
|
|
71
25
|
```ruby
|
72
|
-
|
73
|
-
|
26
|
+
Render.load_schemas!("path/to/json/schemas")
|
27
|
+
Render::Graph.new(:schema_title, { endpoint: "http://films.local/films" }).render
|
74
28
|
```
|
75
29
|
|
76
30
|
*Variable interpolation*
|
77
31
|
|
78
32
|
```ruby
|
79
|
-
|
80
|
-
|
81
|
-
|
33
|
+
api_endpoint = "http://films.local/films/:id?:client_token"
|
34
|
+
env_specific_client_token = "token"
|
35
|
+
|
36
|
+
graph = Render::Graph.new(:schema_title, { endpoint: api_endpoint, client_token: env_specific_client_token })
|
37
|
+
graph.render({ id: "an-id" }) # makes request to "http://films.local/films/an-id?client_token=token"
|
82
38
|
```
|
83
39
|
|
40
|
+
Check out the examples in [integration tests](spec/integration/).
|
41
|
+
|
42
|
+
|
84
43
|
## Contributing
|
85
44
|
|
86
45
|
1. Fork it
|
@@ -32,7 +32,7 @@ module Render
|
|
32
32
|
}
|
33
33
|
}
|
34
34
|
|
35
|
-
Schema.new(schema).
|
35
|
+
Schema.new(schema).render(data).should == {
|
36
36
|
person: {
|
37
37
|
contact: {
|
38
38
|
name: contact_name,
|
@@ -82,7 +82,7 @@ module Render
|
|
82
82
|
}
|
83
83
|
people = [zissou, ned]
|
84
84
|
|
85
|
-
Schema.new(schema).
|
85
|
+
Schema.new(schema).render(people).should == {
|
86
86
|
people: [{
|
87
87
|
name: "Steve Zissou",
|
88
88
|
nicknames: [
|
@@ -18,7 +18,7 @@ module Render
|
|
18
18
|
brand_name = "Sony"
|
19
19
|
response = { brand: brand_name }
|
20
20
|
|
21
|
-
schema.
|
21
|
+
schema.render(response).should == {
|
22
22
|
television: { brand: brand_name }
|
23
23
|
}
|
24
24
|
end
|
@@ -34,7 +34,7 @@ module Render
|
|
34
34
|
|
35
35
|
television_ids = rand(10).times.collect { UUID.generate }
|
36
36
|
|
37
|
-
schema.
|
37
|
+
schema.render(television_ids).should == {
|
38
38
|
televisions: television_ids
|
39
39
|
}
|
40
40
|
end
|
@@ -55,7 +55,7 @@ module Render
|
|
55
55
|
brand_1, brand_2 = *%w(Sony Samsung)
|
56
56
|
response = [{ brand: brand_1 }, { brand: brand_2 }]
|
57
57
|
|
58
|
-
schema.
|
58
|
+
schema.render(response).should == {
|
59
59
|
televisions: [{ brand: brand_1 }, { brand: brand_2 }]
|
60
60
|
}
|
61
61
|
end
|
@@ -78,7 +78,7 @@ module Render
|
|
78
78
|
brand_name = "Sony"
|
79
79
|
response = { brand: { name: brand_name } }
|
80
80
|
|
81
|
-
schema.
|
81
|
+
schema.render(response).should == {
|
82
82
|
television: { brand: { name: brand_name } }
|
83
83
|
}
|
84
84
|
end
|
@@ -33,7 +33,7 @@ describe Render do
|
|
33
33
|
endpoint: @films_endpoint
|
34
34
|
}
|
35
35
|
graph = Render::Graph.new(:films, options)
|
36
|
-
graph.
|
36
|
+
graph.render.should == {
|
37
37
|
films: [
|
38
38
|
{ name: aquatic_name, year: nil },
|
39
39
|
{ name: darjeeling_name, year: nil }
|
@@ -70,7 +70,7 @@ describe Render do
|
|
70
70
|
|
71
71
|
films = Representation::Graph.new(films, { endpoint: @films_endpoint })
|
72
72
|
films.graphs << Representation::Graph.new(film, { endpoint: @film_endpoint, relationships: { films: :id } })
|
73
|
-
films.
|
73
|
+
films.render.should == {}
|
74
74
|
end
|
75
75
|
|
76
76
|
end
|
@@ -25,7 +25,7 @@ describe Render do
|
|
25
25
|
stub_request(:get, aquatic_uri).to_return({ body: [{ id: @film_id }].to_json })
|
26
26
|
|
27
27
|
graph = Render::Graph.new(:films, { endpoint: @films_endpoint, secret_code: @secret_code })
|
28
|
-
graph.
|
28
|
+
graph.render.should == { films: [{ id: @film_id }] }
|
29
29
|
end
|
30
30
|
|
31
31
|
it "returns structured data for specific resources" do
|
@@ -34,7 +34,7 @@ describe Render do
|
|
34
34
|
stub_request(:get, aquatic_uri).to_return({ body: { name: @film_name }.to_json })
|
35
35
|
|
36
36
|
graph = Render::Graph.new(:film, { id: id, endpoint: @film_endpoint, secret_code: @secret_code })
|
37
|
-
graph.
|
37
|
+
graph.render.should == { film: { name: @film_name, year: nil } }
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
@@ -44,7 +44,7 @@ describe Render do
|
|
44
44
|
end
|
45
45
|
|
46
46
|
it "use meaningful values" do
|
47
|
-
response = Render::Graph.new(:film).
|
47
|
+
response = Render::Graph.new(:film).render({ name: @film_name })
|
48
48
|
|
49
49
|
stub_request(:post, "http://films.local/create").to_return({ body: response.to_json })
|
50
50
|
response = post_film(:anything)["film"]
|
@@ -54,7 +54,7 @@ describe Render do
|
|
54
54
|
end
|
55
55
|
|
56
56
|
it "allows users to specify specific values" do
|
57
|
-
response = Render::Graph.new(:film).
|
57
|
+
response = Render::Graph.new(:film).render({ name: @film_name })
|
58
58
|
|
59
59
|
data = { name: @film_name }.to_json
|
60
60
|
stub_request(:post, "http://films.local/create").with({ body: data }).to_return({ body: response.to_json })
|
@@ -0,0 +1,234 @@
|
|
1
|
+
require "render/dottable_hash"
|
2
|
+
|
3
|
+
module Render
|
4
|
+
describe DottableHash do
|
5
|
+
before(:each) do
|
6
|
+
@dottable_hash = DottableHash.new
|
7
|
+
end
|
8
|
+
|
9
|
+
describe "new" do
|
10
|
+
it "creates from a hash" do
|
11
|
+
dottable_hash = DottableHash.new({ "foo" => "bar" })
|
12
|
+
dottable_hash.should == { :foo => "bar" }
|
13
|
+
end
|
14
|
+
|
15
|
+
it "initializes new hashes as dottable_hashes" do
|
16
|
+
dottable_hash = DottableHash.new({ :foo => { :bar => "baz" } })
|
17
|
+
dottable_hash[:foo].class.should == DottableHash
|
18
|
+
end
|
19
|
+
|
20
|
+
it "converts all keys to symbols" do
|
21
|
+
dottable_hash = DottableHash.new({ "foo" => { "bar" => "baz" } })
|
22
|
+
dottable_hash.keys.include?(:foo).should be_true
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "#[]" do
|
27
|
+
it "converts keys to strings" do
|
28
|
+
@dottable_hash[:foo] = "bar"
|
29
|
+
@dottable_hash.keys.include?(:foo).should be_true
|
30
|
+
end
|
31
|
+
|
32
|
+
it "converts hash values to dottable_hashs" do
|
33
|
+
@dottable_hash[:foo] = { bar: { baz: "baz" } }
|
34
|
+
@dottable_hash.foo.bar.class.should == DottableHash
|
35
|
+
end
|
36
|
+
|
37
|
+
it "retrieves values by stringified keys" do
|
38
|
+
@dottable_hash["foo"] = "bar"
|
39
|
+
@dottable_hash[:foo].should == "bar"
|
40
|
+
end
|
41
|
+
|
42
|
+
it "converts hashes in arrays to dottable hashes" do
|
43
|
+
pallet = DottableHash.new
|
44
|
+
pallet.foo = [{ bar: "baz" }]
|
45
|
+
pallet.foo.first.class.should == DottableHash
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe "#delete" do
|
50
|
+
it "symbolizes keys" do
|
51
|
+
@dottable_hash["foo"] = { "bar" => "bar", "baz" => "baz" }
|
52
|
+
@dottable_hash.foo.delete(:bar)
|
53
|
+
@dottable_hash.should == { :foo => { :baz => "baz" } }
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe ".has_key?" do
|
58
|
+
it "converts symbols to strings" do
|
59
|
+
DottableHash.new({ foo: "bar" }).has_key?(:foo).should == true
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
describe "#method_missing" do
|
64
|
+
it "returns value for key when it exists" do
|
65
|
+
@dottable_hash[:foo] = "bar"
|
66
|
+
@dottable_hash.foo.should == "bar"
|
67
|
+
end
|
68
|
+
|
69
|
+
it "raises an error when no key exists" do
|
70
|
+
lambda {
|
71
|
+
@dottable_hash.foo
|
72
|
+
}.should raise_error(NoMethodError)
|
73
|
+
end
|
74
|
+
|
75
|
+
it "returns the same object as in the hash" do
|
76
|
+
@dottable_hash[:foo] = { bar: "baz" }
|
77
|
+
dottable_hash_object_id = @dottable_hash.foo.object_id
|
78
|
+
@dottable_hash.foo.object_id.should == dottable_hash_object_id
|
79
|
+
end
|
80
|
+
|
81
|
+
it "sets values" do
|
82
|
+
@dottable_hash.foo = "bar"
|
83
|
+
@dottable_hash.foo.should == "bar"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
describe "dot access" do
|
88
|
+
it "provides acess to keys as methods" do
|
89
|
+
dottable_hash = DottableHash.new({ "foo" => "bar" })
|
90
|
+
dottable_hash.foo.should == "bar"
|
91
|
+
end
|
92
|
+
|
93
|
+
it "provides acess to nested keys as methods" do
|
94
|
+
dottable_hash = DottableHash.new({ "foo" => {"bar" => {"baz" => "bat"}}})
|
95
|
+
dottable_hash.foo.bar.baz.should == "bat"
|
96
|
+
end
|
97
|
+
|
98
|
+
it "provides indifferent accesss" do
|
99
|
+
dottable_hash = DottableHash.new({ :foo => {:bar => {"baz" => "bat"}}})
|
100
|
+
dottable_hash.foo.bar.baz.should == "bat"
|
101
|
+
end
|
102
|
+
|
103
|
+
it "provides acess to keys with nil values" do
|
104
|
+
dottable_hash = DottableHash.new({ "foo" => {"bar" => nil} })
|
105
|
+
dottable_hash.foo.bar.should == nil
|
106
|
+
end
|
107
|
+
|
108
|
+
it "raises key error when it doesn't exist" do
|
109
|
+
dottable_hash = DottableHash.new({ "foo" => "bar" })
|
110
|
+
expect { dottable_hash.fu }.to raise_error(NoMethodError)
|
111
|
+
end
|
112
|
+
|
113
|
+
it "provides the dot access to a hash inside of an array" do
|
114
|
+
dottable_hash = DottableHash.new({ "foo" => [{"bar" => "baz"}]})
|
115
|
+
dottable_hash.foo.first.bar.should == "baz"
|
116
|
+
end
|
117
|
+
|
118
|
+
it "provides the dot access to to a list of strings inside an array" do
|
119
|
+
dottable_hash = DottableHash.new({ "foo" => ["bar", "baz"]})
|
120
|
+
dottable_hash.foo.should == ["bar", "baz"]
|
121
|
+
end
|
122
|
+
|
123
|
+
it "initializes hashes in nested arrays as dottable_hashs" do
|
124
|
+
dottable_hash = DottableHash.new({ foo: [{ bar: [{ baz: "one" }] }] })
|
125
|
+
dottable_hash.foo.first.bar.first.class.should == DottableHash
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
describe "#fetch" do
|
130
|
+
it "raises KeyErrors" do
|
131
|
+
lambda {
|
132
|
+
DottableHash.new.fetch("non_existent_key")
|
133
|
+
}.should raise_error
|
134
|
+
end
|
135
|
+
|
136
|
+
it "returns dottable_hashs in lieu of hashes" do
|
137
|
+
@dottable_hash["nested_hash"] = { "foo" => "bar" }
|
138
|
+
@dottable_hash.fetch("nested_hash").class.should == DottableHash
|
139
|
+
end
|
140
|
+
|
141
|
+
it "returns value of corresponding key object" do
|
142
|
+
@dottable_hash["foo"] = "bar"
|
143
|
+
@dottable_hash.fetch("foo").should == "bar"
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
147
|
+
|
148
|
+
describe "#fetch_path[!]" do
|
149
|
+
it "returns value of corresponding key object" do
|
150
|
+
@dottable_hash["foo"] = "bar"
|
151
|
+
@dottable_hash.fetch_path("foo").should == "bar"
|
152
|
+
end
|
153
|
+
|
154
|
+
it "returns value of expanded key object" do
|
155
|
+
@dottable_hash["foo"] = { "bar" => "baz" }
|
156
|
+
@dottable_hash.fetch_path("foo.bar").should == "baz"
|
157
|
+
end
|
158
|
+
|
159
|
+
it "raises key errors for nonexistent hashes" do
|
160
|
+
expect {
|
161
|
+
@dottable_hash.fetch_path!("foo")
|
162
|
+
}.to raise_error(KeyError)
|
163
|
+
end
|
164
|
+
|
165
|
+
it "raises key errors when searching into a string" do
|
166
|
+
@dottable_hash["foo"] = "bar"
|
167
|
+
expect {
|
168
|
+
@dottable_hash.fetch_path!("foo.bar")
|
169
|
+
}.to raise_error(KeyError)
|
170
|
+
end
|
171
|
+
|
172
|
+
it "does not raise errors for dottable_hashs with suppressed key errors" do
|
173
|
+
expect {
|
174
|
+
@dottable_hash.fetch_path("foo")
|
175
|
+
}.not_to raise_error
|
176
|
+
|
177
|
+
@dottable_hash["foo"] = "bar"
|
178
|
+
expect {
|
179
|
+
@dottable_hash.fetch_path("foo.bar")
|
180
|
+
}.not_to raise_error
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
describe "#set_path!" do
|
185
|
+
it "sets key's corresponding value" do
|
186
|
+
@dottable_hash.set_path!("foo", "bar")
|
187
|
+
@dottable_hash[:foo].should == "bar"
|
188
|
+
end
|
189
|
+
|
190
|
+
it "sets values for nested paths" do
|
191
|
+
@dottable_hash.set_path!("foo.bar.baz", "i'm really in here!")
|
192
|
+
{
|
193
|
+
:foo => {
|
194
|
+
:bar => {
|
195
|
+
:baz => "i'm really in here!"
|
196
|
+
}
|
197
|
+
}
|
198
|
+
}.should == @dottable_hash
|
199
|
+
end
|
200
|
+
|
201
|
+
it "does not overwrite the root key" do
|
202
|
+
@dottable_hash.set_path!("foo.bar", "bar")
|
203
|
+
@dottable_hash.set_path!("foo.baz.one", "baz1")
|
204
|
+
@dottable_hash.set_path!("foo.baz.two", "baz2")
|
205
|
+
{
|
206
|
+
:foo => {
|
207
|
+
:bar => "bar",
|
208
|
+
:baz => {
|
209
|
+
:one => "baz1",
|
210
|
+
:two => "baz2"
|
211
|
+
}
|
212
|
+
}
|
213
|
+
}.should == @dottable_hash
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
describe "#merge!" do
|
218
|
+
it "works with merged keys as symbols" do
|
219
|
+
dottable_hash = DottableHash.new({ stuff: {} })
|
220
|
+
dottable_hash.stuff.merge!({ things: "widgets" })
|
221
|
+
dottable_hash.stuff.things.should == "widgets"
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
describe "#merge" do
|
226
|
+
it "works with merged keys as symbols" do
|
227
|
+
dottable_hash = DottableHash.new({ stuff: {} })
|
228
|
+
stuff_dottable_hash = dottable_hash.stuff.merge({ things: "widgets" })
|
229
|
+
stuff_dottable_hash.things.should == "widgets"
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
end
|
234
|
+
end
|
@@ -123,13 +123,21 @@ module Render
|
|
123
123
|
end
|
124
124
|
end
|
125
125
|
|
126
|
-
describe "
|
126
|
+
describe "#render" do
|
127
127
|
it "returns its schema's data" do
|
128
128
|
pull = { film: { id: UUID.generate } }
|
129
|
-
@schema.stub({
|
129
|
+
@schema.stub({ render: pull })
|
130
130
|
|
131
131
|
graph = Graph.new(@schema)
|
132
|
-
graph.
|
132
|
+
graph.render.should == pull
|
133
|
+
end
|
134
|
+
|
135
|
+
it "returns a dottable hash" do
|
136
|
+
pull = { film: { id: UUID.generate } }
|
137
|
+
@schema.stub({ render: pull })
|
138
|
+
|
139
|
+
graph = Graph.new(@schema)
|
140
|
+
graph.render.should be_a(DottableHash)
|
133
141
|
end
|
134
142
|
|
135
143
|
it "sends interpolated endpoint to its schema" do
|
@@ -137,8 +145,9 @@ module Render
|
|
137
145
|
client_id = UUID.generate
|
138
146
|
graph = Graph.new(@schema, { endpoint: endpoint, client_id: client_id })
|
139
147
|
|
140
|
-
|
141
|
-
|
148
|
+
pull = { foo: "bar" }
|
149
|
+
@schema.should_receive(:render).with({ endpoint: graph.endpoint }).and_return(pull)
|
150
|
+
graph.render.should == pull
|
142
151
|
end
|
143
152
|
|
144
153
|
context "with nested graphs" do
|
@@ -162,19 +171,19 @@ module Render
|
|
162
171
|
|
163
172
|
it "merges nested graphs" do
|
164
173
|
pulled_data = { a: "attribute" }
|
165
|
-
@director_schema.stub({
|
174
|
+
@director_schema.stub({ render: pulled_data })
|
166
175
|
|
167
176
|
director = Graph.new(@director_schema)
|
168
177
|
film = Graph.new(@film_schema, { graphs: [director]})
|
169
178
|
|
170
|
-
film_graph = film.
|
179
|
+
film_graph = film.render[@film_schema.title.to_sym]
|
171
180
|
film_graph.should include(pulled_data)
|
172
181
|
end
|
173
182
|
|
174
183
|
it "uses parent data to calculate endpoint" do
|
175
184
|
director_id = UUID.generate
|
176
185
|
film = Graph.new(@film_schema)
|
177
|
-
film.schema.stub({
|
186
|
+
film.schema.stub({ render: { film: { director_id: director_id } } })
|
178
187
|
|
179
188
|
endpoint = "http://endpoint.local/directors/:id"
|
180
189
|
interpolated_endpoint = "http://endpoint.local/directors/#{director_id}"
|
@@ -182,11 +191,11 @@ module Render
|
|
182
191
|
director = Graph.new(@director_schema, { endpoint: endpoint, relationships: relationships })
|
183
192
|
|
184
193
|
film.graphs << director
|
185
|
-
director.schema.should_receive(:
|
194
|
+
director.schema.should_receive(:render).with do |args|
|
186
195
|
args[:endpoint].should == interpolated_endpoint
|
187
196
|
end.and_return({})
|
188
197
|
|
189
|
-
film.
|
198
|
+
film.render
|
190
199
|
end
|
191
200
|
|
192
201
|
context "offline" do
|
@@ -195,7 +204,7 @@ module Render
|
|
195
204
|
director = Graph.new(@director_schema, { relationships: relationships })
|
196
205
|
film = Graph.new(@film_schema, { graphs: [director]})
|
197
206
|
|
198
|
-
film_graph = film.
|
207
|
+
film_graph = film.render[@film_schema.title.to_sym]
|
199
208
|
film_graph[@director_schema.title.to_sym][:id].should == film_graph[:director_id]
|
200
209
|
end
|
201
210
|
end
|
@@ -156,7 +156,7 @@ module Render
|
|
156
156
|
stub_request(:get, endpoint).to_return(response)
|
157
157
|
|
158
158
|
film = Schema.new(@film_schema)
|
159
|
-
film.
|
159
|
+
film.render({ endpoint: endpoint }).should == response_body
|
160
160
|
end
|
161
161
|
|
162
162
|
it "raises error if response is not 2xx" do
|
@@ -166,7 +166,7 @@ module Render
|
|
166
166
|
|
167
167
|
expect {
|
168
168
|
film = Schema.new(@film_schema)
|
169
|
-
film.
|
169
|
+
film.render({ endpoint: endpoint })
|
170
170
|
}.to raise_error(Errors::Schema::RequestError)
|
171
171
|
end
|
172
172
|
|
@@ -175,7 +175,7 @@ module Render
|
|
175
175
|
stub_request(:get, endpoint).to_return({ body: "Server Error: 500" })
|
176
176
|
|
177
177
|
expect {
|
178
|
-
Schema.new(@film_schema).
|
178
|
+
Schema.new(@film_schema).render({ endpoint: endpoint })
|
179
179
|
}.to raise_error(Errors::Schema::InvalidResponse)
|
180
180
|
end
|
181
181
|
end
|
@@ -187,7 +187,7 @@ module Render
|
|
187
187
|
|
188
188
|
it "returns schema with fake values" do
|
189
189
|
film = Schema.new(@film_schema)
|
190
|
-
film = film.
|
190
|
+
film = film.render[:film]
|
191
191
|
film[:name].should be_a(String)
|
192
192
|
film[:genre].should be_a(String)
|
193
193
|
end
|
@@ -200,7 +200,7 @@ module Render
|
|
200
200
|
stub_request(:get, endpoint).to_return(response)
|
201
201
|
|
202
202
|
film = Schema.new(@film_schema)
|
203
|
-
film.
|
203
|
+
film.render({ endpoint: endpoint }).should == { film: response_body }
|
204
204
|
end
|
205
205
|
|
206
206
|
it "handles Array responses" do
|
@@ -217,7 +217,7 @@ module Render
|
|
217
217
|
stub_request(:get, endpoint).to_return(response)
|
218
218
|
|
219
219
|
film = Schema.new(@films_schema)
|
220
|
-
film.
|
220
|
+
film.render({ endpoint: endpoint }).should == { films: response_body }
|
221
221
|
end
|
222
222
|
end
|
223
223
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: render
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -137,13 +137,13 @@ files:
|
|
137
137
|
- Gemfile
|
138
138
|
- Gemfile.lock
|
139
139
|
- LICENSE.txt
|
140
|
-
- extensions/boolean.rb
|
141
|
-
- extensions/dottable_hash.rb
|
142
|
-
- extensions/enumerable.rb
|
143
|
-
- extensions/hash.rb
|
144
140
|
- initialize.rb
|
141
|
+
- lib/extensions/boolean.rb
|
142
|
+
- lib/extensions/enumerable.rb
|
143
|
+
- lib/extensions/hash.rb
|
145
144
|
- lib/render.rb
|
146
145
|
- lib/render/attribute.rb
|
146
|
+
- lib/render/dottable_hash.rb
|
147
147
|
- lib/render/errors.rb
|
148
148
|
- lib/render/generator.rb
|
149
149
|
- lib/render/graph.rb
|
@@ -163,6 +163,7 @@ files:
|
|
163
163
|
- spec/support/helpers.rb
|
164
164
|
- spec/unit/extensions/boolean_spec.rb
|
165
165
|
- spec/unit/render/attribute_spec.rb
|
166
|
+
- spec/unit/render/dottable_hash_spec.rb
|
166
167
|
- spec/unit/render/generator_spec.rb
|
167
168
|
- spec/unit/render/graph_spec.rb
|
168
169
|
- spec/unit/render/schema_spec.rb
|
@@ -182,7 +183,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
182
183
|
version: '0'
|
183
184
|
segments:
|
184
185
|
- 0
|
185
|
-
hash:
|
186
|
+
hash: 1123791212905751399
|
186
187
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
187
188
|
none: false
|
188
189
|
requirements:
|
@@ -191,7 +192,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
191
192
|
version: '0'
|
192
193
|
segments:
|
193
194
|
- 0
|
194
|
-
hash:
|
195
|
+
hash: 1123791212905751399
|
195
196
|
requirements: []
|
196
197
|
rubyforge_project:
|
197
198
|
rubygems_version: 1.8.25
|
@@ -211,6 +212,7 @@ test_files:
|
|
211
212
|
- spec/support/helpers.rb
|
212
213
|
- spec/unit/extensions/boolean_spec.rb
|
213
214
|
- spec/unit/render/attribute_spec.rb
|
215
|
+
- spec/unit/render/dottable_hash_spec.rb
|
214
216
|
- spec/unit/render/generator_spec.rb
|
215
217
|
- spec/unit/render/graph_spec.rb
|
216
218
|
- spec/unit/render/schema_spec.rb
|
data/extensions/dottable_hash.rb
DELETED
@@ -1,113 +0,0 @@
|
|
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
|
data/extensions/enumerable.rb
DELETED
@@ -1,36 +0,0 @@
|
|
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
|