render 0.0.4 → 0.0.5
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.
- checksums.yaml +8 -8
- data/Gemfile +1 -1
- data/lib/render.rb +12 -50
- data/lib/render/{array_attribute.rb → attributes/array_attribute.rb} +3 -4
- data/lib/render/attributes/attribute.rb +48 -0
- data/lib/render/{hash_attribute.rb → attributes/hash_attribute.rb} +2 -4
- data/lib/render/definition.rb +31 -0
- data/lib/render/extensions/dottable_hash.rb +82 -0
- data/lib/render/extensions/symbolizable_array.rb +26 -0
- data/lib/render/extensions/symbolizable_hash.rb +28 -0
- data/lib/render/generator.rb +58 -4
- data/lib/render/graph.rb +29 -34
- data/lib/render/schema.rb +12 -12
- data/lib/render/type.rb +63 -0
- data/lib/render/version.rb +1 -1
- data/rakefile.rb +3 -3
- data/readme.md +17 -38
- data/render.gemspec +5 -7
- data/spec/functional/{representation → render}/attribute_spec.rb +3 -4
- data/spec/functional/{representation → render}/graph_spec.rb +6 -6
- data/spec/functional/{representation → render}/nested_schemas_spec.rb +0 -0
- data/spec/functional/{representation → render}/schema_spec.rb +0 -0
- data/spec/integration/render/graph_spec.rb +119 -0
- data/spec/integration/render/nested_graph_spec.rb +67 -0
- data/spec/integration/render/schema_spec.rb +90 -0
- data/spec/support/helpers.rb +2 -1
- data/spec/{schemas → support/schemas}/film.json +1 -0
- data/spec/{schemas → support/schemas}/films.json +1 -0
- data/spec/unit/gemspec_spec.rb +8 -0
- data/spec/unit/{array_attribute_spec.rb → render/attributes/array_attribute_spec.rb} +1 -1
- data/spec/unit/render/{attribute_spec.rb → attributes/attribute_spec.rb} +0 -0
- data/spec/unit/render/{hash_attribute_spec.rb → attributes/hash_attribute_spec.rb} +3 -1
- data/spec/unit/render/definition_spec.rb +85 -0
- data/spec/unit/render/extensions/dottable_hash_spec.rb +148 -0
- data/spec/unit/render/extensions/symbolizable_array_spec.rb +20 -0
- data/spec/unit/render/generator_spec.rb +44 -22
- data/spec/unit/render/graph_spec.rb +18 -18
- data/spec/unit/render/schema_spec.rb +11 -16
- data/spec/unit/render/type_spec.rb +83 -0
- data/spec/unit/render_spec.rb +0 -139
- metadata +70 -60
- data/lib/extensions/boolean.rb +0 -2
- data/lib/extensions/enumerable.rb +0 -16
- data/lib/extensions/hash.rb +0 -39
- data/lib/render/attribute.rb +0 -59
- data/lib/render/dottable_hash.rb +0 -113
- data/spec/integration/nested_graph_spec.rb +0 -85
- data/spec/integration/single_graph_spec.rb +0 -76
- data/spec/unit/extensions/boolean_spec.rb +0 -7
- data/spec/unit/render/dottable_hash_spec.rb +0 -231
@@ -0,0 +1,67 @@
|
|
1
|
+
require "render"
|
2
|
+
|
3
|
+
module Render
|
4
|
+
describe Graph do
|
5
|
+
before(:all) do
|
6
|
+
Render::Definition.load_from_directory!(Helpers::SCHEMA_DIRECTORY)
|
7
|
+
|
8
|
+
@host = "films.local"
|
9
|
+
@aquatic_id = UUID.generate
|
10
|
+
@darjeeling_id = UUID.generate
|
11
|
+
@aquatic_name = "The Life Aquatic with Steve Zissou"
|
12
|
+
@darjeeling_name = "The Darjeeling Limited"
|
13
|
+
end
|
14
|
+
|
15
|
+
after(:all) do
|
16
|
+
Render::Definition.instances.clear
|
17
|
+
end
|
18
|
+
|
19
|
+
it "uses first request's data for subsequent requests" do
|
20
|
+
stub_request(:get, "http://films.local").to_return({ body: [{ id: @aquatic_id }, { id: @darjeeling_id }].to_json })
|
21
|
+
stub_request(:get, "http://films.local/films/#{@aquatic_id}").to_return({ body: { name: @aquatic_name }.to_json })
|
22
|
+
stub_request(:get, "http://films.local/films/#{@darjeeling_id}").to_return({ body: { name: @darjeeling_name }.to_json })
|
23
|
+
|
24
|
+
graph = Render::Graph.new(:films_index, { host: "films.local" })
|
25
|
+
graph.graphs << Render::Graph.new(:films_show, { host: "films.local", relationships: { id: :id } })
|
26
|
+
response = graph.render!
|
27
|
+
|
28
|
+
response.should == {
|
29
|
+
films_index: {
|
30
|
+
films: [{ id: @aquatic_id }, { id: @darjeeling_id }]
|
31
|
+
},
|
32
|
+
films_show: [
|
33
|
+
{ film: { name: @aquatic_name, year: nil } },
|
34
|
+
{ film: { name: @darjeeling_name, year: nil } }
|
35
|
+
]
|
36
|
+
}
|
37
|
+
end
|
38
|
+
|
39
|
+
it "makes subsequent calls from archetype array data" do
|
40
|
+
stub_request(:get, "http://films.local").to_return({ body: [@aquatic_id, @darjeeling_id].to_json })
|
41
|
+
stub_request(:get, "http://films.local/films/#{@aquatic_id}").to_return({ body: { name: @aquatic_name }.to_json })
|
42
|
+
stub_request(:get, "http://films.local/films/#{@darjeeling_id}").to_return({ body: { name: @darjeeling_name }.to_json })
|
43
|
+
|
44
|
+
schema = Render::Schema.new({
|
45
|
+
title: :films_as_array_of_archetypes,
|
46
|
+
type: Array,
|
47
|
+
endpoint: "http://:host",
|
48
|
+
items: {
|
49
|
+
type: UUID
|
50
|
+
}
|
51
|
+
})
|
52
|
+
|
53
|
+
graph = Render::Graph.new(schema, { host: "films.local" })
|
54
|
+
graph.graphs << Render::Graph.new(:films_show, { host: "films.local", relationships: { id: :id } })
|
55
|
+
response = graph.render!
|
56
|
+
|
57
|
+
response.should == {
|
58
|
+
films_as_array_of_archetypes: [@aquatic_id, @darjeeling_id],
|
59
|
+
films_show: [
|
60
|
+
{ film: { name: @aquatic_name, year: nil } },
|
61
|
+
{ film: { name: @darjeeling_name, year: nil } }
|
62
|
+
]
|
63
|
+
}
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require "render"
|
2
|
+
|
3
|
+
module Render
|
4
|
+
describe Schema do
|
5
|
+
before(:all) do
|
6
|
+
Render::Definition.load_from_directory!(Helpers::SCHEMA_DIRECTORY)
|
7
|
+
Render.live = false
|
8
|
+
|
9
|
+
module ::TransformerExample
|
10
|
+
class << self
|
11
|
+
def process_name
|
12
|
+
uri = URI("http://films.local")
|
13
|
+
response = JSON.parse(Net::HTTP.get(uri))
|
14
|
+
{ transformed_to: response.fetch("name") }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
after(:all) do
|
21
|
+
Render::Definition.instances.clear
|
22
|
+
Render.live = true
|
23
|
+
Object.send(:remove_const, :TransformerExample)
|
24
|
+
end
|
25
|
+
|
26
|
+
it "stubs data for testing" do
|
27
|
+
name = "am I transforming this right?"
|
28
|
+
rendered_stub = Render::Schema.new(:films_show).serialize!({ name: name })
|
29
|
+
stub_request(:get, "http://films.local").to_return({ body: rendered_stub.to_json })
|
30
|
+
|
31
|
+
TransformerExample.process_name[:transformed_to].should == name
|
32
|
+
end
|
33
|
+
|
34
|
+
it "enforces schema's definition" do
|
35
|
+
name = "am I transforming this right?"
|
36
|
+
rendered_stub = Render::Schema.new(:films_show).serialize!({ wrong_key: name })
|
37
|
+
stub_request(:get, "http://films.local").to_return({ body: rendered_stub.to_json })
|
38
|
+
|
39
|
+
TransformerExample.process_name[:transformed_to].should_not == name
|
40
|
+
end
|
41
|
+
|
42
|
+
it "prevents errors related to code anticipating actual data" do
|
43
|
+
rendered_stub = Render::Schema.new(:films_show).serialize!
|
44
|
+
stub_request(:get, "http://films.local").to_return({ body: rendered_stub.to_json })
|
45
|
+
|
46
|
+
expect {
|
47
|
+
response = TransformerExample.process_name
|
48
|
+
}.not_to raise_error
|
49
|
+
end
|
50
|
+
|
51
|
+
it "creates fake data for varying types" do
|
52
|
+
schema = {
|
53
|
+
title: :film,
|
54
|
+
type: Object,
|
55
|
+
properties: {
|
56
|
+
id: { type: UUID },
|
57
|
+
title: { type: String },
|
58
|
+
director: {
|
59
|
+
type: Object,
|
60
|
+
properties: {
|
61
|
+
rating: { type: Float }
|
62
|
+
}
|
63
|
+
},
|
64
|
+
genre: {
|
65
|
+
enum: ["horror", "action", "sci-fi"]
|
66
|
+
},
|
67
|
+
tags: {
|
68
|
+
type: Array,
|
69
|
+
required: true,
|
70
|
+
items: {
|
71
|
+
type: Object,
|
72
|
+
properties: {
|
73
|
+
name: { type: String },
|
74
|
+
id: { type: Integer }
|
75
|
+
}
|
76
|
+
}
|
77
|
+
}
|
78
|
+
}
|
79
|
+
}
|
80
|
+
|
81
|
+
response = Render::Schema.new(schema).serialize!
|
82
|
+
UUID.validate(response[:id]).should be_true
|
83
|
+
response[:title].should be_a(String)
|
84
|
+
response[:director][:rating].should be_a(Float)
|
85
|
+
%w(horror action sci-fi).should include(response[:genre])
|
86
|
+
response[:tags].first[:name].should be_a(String)
|
87
|
+
response[:tags].first[:id].should be_a(Integer)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
data/spec/support/helpers.rb
CHANGED
File without changes
|
@@ -126,7 +126,9 @@ module Render
|
|
126
126
|
supported_classes.each do |klass|
|
127
127
|
HashAttribute.new({ key: { type: klass } }).default_value.should be_a(klass)
|
128
128
|
end
|
129
|
-
|
129
|
+
|
130
|
+
value = HashAttribute.new({ key: { type: UUID } }).default_value
|
131
|
+
UUID.validate(value).should be_true
|
130
132
|
end
|
131
133
|
|
132
134
|
it "generates value from enum" do
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require "render/definition"
|
2
|
+
|
3
|
+
module Render
|
4
|
+
describe Definition do
|
5
|
+
before(:each) do
|
6
|
+
@original_defs = Definition.instances.dup
|
7
|
+
end
|
8
|
+
|
9
|
+
after(:each) do
|
10
|
+
Definition.instances = @original_defs
|
11
|
+
end
|
12
|
+
|
13
|
+
describe ".load!" do
|
14
|
+
it "preferences #universal_title over title" do
|
15
|
+
universal_title = "a_service_films_show"
|
16
|
+
definition = {
|
17
|
+
universal_title: universal_title,
|
18
|
+
title: "film",
|
19
|
+
type: Object,
|
20
|
+
properties: {
|
21
|
+
name: { type: String },
|
22
|
+
year: { type: Integer }
|
23
|
+
}
|
24
|
+
}
|
25
|
+
|
26
|
+
Definition.load!(definition)
|
27
|
+
Definition.instances.keys.should include(universal_title.to_sym)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe ".load_schemas!" do
|
32
|
+
before(:each) do
|
33
|
+
Definition.instances.clear
|
34
|
+
@schema_title = "film"
|
35
|
+
@json_schema = <<-JSON
|
36
|
+
{
|
37
|
+
"title": "#{@schema_title}",
|
38
|
+
"type": "object",
|
39
|
+
"properties": {
|
40
|
+
"name": { "type": "string" },
|
41
|
+
"year": { "type": "integer" }
|
42
|
+
}
|
43
|
+
}
|
44
|
+
JSON
|
45
|
+
|
46
|
+
@directory = "/a"
|
47
|
+
@schema_file = "/a/schema.json"
|
48
|
+
Dir.stub(:glob).with(%r{#{@directory}}).and_return([@schema_file])
|
49
|
+
IO.stub(:read).with(@schema_file).and_return(@json_schema)
|
50
|
+
end
|
51
|
+
|
52
|
+
after(:each) do
|
53
|
+
Definition.instances = {}
|
54
|
+
end
|
55
|
+
|
56
|
+
it "stores JSON files" do
|
57
|
+
expect {
|
58
|
+
Definition.load_from_directory!(@directory)
|
59
|
+
}.to change { Definition.instances.keys.size }.by(1)
|
60
|
+
end
|
61
|
+
|
62
|
+
it "accesses parsed schemas with symbols" do
|
63
|
+
Definition.load_from_directory!(@directory)
|
64
|
+
parsed_json = Render::Extensions::DottableHash.new(JSON.parse(@json_schema)).recursively_symbolize_keys!
|
65
|
+
Definition.instances[@schema_title.to_sym].should == parsed_json
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe ".definition" do
|
71
|
+
it "returns definition by its title" do
|
72
|
+
def_title = :the_name
|
73
|
+
definition = { title: def_title, properties: {} }
|
74
|
+
Definition.load!(definition)
|
75
|
+
|
76
|
+
Definition.find(def_title).should == definition
|
77
|
+
end
|
78
|
+
|
79
|
+
it "raises meaningful error if definition is not found" do
|
80
|
+
expect {
|
81
|
+
Definition.find(:definition_with_this_title_has_not_been_loaded)
|
82
|
+
}.to raise_error(Render::Errors::DefinitionNotFound)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
require "render/extensions/dottable_hash"
|
2
|
+
|
3
|
+
module Render
|
4
|
+
module Extensions
|
5
|
+
describe DottableHash do
|
6
|
+
before(:each) do
|
7
|
+
@dottable_hash = DottableHash.new
|
8
|
+
end
|
9
|
+
|
10
|
+
describe "new" do
|
11
|
+
it "creates from a hash" do
|
12
|
+
dottable_hash = DottableHash.new({ "foo" => "bar" })
|
13
|
+
dottable_hash.should == { :foo => "bar" }
|
14
|
+
end
|
15
|
+
|
16
|
+
it "initializes new hashes as dottable_hashes" do
|
17
|
+
dottable_hash = DottableHash.new({ :foo => { :bar => "baz" } })
|
18
|
+
dottable_hash[:foo].class.should == DottableHash
|
19
|
+
end
|
20
|
+
|
21
|
+
it "converts all keys to symbols" do
|
22
|
+
dottable_hash = DottableHash.new({ "foo" => { "bar" => "baz" } })
|
23
|
+
dottable_hash.keys.include?(:foo).should be_true
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "#[]" do
|
28
|
+
it "converts keys to strings" do
|
29
|
+
@dottable_hash[:foo] = "bar"
|
30
|
+
@dottable_hash.keys.include?(:foo).should be_true
|
31
|
+
end
|
32
|
+
|
33
|
+
it "converts hash values to dottable_hashs" do
|
34
|
+
@dottable_hash[:foo] = { bar: { baz: "baz" } }
|
35
|
+
@dottable_hash.foo.bar.class.should == DottableHash
|
36
|
+
end
|
37
|
+
|
38
|
+
it "retrieves values by stringified keys" do
|
39
|
+
@dottable_hash["foo"] = "bar"
|
40
|
+
@dottable_hash[:foo].should == "bar"
|
41
|
+
end
|
42
|
+
|
43
|
+
it "converts hashes in arrays to dottable hashes" do
|
44
|
+
pallet = DottableHash.new
|
45
|
+
pallet.foo = [{ bar: "baz" }]
|
46
|
+
pallet.foo.first.class.should == DottableHash
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe "#delete" do
|
51
|
+
it "symbolizes keys" do
|
52
|
+
@dottable_hash["foo"] = { "bar" => "bar", "baz" => "baz" }
|
53
|
+
@dottable_hash.foo.delete(:bar)
|
54
|
+
@dottable_hash.should == { :foo => { :baz => "baz" } }
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe ".has_key?" do
|
59
|
+
it "converts symbols to strings" do
|
60
|
+
DottableHash.new({ foo: "bar" }).has_key?(:foo).should == true
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe "#method_missing" do
|
65
|
+
it "returns value for key when it exists" do
|
66
|
+
@dottable_hash[:foo] = "bar"
|
67
|
+
@dottable_hash.foo.should == "bar"
|
68
|
+
end
|
69
|
+
|
70
|
+
it "raises an error when no key exists" do
|
71
|
+
lambda {
|
72
|
+
@dottable_hash.foo
|
73
|
+
}.should raise_error(NoMethodError)
|
74
|
+
end
|
75
|
+
|
76
|
+
it "returns the same object as in the hash" do
|
77
|
+
@dottable_hash[:foo] = { bar: "baz" }
|
78
|
+
dottable_hash_object_id = @dottable_hash.foo.object_id
|
79
|
+
@dottable_hash.foo.object_id.should == dottable_hash_object_id
|
80
|
+
end
|
81
|
+
|
82
|
+
it "sets values" do
|
83
|
+
@dottable_hash.foo = "bar"
|
84
|
+
@dottable_hash.foo.should == "bar"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
describe "dot access" do
|
89
|
+
it "provides acess to keys as methods" do
|
90
|
+
dottable_hash = DottableHash.new({ "foo" => "bar" })
|
91
|
+
dottable_hash.foo.should == "bar"
|
92
|
+
end
|
93
|
+
|
94
|
+
it "provides acess to nested keys as methods" do
|
95
|
+
dottable_hash = DottableHash.new({ "foo" => {"bar" => {"baz" => "bat"}}})
|
96
|
+
dottable_hash.foo.bar.baz.should == "bat"
|
97
|
+
end
|
98
|
+
|
99
|
+
it "provides indifferent accesss" do
|
100
|
+
dottable_hash = DottableHash.new({ :foo => {:bar => {"baz" => "bat"}}})
|
101
|
+
dottable_hash.foo.bar.baz.should == "bat"
|
102
|
+
end
|
103
|
+
|
104
|
+
it "provides acess to keys with nil values" do
|
105
|
+
dottable_hash = DottableHash.new({ "foo" => {"bar" => nil} })
|
106
|
+
dottable_hash.foo.bar.should == nil
|
107
|
+
end
|
108
|
+
|
109
|
+
it "raises key error when it doesn't exist" do
|
110
|
+
dottable_hash = DottableHash.new({ "foo" => "bar" })
|
111
|
+
expect { dottable_hash.fu }.to raise_error(NoMethodError)
|
112
|
+
end
|
113
|
+
|
114
|
+
it "provides the dot access to a hash inside of an array" do
|
115
|
+
dottable_hash = DottableHash.new({ "foo" => [{"bar" => "baz"}]})
|
116
|
+
dottable_hash.foo.first.bar.should == "baz"
|
117
|
+
end
|
118
|
+
|
119
|
+
it "provides the dot access to to a list of strings inside an array" do
|
120
|
+
dottable_hash = DottableHash.new({ "foo" => ["bar", "baz"]})
|
121
|
+
dottable_hash.foo.should == ["bar", "baz"]
|
122
|
+
end
|
123
|
+
|
124
|
+
it "initializes hashes in nested arrays as dottable_hashs" do
|
125
|
+
dottable_hash = DottableHash.new({ foo: [{ bar: [{ baz: "one" }] }] })
|
126
|
+
dottable_hash.foo.first.bar.first.class.should == DottableHash
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
describe "#merge!" do
|
131
|
+
it "works with merged keys as symbols" do
|
132
|
+
dottable_hash = DottableHash.new({ stuff: {} })
|
133
|
+
dottable_hash.stuff.merge!({ things: "widgets" })
|
134
|
+
dottable_hash.stuff.things.should == "widgets"
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
describe "#merge" do
|
139
|
+
it "works with merged keys as symbols" do
|
140
|
+
dottable_hash = DottableHash.new({ stuff: {} })
|
141
|
+
stuff_dottable_hash = dottable_hash.stuff.merge({ things: "widgets" })
|
142
|
+
stuff_dottable_hash.things.should == "widgets"
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|