render 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/lib/render.rb CHANGED
@@ -10,36 +10,58 @@ require "extensions/hash"
10
10
  require "render/version"
11
11
  require "render/graph"
12
12
  require "render/generator"
13
+ require "logger"
14
+ require "date"
13
15
 
14
16
  module Render
15
17
  @live = true
16
- @schemas = {}
18
+ @definitions = {}
17
19
  @generators = []
20
+ @logger = ::Logger.new($stdout)
21
+ @threading = true
18
22
 
19
23
  class << self
20
- attr_accessor :live, :schemas, :generators
21
-
22
- def load_schemas!(directory)
23
- Dir.glob("#{directory}/**/*.json").each do |schema_file|
24
- parsed_schema = parse_schema(File.read(schema_file))
25
- schema_title = parsed_schema[:title].to_sym
26
- # TODO Throw an error in the event of conflicts?
27
- self.schemas[schema_title] = parsed_schema
24
+ attr_accessor :live,
25
+ :definitions,
26
+ :generators,
27
+ :logger,
28
+ :threading
29
+
30
+ def threading?
31
+ threading == true
32
+ end
33
+
34
+ def load_definitions!(directory)
35
+ Dir.glob("#{directory}/**/*.json").each do |definition_file|
36
+ logger.info("Reading #{definition_file} definition")
37
+ definition_string = File.read(definition_file)
38
+ parsed_definition = JSON.parse(definition_string).recursive_symbolize_keys!
39
+ load_definition!(parsed_definition)
28
40
  end
29
41
  end
30
42
 
31
- def parse_schema(json)
32
- JSON.parse(json).recursive_symbolize_keys!
43
+ def load_definition!(definition)
44
+ title = definition.fetch(:universal_title, definition.fetch(:title)).to_sym
45
+ self.definitions[title] = definition
33
46
  end
34
47
 
48
+ def definition(title)
49
+ definitions.fetch(title.to_sym)
50
+ rescue KeyError => error
51
+ raise Errors::DefinitionNotFound.new(title)
52
+ end
53
+
54
+ # TODO better type parsing
35
55
  def parse_type(type)
36
- if type.is_a?(String)
37
- return UUID if type == "uuid"
38
- return Boolean if type == "boolean"
39
- Object.const_get(type.capitalize) # TODO better type parsing
40
- else
41
- type
42
- end
56
+ return type unless type.is_a?(String)
57
+
58
+ return UUID if type.match(/uuid/i)
59
+ return Boolean if type.match(/boolean/i)
60
+ return Float if type.match(/number/i)
61
+ return Time if type.match(/date.*time/i)
62
+ Object.const_get(type.capitalize)
63
+ rescue NameError => error
64
+ raise Errors::InvalidType.new(type)
43
65
  end
44
66
  end
45
67
 
data/readme.md CHANGED
@@ -39,6 +39,12 @@ graph.render({ id: "an-id" }) # makes request to "http://films.local/films/an-id
39
39
 
40
40
  Check out the examples in [integration tests](spec/integration/).
41
41
 
42
+ ## Roadmap
43
+
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
42
48
 
43
49
  ## Contributing
44
50
 
@@ -17,12 +17,12 @@ module Render
17
17
  generator = Generator.new({ type: String, matcher: %r{.*name.*}, algorithm: proc { name } })
18
18
  Render.generators << generator
19
19
 
20
- Attribute.new({ name: { type: String } }).default_value.should == name
20
+ HashAttribute.new({ name: { type: String } }).default_value.should == name
21
21
  end
22
22
 
23
23
  it "uses really bare-boned type if no generator is found" do
24
24
  bare_boned_string = "A String"
25
- Attribute.new({ foo: { type: String } }).default_value.should == bare_boned_string
25
+ HashAttribute.new({ foo: { type: String } }).default_value.should == bare_boned_string
26
26
  end
27
27
  end
28
28
  end
@@ -0,0 +1,9 @@
1
+ require "render/graph"
2
+
3
+ module Render
4
+ describe Graph do
5
+ describe "relationships" do
6
+ #
7
+ end
8
+ end
9
+ end
@@ -10,10 +10,10 @@ module Render
10
10
  schema = {
11
11
  title: "person",
12
12
  type: Object,
13
- attributes: {
13
+ properties: {
14
14
  contact: {
15
15
  type: Object,
16
- attributes: {
16
+ properties: {
17
17
  name: { type: String },
18
18
  phone: { type: String }
19
19
  }
@@ -32,7 +32,7 @@ module Render
32
32
  }
33
33
  }
34
34
 
35
- Schema.new(schema).render(data).should == {
35
+ Schema.new(schema).render!(data).should == {
36
36
  person: {
37
37
  contact: {
38
38
  name: contact_name,
@@ -46,18 +46,18 @@ module Render
46
46
  schema = {
47
47
  title: "people",
48
48
  type: Array,
49
- elements: {
49
+ items: {
50
50
  title: :person,
51
51
  type: Object,
52
- attributes: {
52
+ properties: {
53
53
  name: { type: String },
54
54
  nicknames: {
55
55
  title: "nicknames",
56
56
  type: Array,
57
- elements: {
57
+ items: {
58
58
  title: :formalized_name,
59
59
  type: Object,
60
- attributes: {
60
+ properties: {
61
61
  name: { type: String },
62
62
  age: { type: Integer }
63
63
  }
@@ -82,7 +82,7 @@ module Render
82
82
  }
83
83
  people = [zissou, ned]
84
84
 
85
- Schema.new(schema).render(people).should == {
85
+ Schema.new(schema).render!(people).should == {
86
86
  people: [{
87
87
  name: "Steve Zissou",
88
88
  nicknames: [
@@ -6,81 +6,52 @@ module Render
6
6
  Render.stub({ live: false })
7
7
  end
8
8
 
9
- it "parses hash data" do
10
- schema = Schema.new({
11
- title: "television",
12
- type: Object,
13
- attributes: {
14
- brand: { type: String }
15
- }
16
- })
17
-
18
- brand_name = "Sony"
19
- response = { brand: brand_name }
20
-
21
- schema.render(response).should == {
22
- television: { brand: brand_name }
23
- }
24
- end
25
-
26
- it "parses simple arrays" do
27
- schema = Schema.new({
28
- title: "televisions",
29
- type: Array,
30
- elements: {
31
- type: UUID
32
- }
33
- })
34
-
35
- television_ids = rand(10).times.collect { UUID.generate }
36
-
37
- schema.render(television_ids).should == {
38
- televisions: television_ids
39
- }
40
- end
41
-
42
- it "parses arrays of objects" do
43
- schema = Schema.new({
44
- title: :televisions,
45
- type: Array,
46
- elements: {
47
- title: :television,
9
+ describe "#serialize!" do
10
+ it "returns data from hashes" do
11
+ definition = {
12
+ title: "film",
48
13
  type: Object,
49
- attributes: {
50
- brand: { type: String }
14
+ properties: {
15
+ title: {
16
+ type: String
17
+ }
51
18
  }
52
19
  }
53
- })
54
-
55
- brand_1, brand_2 = *%w(Sony Samsung)
56
- response = [{ brand: brand_1 }, { brand: brand_2 }]
20
+ data = { title: "a name" }
21
+ Schema.new(definition).serialize!(data).should == data
22
+ end
57
23
 
58
- schema.render(response).should == {
59
- televisions: [{ brand: brand_1 }, { brand: brand_2 }]
60
- }
61
- end
62
24
 
63
- it "parses nested object data" do
64
- schema = Schema.new({
65
- title: :television,
66
- type: Object,
67
- attributes: {
68
- brand: {
69
- title: :brand,
25
+ it "returns data from arrays" do
26
+ definition = {
27
+ title: "names",
28
+ type: Array,
29
+ items: {
30
+ type: String
31
+ }
32
+ }
33
+ schema = Schema.new(definition)
34
+ names = ["bob", "bill"]
35
+ schema.serialize!(names).should == names
36
+ end
37
+
38
+ it "returns data from arrays of schemas" do
39
+ definition = {
40
+ title: "films",
41
+ type: Array,
42
+ items: {
70
43
  type: Object,
71
- attributes: {
72
- name: { type: String }
44
+ properties: {
45
+ id: { type: UUID }
73
46
  }
74
47
  }
75
48
  }
76
- })
77
-
78
- brand_name = "Sony"
79
- response = { brand: { name: brand_name } }
80
49
 
81
- schema.render(response).should == {
82
- television: { brand: { name: brand_name } }
83
- }
50
+ the_id = UUID.generate
51
+ films = [{ id: the_id }]
52
+ schema = Schema.new(definition)
53
+ schema.serialize!(films).should == films
54
+ end
84
55
  end
85
56
  end
86
57
  end
@@ -2,11 +2,11 @@ require "render"
2
2
 
3
3
  describe Render do
4
4
  before(:all) do
5
- Render.load_schemas!(Helpers::SCHEMA_DIRECTORY)
5
+ Render.load_definitions!(Helpers::SCHEMA_DIRECTORY)
6
6
  end
7
7
 
8
8
  after(:all) do
9
- Render.schemas = {}
9
+ Render.definitions = {}
10
10
  end
11
11
 
12
12
  describe "request" do
@@ -15,10 +15,13 @@ describe Render do
15
15
  @film_endpoint = "http://films.local/films/:id"
16
16
  @aquatic_id = UUID.generate
17
17
  @darjeeling_id = UUID.generate
18
+ @aquatic_name = "The Life Aquatic with Steve Zissou"
19
+ @darjeeling_name = "The Darjeeling Limited"
18
20
  end
19
21
 
20
22
  it "returns structured data for nested queries" do
21
- stub_request(:get, @films_endpoint).to_return({ body: [{ id: @aquatic_id }, { id: @darjeeling_id }].to_json })
23
+ films_index_response = [{ id: @aquatic_id }, { id: @darjeeling_id }]
24
+ stub_request(:get, @films_endpoint).to_return({ body: films_index_response.to_json })
22
25
 
23
26
  aquatic_name = "The Life Aquatic with Steve Zissou"
24
27
  aquatic_uri = @film_endpoint.gsub(":id", @aquatic_id)
@@ -29,48 +32,53 @@ describe Render do
29
32
  stub_request(:get, darjeeling_uri).to_return({ body: { name: darjeeling_name }.to_json })
30
33
 
31
34
  options = {
32
- graphs: [Render::Graph.new(:film, { endpoint: @film_endpoint, relationships: { id: :id }})],
35
+ graphs: [Render::Graph.new(:films_show, { endpoint: @film_endpoint, relationships: { id: :id }})],
33
36
  endpoint: @films_endpoint
34
37
  }
35
- graph = Render::Graph.new(:films, options)
38
+ graph = Render::Graph.new(:films_index, options)
36
39
  graph.render.should == {
37
- films: [
38
- { name: aquatic_name, year: nil },
39
- { name: darjeeling_name, year: nil }
40
+ films_index: {
41
+ films: films_index_response
42
+ },
43
+ films_show: [
44
+ { film: { name: aquatic_name, year: nil } },
45
+ { film: { name: darjeeling_name, year: nil } }
40
46
  ]
41
47
  }
48
+ graph.rendered_data.films_show.first.film.name.should == aquatic_name
42
49
  end
43
50
 
44
51
  it "makes subsequent calls from archetype array data" do
45
- pending "Simple arrays need to be able to make multiple calls"
46
-
47
52
  stub_request(:get, @films_endpoint).to_return({ body: [@aquatic_id, @darjeeling_id].to_json })
48
53
 
49
- aquatic = @film_endpoint.gsub("id", @aquatic_id)
54
+ aquatic = @film_endpoint.gsub(":id", @aquatic_id)
50
55
  stub_request(:get, aquatic).to_return({ body: { name: @aquatic_name }.to_json })
51
56
 
52
- darjeeling = @film_endpoint.gsub("id", @darjeeling_id)
57
+ darjeeling = @film_endpoint.gsub(":id", @darjeeling_id)
53
58
  stub_request(:get, darjeeling).to_return({ body: { name: @darjeeling_name }.to_json })
54
59
 
55
- films = Representation::Schema.new({
60
+ films = Render::Schema.new({
56
61
  title: :films,
57
62
  type: Array,
58
- elements: {
63
+ items: {
59
64
  type: UUID
60
65
  }
61
66
  })
62
67
 
63
- film = Representation::Schema.new({
68
+ film = Render::Schema.new({
64
69
  title: :film,
65
70
  type: Object,
66
- attributes: {
67
- title: { type: String }
71
+ properties: {
72
+ name: { type: String }
68
73
  }
69
74
  })
70
75
 
71
- films = Representation::Graph.new(films, { endpoint: @films_endpoint })
72
- films.graphs << Representation::Graph.new(film, { endpoint: @film_endpoint, relationships: { films: :id } })
73
- films.render.should == {}
76
+ films = Render::Graph.new(films, { endpoint: @films_endpoint })
77
+ films.graphs << Render::Graph.new(film, { endpoint: @film_endpoint, relationships: { id: :id } })
78
+ films.render.film.should =~ [
79
+ { name: @aquatic_name },
80
+ { name: @darjeeling_name }
81
+ ]
74
82
  end
75
83
 
76
84
  end
@@ -2,11 +2,11 @@ require "render"
2
2
 
3
3
  describe Render do
4
4
  before(:all) do
5
- Render.load_schemas!(Helpers::SCHEMA_DIRECTORY)
5
+ Render.load_definitions!(Helpers::SCHEMA_DIRECTORY)
6
6
  end
7
7
 
8
8
  after(:all) do
9
- Render.schemas = {}
9
+ Render.definitions = {}
10
10
  end
11
11
 
12
12
  before(:each) do
@@ -24,8 +24,8 @@ describe Render do
24
24
  aquatic_uri = @films_endpoint.gsub(":secret_code", "secret_code=#{@secret_code}")
25
25
  stub_request(:get, aquatic_uri).to_return({ body: [{ id: @film_id }].to_json })
26
26
 
27
- graph = Render::Graph.new(:films, { endpoint: @films_endpoint, secret_code: @secret_code })
28
- graph.render.should == { films: [{ id: @film_id }] }
27
+ graph = Render::Graph.new(:films_index, { endpoint: @films_endpoint, secret_code: @secret_code })
28
+ graph.render.should == { films_index: { films: [{ id: @film_id }] } }
29
29
  end
30
30
 
31
31
  it "returns structured data for specific resources" do
@@ -33,8 +33,8 @@ describe Render do
33
33
  aquatic_uri = @film_endpoint.gsub(":id", id).gsub(":secret_code", "secret_code=#{@secret_code}")
34
34
  stub_request(:get, aquatic_uri).to_return({ body: { name: @film_name }.to_json })
35
35
 
36
- graph = Render::Graph.new(:film, { id: id, endpoint: @film_endpoint, secret_code: @secret_code })
37
- graph.render.should == { film: { name: @film_name, year: nil } }
36
+ graph = Render::Graph.new(:films_show, { id: id, endpoint: @film_endpoint, secret_code: @secret_code })
37
+ graph.render.should == { films_show: { film: { name: @film_name, year: nil } } }
38
38
  end
39
39
  end
40
40
 
@@ -44,22 +44,23 @@ describe Render do
44
44
  end
45
45
 
46
46
  it "use meaningful values" do
47
- response = Render::Graph.new(:film).render({ name: @film_name })
47
+ response = Render::Graph.new(:films_show).render({ name: @film_name })
48
48
 
49
49
  stub_request(:post, "http://films.local/create").to_return({ body: response.to_json })
50
- response = post_film(:anything)["film"]
50
+ response = post_film(:anything)["films_show"]["film"]
51
51
 
52
52
  response["name"].should be_a(String)
53
53
  response["year"].should be_a(Integer)
54
54
  end
55
55
 
56
56
  it "allows users to specify specific values" do
57
- response = Render::Graph.new(:film).render({ name: @film_name })
57
+ response = Render::Schema.new(:films_show).render!({ name: @film_name })
58
58
 
59
59
  data = { name: @film_name }.to_json
60
- stub_request(:post, "http://films.local/create").with({ body: data }).to_return({ body: response.to_json })
61
- response = post_film(data)["film"]
60
+ request = stub_request(:post, "http://films.local/create").with({ body: data }).to_return({ body: response.to_json })
62
61
 
62
+ response = post_film(data)["films_show"]["film"]
63
+ request.should have_been_made
63
64
  response["name"].should == @film_name
64
65
  end
65
66
  end
@@ -1,7 +1,8 @@
1
1
  {
2
+ "universal_title": "films_show",
2
3
  "title": "film",
3
4
  "type": "object",
4
- "attributes": {
5
+ "properties": {
5
6
  "name": { "type": "string" },
6
7
  "year": { "type": "integer" }
7
8
  }
@@ -1,10 +1,11 @@
1
1
  {
2
+ "universal_title": "films_index",
2
3
  "title": "films",
3
4
  "type": "array",
4
- "elements": {
5
+ "items": {
5
6
  "title": "film",
6
7
  "type" : "object",
7
- "attributes" : {
8
+ "properties" : {
8
9
  "id" : {
9
10
  "type": "uuid"
10
11
  }
data/spec/support.rb CHANGED
@@ -2,6 +2,9 @@ require File.expand_path("../../initialize", __FILE__)
2
2
  require "webmock/rspec"
3
3
  require "support/helpers"
4
4
 
5
+ Render.logger = Logger.new("/dev/null")
6
+ Render.threading = false
7
+
5
8
  RSpec.configure do |config|
6
9
  #
7
10
  end
@@ -0,0 +1,48 @@
1
+ require "render/array_attribute"
2
+
3
+ module Render
4
+ describe ArrayAttribute do
5
+ describe "#initialize" do
6
+ it "sets name to title for faux_value generators to match" do
7
+ ArrayAttribute.new({ title: "ids", items: { type: UUID } }).name.should == :ids
8
+ end
9
+
10
+ describe "#format" do
11
+ it "is set from options" do
12
+ ArrayAttribute.new({ items: { type: String, format: UUID } }).format.should == UUID
13
+ end
14
+
15
+ it "is nil for indeterminable types" do
16
+ ArrayAttribute.new({ items: { type: String, format: "random-iso-format" } }).format.should == nil
17
+ end
18
+ end
19
+ end
20
+
21
+ describe "archetype" do
22
+ it "returns only a value" do
23
+ id = UUID.generate
24
+ attribute = ArrayAttribute.new({ items: { format: UUID } })
25
+ attribute.serialize([id]).should == [id]
26
+ end
27
+ end
28
+
29
+ describe "#faux_data" do
30
+ before(:each) do
31
+ Render.stub({ live: false })
32
+ @attribute = ArrayAttribute.new({ items: { type: Float, required: true } })
33
+ end
34
+
35
+ it "uses explicit value for faux data" do
36
+ explicit_data = [rand(10.0)]
37
+ @attribute.serialize(explicit_data).should == explicit_data
38
+ end
39
+
40
+ it "generates fake number of elements" do
41
+ faux_data = @attribute.serialize
42
+ faux_data.size.should be_between(0, ArrayAttribute::FAUX_DATA_UPPER_LIMIT)
43
+ faux_data.sample.class.should == Float
44
+ end
45
+ end
46
+
47
+ end
48
+ end
@@ -127,12 +127,6 @@ module Render
127
127
  end
128
128
 
129
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
130
  it "returns dottable_hashs in lieu of hashes" do
137
131
  @dottable_hash["nested_hash"] = { "foo" => "bar" }
138
132
  @dottable_hash.fetch("nested_hash").class.should == DottableHash
@@ -143,6 +137,9 @@ module Render
143
137
  @dottable_hash.fetch("foo").should == "bar"
144
138
  end
145
139
 
140
+ it "accepts default value" do
141
+ DottableHash.new({ baz: "buz" }).fetch(:foo, :bar).should == :bar
142
+ end
146
143
  end
147
144
 
148
145
  describe "#fetch_path[!]" do
@@ -6,7 +6,7 @@ module Render
6
6
  expect { Generator }.to_not raise_error
7
7
  end
8
8
 
9
- describe "attributes" do
9
+ describe "properties" do
10
10
  before(:each) do
11
11
  @mandatory_options = { algorithm: proc {} }
12
12
  end
@@ -15,7 +15,7 @@ module Render
15
15
  Generator.new(@mandatory_options.merge({ type: String })).type.should == String
16
16
  end
17
17
 
18
- it "has a matcher to only be used on specific attributes" do
18
+ it "has a matcher to only be used on specific properties" do
19
19
  matcher = %r{.*name.*}
20
20
  Generator.new(@mandatory_options.merge({ matcher: matcher })).matcher.should == matcher
21
21
  end