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
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- MDFmMmYyYWI1MWI3ODE2YjRkNDAwOGEzODAwYWVkYTBiOTYwZjhkOA==
4
+ OTJlZmIxMGY4MTViYTcxMTc1ZDQ1Y2NiMzQyOWIwOTJiNjkzODM1Yw==
5
5
  data.tar.gz: !binary |-
6
- ZTI2MzAwODM2ODAwOGQ3OWM5MmI2ODViNmIxMTg5YmJkM2IwOWY0ZQ==
6
+ OGRkZWY2MWQ2Y2NlY2MxNGJmYThmNzIwMmY3MjAyMTI5NDEwYWVkNw==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- NzFjZWJiMDVjMjNkZTczODBhZTA3OWRiOGEzYTJhYjc3YzBkZTI3OWRkYzUz
10
- NDUzZjMxYzBiYjM5MzAxM2Q4YzVjYjVjYzlmYjIwNjhhOTVlNGQwOWRjYzhk
11
- NTJkN2UzYjAwMzBjMDE3MGMxNTllYzVhYjQwMTI5NGRlMzRlM2E=
9
+ NzdlN2JkNWU3MDdlOTUzYWUwY2RkZTYyOTFiN2U3ZGE5MTNiYTIwMzgzNmQ1
10
+ YmM3MzE3ZDYyODU3NTNjYTI2NjM0NjY3ZjJkNDA4ZDc5Nzg1YjY2ZGIzODgx
11
+ ZWQ2YjhkMDdkNzk5OTg0NTM5MDQ3YjA5YjkzYjZhNGRjMGEwYWQ=
12
12
  data.tar.gz: !binary |-
13
- MjJlZGMxYmNhMWMxMmU1ZjdlYWI2ODMxMWU0M2M1ZDJlM2U2NGZlZTgyYTlh
14
- ZDgxOGNjOTVhODk4YzNlZjA4YmI3NTA1ZjVmZTYxZjk1OGE1NDFlYjljY2Jm
15
- ZTUxYzU3NmM1NzdkNmQ4ZjMwMzY1ZDFjZDZmNmI0MjFjMWZkZjk=
13
+ ZjMwNDIyNDdmZDg1ZGIyNDExMjYwODkzYjY2YTY3ZjllYWUzY2RkMmI4NjE0
14
+ NDQ0NTc2MzVhZjQzZTA0MGZhOTkzMzIxYzk4MTdlNTEwZTViYmM5MjVkNDgx
15
+ YzlhNDVjMWEwODkzMWJiMDZmZWQwNThhMGZhMjE3ODk5OTk4OTg=
data/Gemfile CHANGED
@@ -1,4 +1,4 @@
1
1
  source "http://rubygems.org"
2
2
 
3
- # Specify your gem's dependencies in representation.gemspec
3
+ # Specify your gem's dependencies in render.gemspec
4
4
  gemspec
data/lib/render.rb CHANGED
@@ -1,68 +1,30 @@
1
- # Render allows one to define object Graphs with Schema/endpoint information.
2
- # Once defined and constructed, a Graph can be built at once that will:
3
- # - Query its endpoint to construct a hash for its Schema
4
- # - Add nested Graphs by interpreting/sending data they need
5
-
6
- require "extensions/enumerable"
7
- require "extensions/boolean"
8
- require "extensions/hash"
1
+ require "uuid"
2
+ require "date"
3
+ require "logger"
9
4
 
10
5
  require "render/version"
11
- require "render/graph"
6
+ require "render/errors"
7
+ require "render/extensions/dottable_hash"
8
+ require "render/type"
12
9
  require "render/generator"
13
- require "logger"
14
- require "date"
10
+ require "render/definition"
11
+ require "render/graph"
15
12
 
16
13
  module Render
17
14
  @live = true
18
- @definitions = {}
19
- @generators = []
20
- @logger = ::Logger.new($stdout)
15
+ @logger = ::Logger.new("/dev/null")
21
16
  @threading = true
22
17
 
23
18
  class << self
24
- attr_accessor :live,
25
- :definitions,
26
- :generators,
27
- :logger,
28
- :threading
19
+ attr_accessor :live, :logger, :threading
29
20
 
30
21
  def threading?
31
22
  threading == true
32
23
  end
33
24
 
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)
40
- end
25
+ def live?
26
+ @live == true
41
27
  end
42
28
 
43
- def load_definition!(definition)
44
- title = definition.fetch(:universal_title, definition.fetch(:title)).to_sym
45
- self.definitions[title] = definition
46
- end
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
55
- def parse_type(type)
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)
65
- end
66
29
  end
67
-
68
30
  end
@@ -1,4 +1,4 @@
1
- require "render/attribute"
1
+ require "render/attributes/attribute"
2
2
 
3
3
  module Render
4
4
  class ArrayAttribute < Attribute
@@ -10,10 +10,9 @@ module Render
10
10
  super
11
11
 
12
12
  self.name = options.fetch(:title, :render_array_attribute_untitled).to_sym
13
+ self.required = options.fetch(:required, nil)
13
14
  options = options.fetch(:items)
14
- self.type = Render.parse_type(options[:type])
15
- self.format = Render.parse_type(options[:format]) rescue nil
16
- self.enums = options[:enum]
15
+ process_type!(options)
17
16
 
18
17
  if options.keys.include?(:properties)
19
18
  self.schema = Schema.new(options)
@@ -0,0 +1,48 @@
1
+ require "uuid"
2
+
3
+ module Render
4
+ class Attribute
5
+ SCHEMA_IDENTIFIERS = [:properties, :items].freeze
6
+
7
+ attr_accessor :name,
8
+ :type,
9
+ :schema,
10
+ :enums,
11
+ :format,
12
+ :required
13
+
14
+ def initialize(options = {})
15
+ Render.logger.debug("Initializing attribute #{options}")
16
+ self.required = false
17
+ end
18
+
19
+ def bias_type
20
+ format || type
21
+ end
22
+
23
+ def default_value
24
+ Render.live ? nil : faux_value
25
+ end
26
+
27
+ def nested_schema?(options = {})
28
+ options.any? { |name, value| SCHEMA_IDENTIFIERS.include?(name) }
29
+ end
30
+
31
+ private
32
+
33
+ def process_type!(options)
34
+ self.type = Type.parse!(options[:type])
35
+ self.format = Type.parse(options[:format])
36
+
37
+ if (options[:enum])
38
+ self.enums = options[:enum]
39
+ self.format = Type::Enum
40
+ end
41
+ end
42
+
43
+ def faux_value
44
+ Generator.trigger(bias_type, name, self)
45
+ end
46
+
47
+ end
48
+ end
@@ -1,5 +1,5 @@
1
1
  require "render"
2
- require "render/attribute"
2
+ require "render/attributes/attribute"
3
3
 
4
4
  module Render
5
5
  class HashAttribute < Attribute
@@ -8,9 +8,7 @@ module Render
8
8
 
9
9
  self.name = options.keys.first
10
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]
11
+ process_type!(options)
14
12
 
15
13
  initialize_schema!(options) if nested_schema?(options)
16
14
  end
@@ -0,0 +1,31 @@
1
+ module Render
2
+ class Definition
3
+ @instances = {}
4
+
5
+ class << self
6
+ attr_accessor :instances
7
+
8
+ def load_from_directory!(directory)
9
+ Dir.glob("#{directory}/**/*.json").each do |definition_file|
10
+ Render.logger.info("Reading #{definition_file} definition")
11
+ definition_string = File.read(definition_file)
12
+ json_definition = JSON.parse(definition_string)
13
+ parsed_definition = Extensions::DottableHash.new(json_definition).recursively_symbolize_keys!
14
+ load!(parsed_definition)
15
+ end
16
+ end
17
+
18
+ def load!(definition)
19
+ title = definition.fetch(:universal_title, definition.fetch(:title)).to_sym
20
+ self.instances[title] = definition
21
+ end
22
+
23
+ def find(title)
24
+ instances.fetch(title.to_sym)
25
+ rescue KeyError => error
26
+ raise Errors::DefinitionNotFound.new(title)
27
+ end
28
+
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,82 @@
1
+ require "render/extensions/symbolizable_hash"
2
+ require "render/extensions/symbolizable_array"
3
+
4
+ module Render
5
+ module Extensions
6
+ class DottableHash < SymbolizableHash
7
+ class << self
8
+ def new(element_to_hash = {})
9
+ symbolize_hash = SymbolizableHash.new.merge!(element_to_hash)
10
+ symbolize_hash.symbolize_keys!
11
+ hash = super().merge!(symbolize_hash)
12
+
13
+ hash.each do |key, value|
14
+ hash[key] = initialize_element(value)
15
+ end
16
+
17
+ hash
18
+ end
19
+
20
+ def initialize_element(value)
21
+ case value
22
+ when Hash
23
+ new(value)
24
+ when Array
25
+ values = value.collect do |v|
26
+ initialize_element(v)
27
+ end
28
+ SymbolizableArray.new(values)
29
+ else
30
+ value
31
+ end
32
+ end
33
+ end
34
+
35
+ def []=(key, value)
36
+ key = key.to_sym
37
+ value = self.class.initialize_element(value)
38
+ super
39
+ end
40
+
41
+ def [](key)
42
+ key = key.to_sym
43
+ super
44
+ end
45
+
46
+ def delete(key)
47
+ key = key.to_sym
48
+ super
49
+ end
50
+
51
+ def has_key?(key)
52
+ super(key.to_sym)
53
+ end
54
+
55
+ def merge!(other_hash)
56
+ other_hash = SymbolizableHash.new().merge!(other_hash)
57
+ super(other_hash.recursively_symbolize_keys!)
58
+ end
59
+
60
+ def merge(other_hash)
61
+ other_hash = SymbolizableHash.new().merge!(other_hash)
62
+ super(other_hash.recursively_symbolize_keys!)
63
+ end
64
+
65
+ def method_missing(method, *arguments)
66
+ if method.match(/\=$/)
67
+ self[method.to_s.chop] = arguments.first
68
+ elsif has_key?(method)
69
+ self[method]
70
+ else
71
+ super
72
+ end
73
+ end
74
+
75
+ def fetch(key, *args)
76
+ key = key.to_sym
77
+ super
78
+ end
79
+
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,26 @@
1
+ module Render
2
+ module Extensions
3
+ class SymbolizableArray < Array
4
+ class << self
5
+ def new(array)
6
+ array.inject(super()) do |accumulator, item|
7
+ if item.is_a?(Array)
8
+ accumulator << new(item)
9
+ elsif item.is_a?(Hash)
10
+ accumulator << DottableHash.new(item)
11
+ else
12
+ accumulator << item
13
+ end
14
+ end
15
+ end
16
+ end
17
+
18
+ def recursively_symbolize_keys!
19
+ each do |item|
20
+ item.recursively_symbolize_keys! if item.respond_to?(:recursively_symbolize_keys!)
21
+ end
22
+ self
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,28 @@
1
+ module Render
2
+ module Extensions
3
+ class SymbolizableHash < Hash
4
+ def initialize
5
+ super()
6
+ end
7
+
8
+ def symbolize_keys!
9
+ keys.each do |key|
10
+ self[(key.to_sym rescue key) || key] = delete(key)
11
+ end
12
+ self
13
+ end
14
+
15
+ def symbolize_keys
16
+ dup.symbolize_keys!
17
+ end
18
+
19
+ def recursively_symbolize_keys!
20
+ symbolize_keys!
21
+ values.each do |value|
22
+ value.recursively_symbolize_keys! if value.respond_to?(:recursively_symbolize_keys!)
23
+ end
24
+ self
25
+ end
26
+ end
27
+ end
28
+ end
@@ -1,14 +1,68 @@
1
+ # Generators make fake data in non-live mode.
2
+ # They are used for attributes of a specified type, and whose name matches its defined matcher.
3
+
4
+ require "uuid"
1
5
  require "render/errors"
2
6
 
3
7
  module Render
4
8
  class Generator
9
+ @instances = []
10
+
11
+ class << self
12
+ attr_accessor :instances
13
+
14
+ # When in non-live mode, e.g. testing, you can use custom generators to make fake data for a given attribute type
15
+ # and name-matcher.
16
+ # @param type [Class] Use generator exclusively for this type of data
17
+ # @param matcher [Regexp] Use generator for attribute whose name matches this
18
+ # @param algorithm [Proc, #call] Call this to generate a value
19
+ def create!(type, matcher, algorithm)
20
+ generator = new(type, matcher, algorithm)
21
+ instances.unshift(generator)
22
+ generator
23
+ end
24
+
25
+ def trigger(type, to_match, algorithm_argument = nil)
26
+ generator = find(type, to_match)
27
+ generator.trigger(algorithm_argument)
28
+ end
29
+
30
+ def find(type, to_match)
31
+ instances.detect do |generator|
32
+ (generator.type == type) && to_match.match(generator.matcher)
33
+ end
34
+ end
35
+ end
36
+
5
37
  attr_accessor :type, :matcher, :algorithm
6
38
 
7
- def initialize(properties = {})
8
- properties.symbolize_keys!
9
- %w(type matcher algorithm).each { |attribute| self.__send__("#{attribute}=", properties[attribute.to_sym]) }
10
- raise Errors::Generator::MalformedAlgorithm.new(algorithm) if !algorithm.respond_to?(:call)
39
+ def initialize(type, matcher, algorithm)
40
+ self.type = type
41
+ self.matcher = matcher
42
+ set_algorithm!(algorithm)
43
+ end
44
+
45
+ def trigger(algorithm_argument = nil)
46
+ algorithm[algorithm_argument]
47
+ end
48
+
49
+ private
50
+
51
+ def set_algorithm!(algorithm)
52
+ if algorithm.respond_to?(:call)
53
+ self.algorithm = algorithm
54
+ else
55
+ raise Errors::Generator::MalformedAlgorithm.new(algorithm)
56
+ end
11
57
  end
12
58
 
59
+ # Default set to ensure each type can generate fake data.
60
+ Generator.create!(String, /.*/, proc { |attribute| "#{attribute.name} (generated)" })
61
+ Generator.create!(Integer, /.*/, proc { rand(100) })
62
+ Generator.create!(Float, /.*/, proc { rand(0.1..99).round(2) })
63
+ Generator.create!(UUID, /.*/, proc { UUID.generate })
64
+ Generator.create!(Time, /.*/, proc { |attribute| time = Time.now; (attribute.type == String) ? time.to_s : time })
65
+ Generator.create!(Type::Boolean, /.*/, proc { [true, false].sample })
66
+ Generator.create!(Type::Enum, /.*/, proc { |attribute| attribute.enums.sample })
13
67
  end
14
68
  end