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.
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