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/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- render (0.0.2)
4
+ render (0.0.3)
5
5
  uuid (= 2.3.7)
6
6
 
7
7
  GEM
@@ -11,10 +11,10 @@ GEM
11
11
  columnize (0.3.6)
12
12
  crack (0.4.1)
13
13
  safe_yaml (~> 0.9.0)
14
- debugger (1.5.0)
14
+ debugger (1.6.1)
15
15
  columnize (>= 0.3.1)
16
16
  debugger-linecache (~> 1.2.0)
17
- debugger-ruby_core_source (~> 1.2.0)
17
+ debugger-ruby_core_source (~> 1.2.3)
18
18
  debugger-linecache (1.2.0)
19
19
  debugger-ruby_core_source (1.2.3)
20
20
  diff-lcs (1.2.4)
@@ -0,0 +1,52 @@
1
+ require "render/attribute"
2
+
3
+ module Render
4
+ class ArrayAttribute < Attribute
5
+ FAUX_DATA_UPPER_LIMIT = 5.freeze
6
+
7
+ attr_accessor :archetype
8
+
9
+ def initialize(options = {})
10
+ super
11
+
12
+ self.name = options.fetch(:title, :render_array_attribute_untitled).to_sym
13
+ options = options[:items]
14
+ self.type = Render.parse_type(options[:type])
15
+ self.format = Render.parse_type(options[:format]) rescue nil
16
+ self.enums = options[:enum]
17
+
18
+ if options.keys.include?(:properties)
19
+ self.schema = Schema.new(options)
20
+ else
21
+ self.archetype = true
22
+ end
23
+ end
24
+
25
+ def serialize(explicit_values = nil)
26
+ explicit_values = faux_array_data if (Render.live == false && explicit_values.nil?)
27
+ if archetype
28
+ explicit_values.collect do |value|
29
+ value || default_value
30
+ end
31
+ else
32
+ explicit_values.collect do |value|
33
+ schema.serialize!(value)
34
+ end
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ def faux_array_data
41
+ rand(lower_limit..FAUX_DATA_UPPER_LIMIT).times.collect do
42
+ archetype ? nil : {}
43
+ end
44
+ end
45
+
46
+ def lower_limit
47
+ # lower_limit = (required ? 1 : 0)
48
+ 1
49
+ end
50
+
51
+ end
52
+ end
@@ -1,89 +1,52 @@
1
1
  # An Attribute represents a specific key and value as part of a Schema.
2
- # It is responsible for casting its value and generating sample (default) data when not in live-mode.
2
+ # It is responsible for casting its value and generating sample data.
3
3
 
4
4
  require "uuid"
5
5
 
6
6
  module Render
7
7
  class Attribute
8
- attr_accessor :name, :type, :schema, :archetype, :enums
8
+ SCHEMA_IDENTIFIERS = [:properties, :items].freeze
9
9
 
10
- # Initialize take a few different Hashes
11
- # { name: { type: UUID } } for standard Hashes to be aligned
12
- # { type: UUID } for elements in an array to be parsed
13
- # { name: { type: Object, attributes { ... } } for nested schemas
14
- def initialize(options = {})
15
- self.name = options.keys.first
16
- if (options.keys.first == :type && !options[options.keys.first].is_a?(Hash)) # todo there has to be a better way to do this
17
- initialize_as_archetype(options)
18
- else
19
- self.type = Render.parse_type(options[name][:type])
20
- self.enums = options[name][:enum]
21
- initialize_schema!(options) if schema_value?(options)
22
- end
23
- end
24
-
25
- def initialize_as_archetype(options)
26
- self.type = Render.parse_type(options[:type])
27
- self.enums = options[:enum]
28
- self.archetype = true
29
- end
10
+ attr_accessor :name,
11
+ :type,
12
+ :schema,
13
+ :enums,
14
+ :format,
15
+ :required
30
16
 
31
- def initialize_schema!(options)
32
- schema_options = {
33
- title: name,
34
- type: type
35
- }
36
-
37
- definition = options[name]
38
- if definition.keys.include?(:attributes)
39
- schema_options.merge!({ attributes: definition[:attributes] })
40
- else
41
- schema_options.merge!({ elements: definition[:elements] })
42
- end
43
-
44
- self.schema = Schema.new(schema_options)
45
- end
46
-
47
- def serialize(explicit_value)
48
- if archetype
49
- explicit_value || default_value
50
- else
51
- to_hash(explicit_value)
52
- end
17
+ def initialize(options = {})
18
+ Render.logger.debug("Initializing attribute #{options}")
19
+ self.required = false
53
20
  end
54
21
 
55
- def to_hash(explicit_value = nil)
56
- value = if schema_value?
57
- schema.serialize(explicit_value)
58
- else
59
- # TODO guarantee type for explicit value
60
- (explicit_value || default_value)
61
- end
62
-
63
- { name.to_sym => value }
22
+ def bias_type
23
+ format || type
64
24
  end
65
25
 
66
26
  def default_value
67
27
  Render.live ? nil : faux_value
68
28
  end
69
29
 
70
- def schema_value?(options = {})
71
- return true if schema
72
- options[name].is_a?(Hash) && (options[name][:attributes] || options[name][:elements])
30
+ def nested_schema?(options = {})
31
+ options.any? { |name, value| SCHEMA_IDENTIFIERS.include?(name) }
73
32
  end
74
33
 
75
34
  private
76
35
 
36
+ # TODO implement better #faux_value
77
37
  def faux_value
78
- # TODO implement better #faux_value
79
38
  return enums.sample if enums
80
39
  return generator_value if generator_value # todo optimize generator_value call
81
40
 
82
- case(type.name)
41
+ case(bias_type.name)
83
42
  when("String") then "A String"
84
43
  when("Integer") then rand(1000)
85
44
  when("UUID") then UUID.generate
86
45
  when("Boolean") then [true, false].sample
46
+ when("Float") then rand(0.1..99)
47
+ when("Time")
48
+ time = Time.now
49
+ (type == String) ? time.to_s : time
87
50
  end
88
51
  end
89
52
 
@@ -93,7 +56,6 @@ module Render
93
56
  end
94
57
  generator.algorithm.call if generator
95
58
  end
96
-
97
59
  end
98
- end
99
60
 
61
+ end
@@ -59,9 +59,8 @@ module Render
59
59
  end
60
60
  end
61
61
 
62
- def fetch(key)
62
+ def fetch(key, *args)
63
63
  key = key.to_sym
64
- raise KeyError.new("Invalid key: #{key}") unless has_key?(key)
65
64
  super
66
65
  end
67
66
 
data/lib/render/errors.rb CHANGED
@@ -1,5 +1,17 @@
1
1
  module Render
2
2
  module Errors
3
+ class InvalidType < StandardError
4
+ attr_accessor :name
5
+
6
+ def initialize(name)
7
+ self.name = name
8
+ end
9
+
10
+ def to_s
11
+ "Cannot parse type: #{name}."
12
+ end
13
+ end
14
+
3
15
  class Generator
4
16
  class MalformedAlgorithm < StandardError
5
17
  attr_accessor :algorithm
@@ -23,24 +35,24 @@ module Render
23
35
  end
24
36
 
25
37
  def to_s
26
- "No value for key #{config_key} found in config or parental_attributes."
38
+ "No value for key #{config_key} found in config or parental_properties."
27
39
  end
28
40
  end
29
41
  end
30
42
 
31
- class Schema
32
- class NotFound < StandardError
33
- attr_accessor :title
43
+ class DefinitionNotFound < StandardError
44
+ attr_accessor :title
34
45
 
35
- def initialize(title)
36
- self.title = title
37
- end
46
+ def initialize(title)
47
+ self.title = title
48
+ end
38
49
 
39
- def to_s
40
- "Schema with title #{title} is not loaded"
41
- end
50
+ def to_s
51
+ "Schema with title #{title} is not loaded"
42
52
  end
53
+ end
43
54
 
55
+ class Schema
44
56
  class RequestError < StandardError
45
57
  attr_accessor :endpoint, :response
46
58
 
@@ -4,9 +4,9 @@ module Render
4
4
  class Generator
5
5
  attr_accessor :type, :matcher, :algorithm
6
6
 
7
- def initialize(attributes = {})
8
- attributes.symbolize_keys!
9
- %w(type matcher algorithm).each { |attribute| self.__send__("#{attribute}=", attributes[attribute.to_sym]) }
7
+ def initialize(properties = {})
8
+ properties.symbolize_keys!
9
+ %w(type matcher algorithm).each { |attribute| self.__send__("#{attribute}=", properties[attribute.to_sym]) }
10
10
  raise Errors::Generator::MalformedAlgorithm.new(algorithm) if !algorithm.respond_to?(:call)
11
11
  end
12
12
 
data/lib/render/graph.rb CHANGED
@@ -17,24 +17,18 @@ module Render
17
17
  :raw_endpoint,
18
18
  :relationships,
19
19
  :graphs,
20
- :params,
21
- :parental_params, # TODO rename this to inherited_params
22
- :config
23
-
24
- def initialize(schema, attributes = {})
25
- self.schema = if schema.is_a?(Symbol)
26
- Schema.new(schema)
27
- else
28
- schema
29
- end
30
-
31
- self.relationships = (attributes.delete(:relationships) || {})
32
- self.raw_endpoint = (attributes.delete(:endpoint) || "")
33
- self.graphs = (attributes.delete(:graphs) || [])
34
- self.config = attributes
35
- self.parental_params = {}
36
-
37
- initialize_params!
20
+ :inherited_data,
21
+ :config,
22
+ :rendered_data
23
+
24
+ def initialize(schema_or_definition, options = {})
25
+ self.schema = determine_schema(schema_or_definition)
26
+ self.relationships = (options.delete(:relationships) || {})
27
+ self.graphs = (options.delete(:graphs) || [])
28
+ self.raw_endpoint = options.delete(:endpoint).to_s
29
+ self.config = options
30
+
31
+ self.inherited_data = {}
38
32
  end
39
33
 
40
34
  def endpoint
@@ -55,44 +49,60 @@ module Render
55
49
  uri.to_s
56
50
  end
57
51
 
58
- def render(inherited_attributes = {})
59
- calculate_parental_params!(inherited_attributes)
60
- graph_attributes = schema.render(inherited_attributes.merge(parental_params.merge({ endpoint: endpoint })))
52
+ def render(inherited_properties = {})
53
+ self.inherited_data = inherited_properties
61
54
 
62
- graph = graphs.inject(graph_attributes) do |attributes, nested_graph|
63
- threads = []
64
- # TODO threading should be configured so people may also think about Thread.abort_on_transaction!
65
- threads << Thread.new do
66
- title = schema.title.to_sym
67
- parent_data = attributes[title]
68
- nested_graph_data = if parent_data.is_a?(Array)
69
- data = parent_data.collect do |element|
70
- nested_graph.render(element)
55
+ graph_data = DottableHash.new
56
+ inherited_data = relationship_data_from_parent.merge({ endpoint: endpoint })
57
+ rendered_data = schema.render!(inherited_data) do |parent_data|
58
+ loop_with_configured_threading(graphs) do |graph|
59
+ if parent_data.is_a?(Array)
60
+ graph_data[graph.title] = parent_data.inject([]) do |nested_data, element|
61
+ nested_data << graph.render(element)[graph.title]
71
62
  end
72
- key = data.first.keys.first
73
- attributes[title] = data.collect { |d| d[key] }
74
63
  else
75
- data = nested_graph.render(parent_data)
76
- parent_data.merge!(data)
64
+ nested_data = graph.render(parent_data)
65
+ graph_data.merge!(nested_data)
66
+ end
67
+ end
68
+ end
69
+
70
+ self.rendered_data = graph_data.merge!(rendered_data)
71
+ end
72
+
73
+ def loop_with_configured_threading(elements)
74
+ if Render.threading?
75
+ threads = []
76
+ elements.each do |element|
77
+ threads << Thread.new do
78
+ yield element
77
79
  end
78
80
  end
79
81
  threads.collect(&:join)
80
- attributes
82
+ else
83
+ elements.each do |element|
84
+ yield element
85
+ end
81
86
  end
82
- DottableHash.new(graph)
87
+ end
88
+
89
+ def title
90
+ schema.universal_title || schema.title
83
91
  end
84
92
 
85
93
  private
86
94
 
87
- def initialize_params!
88
- self.params = raw_endpoint.scan(PARAMS).flatten.inject({}) do |params, param|
89
- params.merge({ param.to_sym => nil })
95
+ def determine_schema(schema_or_definition)
96
+ if schema_or_definition.is_a?(Schema)
97
+ schema_or_definition
98
+ else
99
+ Schema.new(schema_or_definition)
90
100
  end
91
101
  end
92
102
 
93
- def calculate_parental_params!(inherited)
94
- self.parental_params = relationships.inject(inherited) do |attributes, (parent_key, child_key)|
95
- attributes.merge({ child_key => inherited[parent_key] })
103
+ def relationship_data_from_parent
104
+ relationships.inject({}) do |data, (parent_key, child_key)|
105
+ data.merge({ child_key => value_from_inherited_data(child_key) })
96
106
  end
97
107
  end
98
108
 
@@ -101,7 +111,18 @@ module Render
101
111
  end
102
112
 
103
113
  def param_value(key)
104
- parental_params[key] || config[key] || raise(Errors::Graph::EndpointKeyNotFound.new(key))
114
+ value_from_inherited_data(key) || config[key] || raise(Errors::Graph::EndpointKeyNotFound.new(key))
115
+ end
116
+
117
+ def value_from_inherited_data(key)
118
+ relationships.each do |parent_key, child_key|
119
+ if !inherited_data.is_a?(Hash)
120
+ return inherited_data
121
+ elsif (child_key == key)
122
+ return inherited_data.fetch(parent_key)
123
+ end
124
+ end
125
+ nil
105
126
  end
106
127
 
107
128
  end
@@ -0,0 +1,38 @@
1
+ require "render"
2
+ require "render/attribute"
3
+
4
+ module Render
5
+ class HashAttribute < Attribute
6
+ def initialize(options = {})
7
+ super
8
+
9
+ self.name = options.keys.first
10
+ options = options[name]
11
+ self.type = Render.parse_type(options[:type])
12
+ self.format = Render.parse_type(options[:format]) rescue nil
13
+ self.enums = options[:enum]
14
+
15
+ initialize_schema!(options) if nested_schema?(options)
16
+ end
17
+
18
+ def initialize_schema!(options)
19
+ schema_options = {
20
+ title: name,
21
+ type: bias_type
22
+ }
23
+
24
+ self.schema = Schema.new(schema_options.merge(options))
25
+ end
26
+
27
+ def serialize(explicit_value)
28
+ if !!schema
29
+ value = schema.serialize!(explicit_value)
30
+ { name.to_sym => value }
31
+ else
32
+ value = (explicit_value || default_value)
33
+ { name.to_sym => value }
34
+ end
35
+ end
36
+
37
+ end
38
+ end
data/lib/render/schema.rb CHANGED
@@ -1,114 +1,100 @@
1
- # The Schema defines a collection of attributes.
2
- # It is responsible for returning its attributes' values back to its Graph.
1
+ # The Schema defines a collection of properties.
2
+ # It is responsible for returning its properties' values back to its Graph.
3
3
 
4
4
  require "net/http"
5
5
  require "json"
6
6
  require "render"
7
7
  require "render/attribute"
8
+ require "render/array_attribute"
9
+ require "render/hash_attribute"
10
+ require "render/dottable_hash"
8
11
 
9
12
  module Render
10
13
  class Schema
14
+ DEFAULT_TITLE = "untitled".freeze
15
+
11
16
  attr_accessor :title,
12
17
  :type,
13
- :attributes,
14
- :schema
18
+ :definition,
19
+ :array_attribute,
20
+ :hash_attributes,
21
+ :universal_title,
22
+ :raw_data,
23
+ :serialized_data,
24
+ :rendered_data
25
+
26
+ # TODO When given { ids: [1,2] }, parental_mapping { ids: id } means to make 2 calls
27
+ def initialize(definition_or_title)
28
+ Render.logger.debug("Loading #{definition_or_title}")
15
29
 
16
- # The schema need to know where its getting a value from
17
- # an Attribute, e.g. { foo: "bar" } => { foo: { type: String } }
18
- # an Archetype, e.g. [1,2,3] => { type: Integer } # could this be a pass-through?
19
- # an Attribute-Schema, e.g. { foo: { bar: "baz" } } => { foo: { type: Object, attributes: { bar: { type: String } } }
20
- # an Attribute-Array, e.g. [{ foo: "bar" }] => { type: Array, elements: { type: Object, attributes: { foo: { type: String } } } }
21
- # and we need to identify when given { ids: [1,2] }, parental_mapping { ids: id } means to make 2 calls
22
- def initialize(schema_or_title)
23
- self.schema = schema_or_title.is_a?(Hash) ? schema_or_title : find_schema(schema_or_title)
24
- self.title = schema[:title]
25
- self.type = Render.parse_type(schema[:type])
30
+ self.definition = determine_definition(definition_or_title)
31
+ title_or_default = definition.fetch(:title, DEFAULT_TITLE)
32
+ self.title = title_or_default.to_sym
33
+ self.type = Render.parse_type(definition[:type])
34
+ self.universal_title = definition.fetch(:universal_title, nil)
26
35
 
27
- if array_of_schemas?(schema[:elements])
28
- self.attributes = [Attribute.new({ elements: schema[:elements] })]
36
+ if definition.keys.include?(:items)
37
+ self.array_attribute = ArrayAttribute.new(definition)
29
38
  else
30
- definitions = schema[:attributes] || schema[:elements]
31
- self.attributes = definitions.collect do |key, value|
32
- Attribute.new({ key => value })
39
+ self.hash_attributes = definition.fetch(:properties).collect do |name, attribute_definition|
40
+ HashAttribute.new({ name => attribute_definition })
33
41
  end
34
42
  end
35
43
  end
36
44
 
37
- def array_of_schemas?(definition = {})
38
- return false unless definition
39
- definition.keys.include?(:attributes)
40
- end
41
-
42
- def render(options = {})
43
- endpoint = options.delete(:endpoint)
44
- data = Render.live ? request(endpoint) : options
45
- { title.to_sym => serialize(data) }
45
+ def serialize!(explicit_data = nil)
46
+ if (type == Array)
47
+ self.serialized_data = array_attribute.serialize(explicit_data)
48
+ else
49
+ self.serialized_data = hash_attributes.inject({}) do |processed_explicit_data, attribute|
50
+ explicit_data ||= {}
51
+ value = explicit_data.fetch(attribute.name, nil)
52
+ serialized_attribute = attribute.serialize(value)
53
+ processed_explicit_data.merge!(serialized_attribute)
54
+ end
55
+ end
46
56
  end
47
57
 
48
- def serialize(data)
49
- # data.is_a?(Array) ? to_array(data) : to_hash(data)
50
- (type == Array) ? to_array(data) : to_hash(data)
58
+ def render!(options_and_explicit_data = nil)
59
+ endpoint = options_and_explicit_data.delete(:endpoint) if options_and_explicit_data.is_a?(Hash)
60
+ self.raw_data = Render.live ? request(endpoint) : options_and_explicit_data
61
+ serialize!(raw_data)
62
+ yield serialized_data if block_given?
63
+ self.rendered_data = DottableHash.new(hash_with_title_prefixes(serialized_data))
51
64
  end
52
65
 
53
66
  private
54
67
 
55
- def find_schema(title)
56
- loaded_schema = Render.schemas[title.to_sym]
57
- raise Errors::Schema::NotFound.new(title) if !loaded_schema
58
- loaded_schema
59
- end
60
-
61
- def request(endpoint)
62
- response = Net::HTTP.get_response(URI(endpoint))
63
- if response.kind_of?(Net::HTTPSuccess)
64
- response = JSON.parse(response.body).recursive_symbolize_keys!
65
- if (response.is_a?(Array) || (response[title.to_sym] == nil))
66
- response
67
- else
68
- response[title.to_sym]
69
- end
68
+ def determine_definition(definition_or_title)
69
+ if (definition_or_title.is_a?(Hash) && !definition_or_title.empty?)
70
+ definition_or_title
70
71
  else
71
- raise Errors::Schema::RequestError.new(endpoint, response)
72
+ Render.definition(definition_or_title)
72
73
  end
73
- rescue JSON::ParserError => error
74
- raise Errors::Schema::InvalidResponse.new(endpoint, response.body)
75
- end
76
-
77
- def to_array(elements)
78
- # elements.first.is_a?(Hash) ? to_array_of_schemas(elements) : to_array_of_elements(elements)
79
- attributes.first.schema_value? ? to_array_of_schemas(elements) : to_array_of_elements(elements)
80
74
  end
81
75
 
82
- def to_array_of_elements(elements)
83
- (elements = stubbed_array) if !Render.live && (!elements || elements.empty?)
84
- archetype = attributes.first # there should only be one in the event that it's an array schema
85
- elements.collect do |element|
86
- archetype.serialize(element)
76
+ def hash_with_title_prefixes(data)
77
+ if universal_title
78
+ { universal_title => { title => data } }
79
+ else
80
+ { title => data }
87
81
  end
88
82
  end
89
83
 
90
- def to_array_of_schemas(elements)
91
- (elements = stubbed_array) if !Render.live && (!elements || elements.empty?)
92
- elements.collect do |element|
93
- attributes.inject({}) do |attributes, attribute|
94
- attributes.merge(attribute.to_hash(element)).values.first
95
- end
96
- end
84
+ def request(endpoint)
85
+ default_request(endpoint)
97
86
  end
98
87
 
99
- def to_hash(explicit_values = {})
100
- explicit_values ||= {} # !Render.live check
101
- attributes.inject({}) do |accum, attribute|
102
- explicit_value = explicit_values[attribute.name]
103
- hash = attribute.to_hash(explicit_value)
104
- accum.merge(hash)
88
+ def default_request(endpoint)
89
+ response = Net::HTTP.get_response(URI(endpoint))
90
+ if response.kind_of?(Net::HTTPSuccess)
91
+ JSON.parse(response.body).recursive_symbolize_keys!
92
+ else
93
+ raise Errors::Schema::RequestError.new(endpoint, response)
105
94
  end
95
+ rescue JSON::ParserError => error
96
+ raise Errors::Schema::InvalidResponse.new(endpoint, response.body)
106
97
  end
107
98
 
108
- def stubbed_array
109
- elements = []
110
- rand(1..3).times { elements << nil }
111
- elements
112
- end
113
99
  end
114
100
  end
@@ -1,3 +1,3 @@
1
1
  module Render
2
- VERSION = "0.0.2"
2
+ VERSION = "0.0.3"
3
3
  end