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
data/lib/render/graph.rb CHANGED
@@ -1,12 +1,6 @@
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
1
  require "render/schema"
8
2
  require "render/errors"
9
- require "render/dottable_hash"
3
+ require "render/extensions/dottable_hash"
10
4
 
11
5
  module Render
12
6
  class Graph
@@ -25,31 +19,17 @@ module Render
25
19
  self.schema = determine_schema(schema_or_definition)
26
20
  self.relationships = (options.delete(:relationships) || {})
27
21
  self.graphs = (options.delete(:graphs) || [])
28
- self.raw_endpoint = options.delete(:endpoint).to_s
22
+ self.raw_endpoint = (options.delete(:endpoint) || schema.definition[:endpoint]).to_s
29
23
  self.config = options
30
24
 
31
25
  self.inherited_data = {}
32
26
  end
33
27
 
34
- def endpoint
35
- uri = URI(raw_endpoint)
36
-
37
- uri.path.gsub!(PARAMS) do |param|
38
- key = param_key(param)
39
- param.gsub(PARAM, param_value(key))
40
- end
41
-
42
- if uri.query
43
- uri.query.gsub!(PARAMS) do |param|
44
- key = param_key(param)
45
- "#{key}=#{param_value(key)}&"
46
- end.chop!
47
- end
48
-
49
- uri.to_s
28
+ def title
29
+ schema.universal_title || schema.title
50
30
  end
51
31
 
52
- def render(inherited_properties = nil)
32
+ def render!(inherited_properties = nil)
53
33
  self.inherited_data = inherited_properties
54
34
  if (schema.type == Array)
55
35
  explicit_data = inherited_data
@@ -58,16 +38,16 @@ module Render
58
38
  explicit_data = explicit_data.merge!(relationship_data_from_parent)
59
39
  end
60
40
 
61
- graph_data = DottableHash.new
41
+ graph_data = Extensions::DottableHash.new
62
42
 
63
43
  rendered_data = schema.render!(explicit_data, endpoint) do |parent_data|
64
44
  loop_with_configured_threading(graphs) do |graph|
65
45
  if parent_data.is_a?(Array)
66
46
  graph_data[graph.title] = parent_data.inject([]) do |nested_data, element|
67
- nested_data << graph.render(element)[graph.title]
47
+ nested_data << graph.render!(element)[graph.title]
68
48
  end
69
49
  else
70
- nested_data = graph.render(parent_data)
50
+ nested_data = graph.render!(parent_data)
71
51
  graph_data.merge!(nested_data)
72
52
  end
73
53
  end
@@ -76,6 +56,27 @@ module Render
76
56
  self.rendered_data = graph_data.merge!(rendered_data)
77
57
  end
78
58
 
59
+ private
60
+
61
+ def endpoint
62
+ raw_endpoint.gsub!(":host", config.fetch(:host)) if raw_endpoint.match(":host")
63
+ uri = URI(raw_endpoint)
64
+
65
+ uri.path.gsub!(PARAMS) do |param|
66
+ key = param_key(param)
67
+ param.gsub(PARAM, param_value(key).to_s)
68
+ end
69
+
70
+ if uri.query
71
+ uri.query.gsub!(PARAMS) do |param|
72
+ key = param_key(param)
73
+ "#{key}=#{param_value(key)}&"
74
+ end.chop!
75
+ end
76
+
77
+ uri.to_s
78
+ end
79
+
79
80
  def loop_with_configured_threading(elements)
80
81
  if Render.threading?
81
82
  threads = []
@@ -92,12 +93,6 @@ module Render
92
93
  end
93
94
  end
94
95
 
95
- def title
96
- schema.universal_title || schema.title
97
- end
98
-
99
- private
100
-
101
96
  def determine_schema(schema_or_definition)
102
97
  if schema_or_definition.is_a?(Schema)
103
98
  schema_or_definition
data/lib/render/schema.rb CHANGED
@@ -1,13 +1,9 @@
1
- # The Schema defines a collection of properties.
2
- # It is responsible for returning its properties' values back to its Graph.
3
-
4
1
  require "net/http"
5
2
  require "json"
6
3
  require "render"
7
- require "render/attribute"
8
- require "render/array_attribute"
9
- require "render/hash_attribute"
10
- require "render/dottable_hash"
4
+ require "render/attributes/array_attribute"
5
+ require "render/attributes/hash_attribute"
6
+ require "render/extensions/dottable_hash"
11
7
 
12
8
  module Render
13
9
  class Schema
@@ -23,14 +19,13 @@ module Render
23
19
  :serialized_data,
24
20
  :rendered_data
25
21
 
26
- # TODO When given { ids: [1,2] }, parental_mapping { ids: id } means to make 2 calls
27
22
  def initialize(definition_or_title)
28
23
  Render.logger.debug("Loading #{definition_or_title}")
29
24
 
30
25
  self.definition = determine_definition(definition_or_title)
31
26
  title_or_default = definition.fetch(:title, DEFAULT_TITLE)
32
27
  self.title = title_or_default.to_sym
33
- self.type = Render.parse_type(definition[:type])
28
+ self.type = Type.parse!(definition[:type])
34
29
  self.universal_title = definition.fetch(:universal_title, nil)
35
30
 
36
31
  if definition.keys.include?(:items)
@@ -61,7 +56,7 @@ module Render
61
56
  self.raw_data = Render.live ? request(endpoint) : explicit_data
62
57
  serialize!(raw_data)
63
58
  yield serialized_data if block_given?
64
- self.rendered_data = DottableHash.new(hash_with_title_prefixes(serialized_data))
59
+ self.rendered_data = Extensions::DottableHash.new(hash_with_title_prefixes(serialized_data))
65
60
  end
66
61
 
67
62
  private
@@ -70,7 +65,7 @@ module Render
70
65
  if (definition_or_title.is_a?(Hash) && !definition_or_title.empty?)
71
66
  definition_or_title
72
67
  else
73
- Render.definition(definition_or_title)
68
+ Definition.find(definition_or_title)
74
69
  end
75
70
  end
76
71
 
@@ -89,7 +84,12 @@ module Render
89
84
  def default_request(endpoint)
90
85
  response = Net::HTTP.get_response(URI(endpoint))
91
86
  if response.kind_of?(Net::HTTPSuccess)
92
- JSON.parse(response.body).recursive_symbolize_keys!
87
+ response = JSON.parse(response.body.to_s)
88
+ if response.is_a?(Array)
89
+ Extensions::SymbolizableArray.new(response).recursively_symbolize_keys!
90
+ else
91
+ Extensions::DottableHash.new(response).recursively_symbolize_keys!
92
+ end
93
93
  else
94
94
  raise Errors::Schema::RequestError.new(endpoint, response)
95
95
  end
@@ -0,0 +1,63 @@
1
+ # Types define classes of data being interpreted. This is especially important in modeling fake data.
2
+ # Add additional types for your specific needs, along with a generator to create fake data for it.
3
+
4
+ require "uuid"
5
+
6
+ module Render
7
+ module Type
8
+ @instances = {}
9
+
10
+ class Enum; end
11
+ class Boolean; end
12
+
13
+ class << self
14
+ attr_accessor :instances
15
+
16
+ def add!(name, klass)
17
+ self.instances.merge!({ formatted_name(name) => klass })
18
+ end
19
+
20
+ def find(name)
21
+ class_for_name(name) || class_for_name(render_name(name))
22
+ end
23
+
24
+ def parse(name, raise_error = false)
25
+ return name unless name.is_a?(String)
26
+ Render::Type.find(name) || Object.const_get(name.capitalize)
27
+ rescue NameError => error
28
+ raise Errors::InvalidType.new(name) if raise_error
29
+ end
30
+
31
+ def parse!(name)
32
+ parse(name, true)
33
+ end
34
+
35
+ private
36
+
37
+ def class_for_name(name)
38
+ instances.each do |(instance_name, instance_class)|
39
+ return instance_class if name.to_s.match(/#{instance_name}/i)
40
+ end
41
+ nil
42
+ end
43
+
44
+ def formatted_name(name)
45
+ name.to_s.downcase.to_sym
46
+ end
47
+
48
+ def render_name(name)
49
+ formatted_name("render_#{name}")
50
+ end
51
+
52
+ def add_render_specific_type!(name)
53
+ add!(render_name(name), Type.const_get(name))
54
+ end
55
+ end
56
+
57
+ add!(:uuid, UUID)
58
+ add!(:number, Float)
59
+ add!(:time, Time)
60
+ add_render_specific_type!(:Boolean)
61
+ add_render_specific_type!(:Enum)
62
+ end
63
+ end
@@ -1,3 +1,3 @@
1
1
  module Render
2
- VERSION = "0.0.4"
2
+ VERSION = "0.0.5"
3
3
  end
data/rakefile.rb CHANGED
@@ -1,10 +1,10 @@
1
1
  require "bundler/gem_tasks"
2
2
  require "rspec/core/rake_task"
3
3
 
4
- task default: :spec
4
+ task default: %w(render:spec)
5
5
 
6
- module RSpec::Core
7
- RakeTask.new(:spec) do |config|
6
+ namespace :render do
7
+ RSpec::Core::RakeTask.new(:spec) do |config|
8
8
  config.verbose = false
9
9
  config.rspec_opts = ["--order rand"]
10
10
  end
data/readme.md CHANGED
@@ -1,55 +1,34 @@
1
1
  # Render
2
2
 
3
- Create and test API requests simply with schemas.
3
+ Render improves the way you work with APIs.
4
4
 
5
- ```ruby
6
- Render.load_schemas!("spec/schemas") # JSON schema directory
7
- Render::Graph.new(:film, { endpoint: "http://films.local/films" }).render
8
- # or stub out schema-specific data
9
- Render.live = false
10
- Render::Graph.new(:film).render
11
- ```
5
+ * [Generate type-specific, dynamic API response data for testing](spec/integration/render/schema_spec.rb) with just a schema (JSON or Ruby)
6
+ * [Make API requests](spec/integration/render/graph_spec.rb) with a URL and a schema
7
+ * Build graphs that [interpret data from one endpoint to call others](spec/integration/render/nested_graph_spec.rb)
12
8
 
13
- *Use with caution* (Render is under initial development) by updating your Gemfile:
9
+ ## Setup
14
10
 
15
- gem "render"
11
+ Update your Gemfile:
16
12
 
17
- ## Caveats
18
-
19
- - Render will modify ::Hash and ::Enumerable to provide symbolize/stringify keys methods.
13
+ gem "render"
20
14
 
21
15
  ## Usage
22
16
 
23
- *Autoload schemas*
24
-
25
- ```ruby
26
- Render.load_schemas!("path/to/json/schemas")
27
- Render::Graph.new(:schema_title, { endpoint: "http://films.local/films" }).render
28
- ```
17
+ Check out examples as part of the [integration tests](spec/integration/render).
29
18
 
30
- *Variable interpolation*
31
-
32
- ```ruby
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"
38
- ```
19
+ ## Caveats
39
20
 
40
- Check out the examples in [integration tests](spec/integration/).
21
+ - Render is under initial development
41
22
 
42
23
  ## Roadmap
43
24
 
44
- 1. Custom HTTP headers (e.g. { pragma: "no-cache", host: "dont_redirect_to_www.site.com" })
45
- 2. Enhanced Attribute metadata (e.g. minlength)
46
- 3. Parental params from root-level array
47
- 4. Deep merge in #render for faux values
25
+ 1. Custom headers (e.g. { pragma: "no-cache", host: "dont_redirect_to_www.site.com" })
26
+ 2. Enhance Attribute metadata (e.g. minlength)
27
+ 3. Enhance Graph to Graph relationships
28
+ 4. Custom request strategy
48
29
 
49
30
  ## Contributing
50
31
 
51
- 1. Fork it
52
- 2. Create your feature branch (`git checkout -b my-new-feature`)
53
- 3. Commit your changes (`git commit -am 'Add some feature'`)
54
- 4. Push to the branch (`git push origin my-new-feature`)
55
- 5. Create new Pull Request
32
+ * Bugs and questions welcomed. If you know (or kind of know) what's going on:
33
+ * Write a failing test, kudos for solving it
34
+ * Put up a [pull request](https://help.github.com/articles/using-pull-requests)
data/render.gemspec CHANGED
@@ -19,12 +19,10 @@ Gem::Specification.new do |spec|
19
19
  spec.require_paths = ["lib"]
20
20
 
21
21
  spec.add_development_dependency "bundler", "~> 1.3"
22
- spec.add_development_dependency "rake"
23
-
22
+ spec.add_development_dependency "rake", "~> 10.1"
24
23
  spec.add_runtime_dependency "uuid", "2.3.7"
25
-
26
- spec.add_development_dependency "rake"
27
- spec.add_development_dependency "debugger"
28
- spec.add_development_dependency "rspec"
29
- spec.add_development_dependency "webmock"
24
+ spec.add_development_dependency "debugger", "~> 1.6"
25
+ spec.add_development_dependency "rspec", "~> 2.14"
26
+ spec.add_development_dependency "webmock", "~> 1.17"
27
+ spec.add_development_dependency "yard", "~> 0.8"
30
28
  end
@@ -2,18 +2,17 @@ module Render
2
2
  describe Attribute do
3
3
  context "generators" do
4
4
  before(:each) do
5
- @original_generators = Render.generators.dup
5
+ @original_generators = Generator.instances.dup
6
6
  Render.stub({ live: false })
7
7
  end
8
8
 
9
9
  after(:each) do
10
- Render.generators = @original_generators
10
+ Generator.instances = @original_generators
11
11
  end
12
12
 
13
13
  it "uses matching generator for #faux_value" do
14
14
  name = "Canada Dry"
15
- generator = Generator.new({ type: String, matcher: %r{.*name.*}, algorithm: proc { name } })
16
- Render.generators << generator
15
+ Generator.create!(String, %r{.*name.*}, proc { name })
17
16
 
18
17
  HashAttribute.new({ name: { type: String } }).default_value.should == name
19
18
  end
@@ -28,7 +28,7 @@ module Render
28
28
 
29
29
  it "generates random number of array elements" do
30
30
  graph = Graph.new(@array_definition)
31
- generated_book_sizes = 5.times.collect { graph.render.books.size }
31
+ generated_book_sizes = 5.times.collect { graph.render!.books.size }
32
32
  generated_book_sizes.compact.size.should > 1
33
33
  end
34
34
 
@@ -36,28 +36,28 @@ module Render
36
36
  it "uses explicit data for hashes" do
37
37
  graph = Render::Graph.new(@hash_definition)
38
38
  green_eggs_and_ham = "Green Eggs and Ham"
39
- data = graph.render({ title: green_eggs_and_ham })
39
+ data = graph.render!({ title: green_eggs_and_ham })
40
40
  data.book.title.should == green_eggs_and_ham
41
41
  end
42
42
 
43
43
  it "uses explicit nil data for hashes" do
44
44
  graph = Render::Graph.new(@hash_definition)
45
- data = graph.render({ title: nil })
45
+ data = graph.render!({ title: nil })
46
46
  data.book.title.should == nil
47
47
  end
48
48
 
49
49
  it "uses explicit data for arrays" do
50
50
  graph = Render::Graph.new(@array_definition)
51
51
  id = UUID.generate
52
- graph.render([id]).books.should == [id]
53
- graph.render([]).books.should == []
52
+ graph.render!([id]).books.should == [id]
53
+ graph.render!([]).books.should == []
54
54
  end
55
55
 
56
56
  it "uses explicit data for nested data" do
57
57
  @array_definition[:items] = @hash_definition
58
58
  nested_graph = Graph.new(@array_definition)
59
59
  tell_tale_heart = "The Tell-Tale Heart"
60
- data = nested_graph.render([{ title: tell_tale_heart }])
60
+ data = nested_graph.render!([{ title: tell_tale_heart }])
61
61
  data.books.size.should == 1
62
62
  data.books.first.title.should == tell_tale_heart
63
63
  end
@@ -0,0 +1,119 @@
1
+ require "render"
2
+
3
+ module Render
4
+ describe Graph do
5
+ before(:each) do
6
+ @schema = {
7
+ title: :films,
8
+ type: Array,
9
+ items: {
10
+ id: { type: "number" }
11
+ }
12
+ }
13
+ end
14
+
15
+ it "requests data from an endpoint" do
16
+ stub_request(:get, "http://films.local").to_return({ body: [{ id: 1 }].to_json })
17
+
18
+ response = Render::Graph.new(@schema, { endpoint: "http://films.local" }).render!
19
+ response.should == { films: [{ id: 1 }] }
20
+ end
21
+
22
+ it "request data from endpoint with explicit values" do
23
+ director_1s_films_request = stub_request(:get, "http://films.local/directors/1/films").to_return({ body: "{}" })
24
+ @schema.merge!({ endpoint: "http://films.local/directors/:id/films" })
25
+
26
+ response = Render::Graph.new(@schema, { id: 1 }).render!
27
+ director_1s_films_request.should have_been_made.once
28
+ end
29
+
30
+ it "requests data from an endpoint specified in schema" do
31
+ stub_request(:get, "http://films.local").to_return({ body: [{ id: 1 }].to_json })
32
+ @schema.merge!({ endpoint: "http://films.local" })
33
+
34
+ response = Render::Graph.new(@schema).render!
35
+ response.should == { films: [{ id: 1 }] }
36
+ end
37
+
38
+ it "interpolates variables into endpoint" do
39
+ stub_request(:get, "http://films.local").to_return({ body: [{ id: 1 }].to_json })
40
+ @schema.merge!({ endpoint: "http://:host" })
41
+
42
+ response = Render::Graph.new(@schema, { host: "films.local" }).render!
43
+ response.should == { films: [{ id: 1 }] }
44
+ end
45
+
46
+ describe "testing" do
47
+ before(:each) do
48
+ @original_live = Render.live
49
+ Render.live = false
50
+ end
51
+
52
+ after(:each) do
53
+ Render.live = @original_live
54
+ end
55
+
56
+ it "creates fake data for testing" do
57
+ schema = {
58
+ title: :film,
59
+ type: Object,
60
+ properties: {
61
+ id: { type: UUID },
62
+ title: { type: String },
63
+ director: {
64
+ type: Object,
65
+ properties: {
66
+ name: { type: String },
67
+ rating: { type: Float }
68
+ }
69
+ },
70
+ genre: {
71
+ enum: ["horror", "action", "sci-fi"]
72
+ },
73
+ tags: {
74
+ type: Array,
75
+ required: true,
76
+ items: {
77
+ type: Object,
78
+ properties: {
79
+ name: { type: String },
80
+ id: { type: Integer }
81
+ }
82
+ }
83
+ }
84
+ }
85
+ }
86
+
87
+ response = Render::Graph.new(schema).render!
88
+ UUID.validate(response.film.id).should be_true
89
+ response.film.title.should be_a(String)
90
+ response.film.director.name.should be_a(String)
91
+ response.film.director.rating.should be_a(Float)
92
+ %w(horror action sci-fi).should include(response.film.genre)
93
+ response.film.tags.first.name.should be_a(String)
94
+ response.film.tags.first.id.should be_a(Integer)
95
+ end
96
+
97
+ it "allows overwriting fake data values" do
98
+ schema = {
99
+ title: :film,
100
+ type: Object,
101
+ properties: {
102
+ id: { type: UUID },
103
+ director: {
104
+ type: Object,
105
+ properties: {
106
+ name: { type: String }
107
+ }
108
+ }
109
+ }
110
+ }
111
+
112
+ her_name = "Kathryn Bigelow"
113
+ response = Render::Graph.new(schema).render!({ director: { name: her_name } })
114
+ response.film.director.name.should == her_name
115
+ end
116
+ end
117
+
118
+ end
119
+ end