render 0.0.2 → 0.0.3

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