render 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
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