render 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +8 -8
  2. data/Gemfile +1 -1
  3. data/lib/render.rb +12 -50
  4. data/lib/render/{array_attribute.rb → attributes/array_attribute.rb} +3 -4
  5. data/lib/render/attributes/attribute.rb +48 -0
  6. data/lib/render/{hash_attribute.rb → attributes/hash_attribute.rb} +2 -4
  7. data/lib/render/definition.rb +31 -0
  8. data/lib/render/extensions/dottable_hash.rb +82 -0
  9. data/lib/render/extensions/symbolizable_array.rb +26 -0
  10. data/lib/render/extensions/symbolizable_hash.rb +28 -0
  11. data/lib/render/generator.rb +58 -4
  12. data/lib/render/graph.rb +29 -34
  13. data/lib/render/schema.rb +12 -12
  14. data/lib/render/type.rb +63 -0
  15. data/lib/render/version.rb +1 -1
  16. data/rakefile.rb +3 -3
  17. data/readme.md +17 -38
  18. data/render.gemspec +5 -7
  19. data/spec/functional/{representation → render}/attribute_spec.rb +3 -4
  20. data/spec/functional/{representation → render}/graph_spec.rb +6 -6
  21. data/spec/functional/{representation → render}/nested_schemas_spec.rb +0 -0
  22. data/spec/functional/{representation → render}/schema_spec.rb +0 -0
  23. data/spec/integration/render/graph_spec.rb +119 -0
  24. data/spec/integration/render/nested_graph_spec.rb +67 -0
  25. data/spec/integration/render/schema_spec.rb +90 -0
  26. data/spec/support/helpers.rb +2 -1
  27. data/spec/{schemas → support/schemas}/film.json +1 -0
  28. data/spec/{schemas → support/schemas}/films.json +1 -0
  29. data/spec/unit/gemspec_spec.rb +8 -0
  30. data/spec/unit/{array_attribute_spec.rb → render/attributes/array_attribute_spec.rb} +1 -1
  31. data/spec/unit/render/{attribute_spec.rb → attributes/attribute_spec.rb} +0 -0
  32. data/spec/unit/render/{hash_attribute_spec.rb → attributes/hash_attribute_spec.rb} +3 -1
  33. data/spec/unit/render/definition_spec.rb +85 -0
  34. data/spec/unit/render/extensions/dottable_hash_spec.rb +148 -0
  35. data/spec/unit/render/extensions/symbolizable_array_spec.rb +20 -0
  36. data/spec/unit/render/generator_spec.rb +44 -22
  37. data/spec/unit/render/graph_spec.rb +18 -18
  38. data/spec/unit/render/schema_spec.rb +11 -16
  39. data/spec/unit/render/type_spec.rb +83 -0
  40. data/spec/unit/render_spec.rb +0 -139
  41. metadata +70 -60
  42. data/lib/extensions/boolean.rb +0 -2
  43. data/lib/extensions/enumerable.rb +0 -16
  44. data/lib/extensions/hash.rb +0 -39
  45. data/lib/render/attribute.rb +0 -59
  46. data/lib/render/dottable_hash.rb +0 -113
  47. data/spec/integration/nested_graph_spec.rb +0 -85
  48. data/spec/integration/single_graph_spec.rb +0 -76
  49. data/spec/unit/extensions/boolean_spec.rb +0 -7
  50. 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
@@ -1,3 +1,4 @@
1
1
  module Helpers
2
- SCHEMA_DIRECTORY = File.expand_path("../../schemas", __FILE__)
2
+ SCHEMA_DIRECTORY = File.expand_path("../schemas", __FILE__)
3
+ puts SCHEMA_DIRECTORY.inspect
3
4
  end
@@ -2,6 +2,7 @@
2
2
  "universal_title": "films_show",
3
3
  "title": "film",
4
4
  "type": "object",
5
+ "endpoint": "http://:host/films/:id",
5
6
  "properties": {
6
7
  "name": { "type": "string" },
7
8
  "year": { "type": "integer" }
@@ -2,6 +2,7 @@
2
2
  "universal_title": "films_index",
3
3
  "title": "films",
4
4
  "type": "array",
5
+ "endpoint": "http://:host",
5
6
  "items": {
6
7
  "title": "film",
7
8
  "type" : "object",
@@ -0,0 +1,8 @@
1
+ describe :gemspec do
2
+ it "is valid" do
3
+ spec = Gem::Specification::load("render.gemspec")
4
+ expect {
5
+ spec.validate
6
+ }.not_to raise_error
7
+ end
8
+ end
@@ -1,4 +1,4 @@
1
- require "render/array_attribute"
1
+ require "render/attributes/array_attribute"
2
2
 
3
3
  module Render
4
4
  describe ArrayAttribute do
@@ -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
- UUID.validate(HashAttribute.new({ key: { type: UUID } }).default_value).should be_true
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