representors 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 (73) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +18 -0
  3. data/Gemfile +3 -0
  4. data/Gemfile.lock +126 -0
  5. data/LICENSE.md +19 -0
  6. data/README.md +28 -0
  7. data/Rakefile +10 -0
  8. data/lib/representor_support/utilities.rb +39 -0
  9. data/lib/representors.rb +5 -0
  10. data/lib/representors/errors.rb +7 -0
  11. data/lib/representors/field.rb +108 -0
  12. data/lib/representors/options.rb +67 -0
  13. data/lib/representors/representor.rb +161 -0
  14. data/lib/representors/representor_builder.rb +64 -0
  15. data/lib/representors/representor_hash.rb +59 -0
  16. data/lib/representors/serialization.rb +4 -0
  17. data/lib/representors/serialization/deserializer_base.rb +29 -0
  18. data/lib/representors/serialization/deserializer_factory.rb +13 -0
  19. data/lib/representors/serialization/hal_deserializer.rb +44 -0
  20. data/lib/representors/serialization/hal_serializer.rb +91 -0
  21. data/lib/representors/serialization/hale_deserializer.rb +162 -0
  22. data/lib/representors/serialization/hale_serializer.rb +110 -0
  23. data/lib/representors/serialization/serialization_base.rb +27 -0
  24. data/lib/representors/serialization/serialization_factory_base.rb +54 -0
  25. data/lib/representors/serialization/serializer_base.rb +20 -0
  26. data/lib/representors/serialization/serializer_factory.rb +17 -0
  27. data/lib/representors/transition.rb +130 -0
  28. data/lib/representors/version.rb +4 -0
  29. data/spec/fixtures/complex_hal.json +92 -0
  30. data/spec/fixtures/complex_hale_document.json +81 -0
  31. data/spec/fixtures/drds_hash.rb +120 -0
  32. data/spec/fixtures/hale_spec_examples/basic.json +77 -0
  33. data/spec/fixtures/hale_spec_examples/complex_reference_objects.json +157 -0
  34. data/spec/fixtures/hale_spec_examples/data.json +17 -0
  35. data/spec/fixtures/hale_spec_examples/data_objects.json +96 -0
  36. data/spec/fixtures/hale_spec_examples/link_objects.json +18 -0
  37. data/spec/fixtures/hale_spec_examples/nested_ref.json +43 -0
  38. data/spec/fixtures/hale_spec_examples/reference_objects.json +89 -0
  39. data/spec/fixtures/hale_tutorial_examples/basic_links.json +85 -0
  40. data/spec/fixtures/hale_tutorial_examples/basic_links_with_orders.json +96 -0
  41. data/spec/fixtures/hale_tutorial_examples/basic_links_with_references.json +108 -0
  42. data/spec/fixtures/hale_tutorial_examples/embedded.json +182 -0
  43. data/spec/fixtures/hale_tutorial_examples/empty.json +1 -0
  44. data/spec/fixtures/hale_tutorial_examples/enctype.json +14 -0
  45. data/spec/fixtures/hale_tutorial_examples/final.json +141 -0
  46. data/spec/fixtures/hale_tutorial_examples/get_link.json +17 -0
  47. data/spec/fixtures/hale_tutorial_examples/get_link_with_data.json +29 -0
  48. data/spec/fixtures/hale_tutorial_examples/links.json +11 -0
  49. data/spec/fixtures/hale_tutorial_examples/links_only.json +3 -0
  50. data/spec/fixtures/hale_tutorial_examples/meta.json +208 -0
  51. data/spec/fixtures/hale_tutorial_examples/self_link.json +7 -0
  52. data/spec/fixtures/single_drd.rb +266 -0
  53. data/spec/lib/representors/complex_representor_spec.rb +288 -0
  54. data/spec/lib/representors/field_spec.rb +141 -0
  55. data/spec/lib/representors/representor_builder_spec.rb +223 -0
  56. data/spec/lib/representors/representor_spec.rb +285 -0
  57. data/spec/lib/representors/serialization/deserializer_factory_spec.rb +118 -0
  58. data/spec/lib/representors/serialization/hal_deserializer_spec.rb +34 -0
  59. data/spec/lib/representors/serialization/hal_serializer_spec.rb +171 -0
  60. data/spec/lib/representors/serialization/hale_deserializer_spec.rb +59 -0
  61. data/spec/lib/representors/serialization/hale_roundtrip_spec.rb +34 -0
  62. data/spec/lib/representors/serialization/hale_serializer_spec.rb +659 -0
  63. data/spec/lib/representors/serialization/serializer_factory_spec.rb +108 -0
  64. data/spec/lib/representors/transition_spec.rb +349 -0
  65. data/spec/spec_helper.rb +32 -0
  66. data/spec/support/basic-hale.json +12 -0
  67. data/spec/support/hal_representor_shared.rb +206 -0
  68. data/spec/support/helpers.rb +8 -0
  69. data/tasks/benchmark.rake +75 -0
  70. data/tasks/complex_hal_document.json +98 -0
  71. data/tasks/test_specs.rake +37 -0
  72. data/tasks/yard.rake +22 -0
  73. metadata +232 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 302a3db2817d8fe764b0ee2649ce4771f1d57a40
4
+ data.tar.gz: 38cb226342fa028833cc3582a85832838e86820f
5
+ SHA512:
6
+ metadata.gz: 65ed976754d9aac6f894b55b13e08aba69c18ec08a394efd000233552b4f95d6691c098c712592981ea2b7bca0b4a0e7a18d5e85646129750252a57919826c0b
7
+ data.tar.gz: 5d63a44bbd9e5419e431f4045e4c7b166343281b1029a5a3277ecc79dfc2340c0edb7d6fdba3a0cafb03e7660283b5589ada30dd40dee08a69b4841347f1632e
@@ -0,0 +1,18 @@
1
+ # 0.0.5
2
+ * Sync changes from the develop branch that had not been ported back to 0-0-stable (see PR #52 and PR #53)
3
+
4
+ # 0.0.4
5
+ * Updates and clean ups
6
+ * Remove support of Ruby < 2.1
7
+
8
+ # 0.0.3
9
+ * Update to latest rspec and mutant
10
+ * Implement URL templating
11
+ * Change on behaviour of `Representor::Transition#templated_uri`. It only provides a template URL if the original URL followed the RFC 6570.
12
+
13
+ # 0.0.2
14
+ * Significant refactor of Builder to be non mutating
15
+ * Includes Hale deserializer
16
+
17
+ # 0.0.1
18
+ * Initial Release
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
@@ -0,0 +1,126 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ representors (0.0.3)
5
+ addressable (~> 2.3)
6
+ rake
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ abstract_type (0.0.7)
12
+ adamantium (0.2.0)
13
+ ice_nine (~> 0.11.0)
14
+ memoizable (~> 0.4.0)
15
+ addressable (2.3.7)
16
+ anima (0.2.0)
17
+ abstract_type (~> 0.0.7)
18
+ adamantium (~> 0.1)
19
+ equalizer (~> 0.0.8)
20
+ ast (2.0.0)
21
+ awesome_print (1.2.0)
22
+ byebug (3.2.0)
23
+ columnize (~> 0.8)
24
+ debugger-linecache (~> 1.2)
25
+ coderay (1.1.0)
26
+ columnize (0.9.0)
27
+ concord (0.1.5)
28
+ adamantium (~> 0.2.0)
29
+ equalizer (~> 0.0.9)
30
+ debugger-linecache (1.2.0)
31
+ diff-lcs (1.2.5)
32
+ docile (1.1.5)
33
+ equalizer (0.0.10)
34
+ ice_nine (0.11.1)
35
+ memoizable (0.4.2)
36
+ thread_safe (~> 0.3, >= 0.3.1)
37
+ method_source (0.8.2)
38
+ morpher (0.2.3)
39
+ abstract_type (~> 0.0.7)
40
+ adamantium (~> 0.2.0)
41
+ anima (~> 0.2.0)
42
+ ast (~> 2.0.0)
43
+ concord (~> 0.1.4)
44
+ equalizer (~> 0.0.9)
45
+ ice_nine (~> 0.11.0)
46
+ procto (~> 0.0.2)
47
+ multi_json (1.10.1)
48
+ mutant (0.7.8)
49
+ abstract_type (~> 0.0.7)
50
+ adamantium (~> 0.2.0)
51
+ anima (~> 0.2.0)
52
+ ast (~> 2.0)
53
+ concord (~> 0.1.5)
54
+ diff-lcs (~> 1.2)
55
+ equalizer (~> 0.0.9)
56
+ ice_nine (~> 0.11.1)
57
+ memoizable (~> 0.4.2)
58
+ morpher (~> 0.2.3)
59
+ parallel (~> 1.3)
60
+ parser (~> 2.2.0.2)
61
+ procto (~> 0.0.2)
62
+ unparser (~> 0.2.2)
63
+ mutant-rspec (0.7.8)
64
+ mutant (~> 0.7.8)
65
+ rspec-core (>= 3.0.0, < 3.3.0)
66
+ parallel (1.4.1)
67
+ parser (2.2.0.3)
68
+ ast (>= 1.1, < 3.0)
69
+ procto (0.0.2)
70
+ pry (0.9.12.6)
71
+ coderay (~> 1.0)
72
+ method_source (~> 0.8)
73
+ slop (~> 3.4)
74
+ pry-nav (0.2.3)
75
+ pry (~> 0.9.10)
76
+ pry-remote (0.1.8)
77
+ pry (~> 0.9)
78
+ slop (~> 3.0)
79
+ rake (10.4.2)
80
+ redcarpet (3.1.2)
81
+ rspec (3.2.0)
82
+ rspec-core (~> 3.2.0)
83
+ rspec-expectations (~> 3.2.0)
84
+ rspec-mocks (~> 3.2.0)
85
+ rspec-core (3.2.2)
86
+ rspec-support (~> 3.2.0)
87
+ rspec-expectations (3.2.0)
88
+ diff-lcs (>= 1.2.0, < 2.0)
89
+ rspec-support (~> 3.2.0)
90
+ rspec-mocks (3.2.1)
91
+ diff-lcs (>= 1.2.0, < 2.0)
92
+ rspec-support (~> 3.2.0)
93
+ rspec-support (3.2.2)
94
+ simplecov (0.8.2)
95
+ docile (~> 1.1.0)
96
+ multi_json
97
+ simplecov-html (~> 0.8.0)
98
+ simplecov-html (0.8.0)
99
+ slop (3.5.0)
100
+ thread_safe (0.3.5)
101
+ unparser (0.2.2)
102
+ abstract_type (~> 0.0.7)
103
+ adamantium (~> 0.2.0)
104
+ concord (~> 0.1.5)
105
+ diff-lcs (~> 1.2.5)
106
+ equalizer (~> 0.0.9)
107
+ parser (~> 2.2.0.2)
108
+ procto (~> 0.0.2)
109
+ yard (0.8.7.4)
110
+
111
+ PLATFORMS
112
+ ruby
113
+
114
+ DEPENDENCIES
115
+ awesome_print (~> 1.2.0)
116
+ byebug (~> 3.2)
117
+ mutant (~> 0.7.8)
118
+ mutant-rspec (~> 0.7.8)
119
+ pry
120
+ pry-nav
121
+ pry-remote
122
+ redcarpet (~> 3.1.1)
123
+ representors!
124
+ rspec (~> 3.2)
125
+ simplecov (~> 0.8.2)
126
+ yard (~> 0.8.7)
@@ -0,0 +1,19 @@
1
+ Copyright &copy; 2014 Medidata Solutions Worldwide
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
@@ -0,0 +1,28 @@
1
+ # Representors
2
+
3
+ A library to simplify Hypermedia message representation. It has the knowledge of Hypermedia media-types from the Ancients!
4
+
5
+ This gem provides serializers and deserializers from/to known Hypermedia formats. It currently supports HAL and HALE.
6
+ It also provides a Representor class to hold the information from hypermedia responses, this class provides method to access properties, transitions, etc.
7
+
8
+
9
+ ## Developing
10
+
11
+ Write your tests, write your code and make sure all tests pass:
12
+ ```
13
+ bundle exec rspec
14
+ ```
15
+
16
+ Also, you can check your test coverage by running mutant on the classes you have worked on.
17
+ For instance if you modified Representors::Representor, please execute:
18
+ ```
19
+ MUTANT=true mutant --include lib --require representors --score 95 --use rspec Representors::Representor*
20
+ ```
21
+
22
+ Reaching 100% mutant coverage is not feasible sometimes as they may be some false positives.
23
+ But please investigate any missing coverage, as it may indicate an actual problem with the tests.
24
+
25
+
26
+ ## Copyright
27
+
28
+ Copyright &copy; 2016 Medidata Solutions Worldwide. See [LICENSE](LICENSE.md) for details.
@@ -0,0 +1,10 @@
1
+ lib_dir = File.expand_path('../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib_dir)
3
+ $LOAD_PATH.uniq!
4
+
5
+ require 'rubygems'
6
+ require 'bundler/setup'
7
+ require 'rake'
8
+ require 'rake/clean'
9
+
10
+ Dir['tasks/**/*.rake'].each { |rake| load rake }
@@ -0,0 +1,39 @@
1
+ module RepresentorSupport
2
+ module Utilities
3
+
4
+ # Accepts a hash and returns a new hash with symbolized keys
5
+ def symbolize_keys(hash)
6
+ Hash[hash.map{|(k,v)| [k.to_sym,v]}]
7
+ end
8
+
9
+ # Will recursively deep dup an aribitrary set of nested hashes and arrays,
10
+ # but does not handle complex objects. Also, this benchmarked *way* faster
11
+ # than the Marshal approach.
12
+ def deep_dup(obj)
13
+ if obj.is_a?(Hash)
14
+ result = {}
15
+ obj.each { |k,v| result[k] = deep_dup(v) }
16
+ result
17
+ elsif obj.is_a?(Array)
18
+ obj.map { |el| deep_dup(el) }
19
+ else
20
+ dup_or_self(obj)
21
+ end
22
+ end
23
+
24
+ # Unfortunately the best way to test if you can actually dup an object is to
25
+ # try, and handle the TypeError if not succesful.
26
+ def dup_or_self(obj)
27
+ begin
28
+ obj.dup
29
+ rescue TypeError
30
+ obj
31
+ end
32
+ end
33
+
34
+ def map_or_apply(proc, obj)
35
+ obj.is_a?(Array) ? obj.map { |sub| proc.(sub) } : proc.(obj)
36
+ end
37
+
38
+ end
39
+ end
@@ -0,0 +1,5 @@
1
+ require 'representors/representor_hash'
2
+ require 'representors/representor'
3
+ require 'representors/representor_builder'
4
+
5
+ require 'representors/serialization'
@@ -0,0 +1,7 @@
1
+ module Representors
2
+ # General error when there is a problem with any deserialization.
3
+ class DeserializationError < StandardError; end
4
+
5
+ # Error to raise when the media-type we are trying to de/serialize is not known by this gem
6
+ class UnknownMediaTypeError < StandardError; end
7
+ end
@@ -0,0 +1,108 @@
1
+ require 'yaml'
2
+ require 'representors/options'
3
+ require 'representor_support/utilities'
4
+ module Representors
5
+ ##
6
+ # Manages the respresentation of hypermedia fields for different media-types.
7
+ class Field < String
8
+ include RepresentorSupport::Utilities
9
+
10
+ SIMPLE_METHODS = %w(name value default description field_type type data_type cardinality doc).map(&:to_sym)
11
+ DESCRIPTORS_KEY = :descriptors
12
+ ATTRIBUTE_FIELDS = 'attribute'
13
+ PARAMETER_FIELDS = 'href'
14
+ VALIDATORS_KEY = :validators
15
+ OPTIONS_KEY = :options
16
+ SCOPE_KEY = :scope
17
+ NAME_KEY = :name
18
+ DEFAULT_SCOPE = 'attribute'
19
+
20
+ # @example
21
+ # hash = {field_name: {field_property: property_name}}
22
+ # Fields.new(hash)
23
+ # It must only have one key/vale pair where the value is a hash
24
+ # @param [Hash] the abstract representation of a Field
25
+ def initialize(field_hash)
26
+ name = field_hash.keys.first
27
+ field_hash[name] = symbolize_keys(field_hash[name])
28
+ @field_hash = field_hash[name].clone
29
+ @field_hash[NAME_KEY] = name
30
+ super value.to_s
31
+ end
32
+
33
+ SIMPLE_METHODS.each do |meth|
34
+ define_method(meth) { @field_hash[meth] }
35
+ end
36
+
37
+ # @return [String] representing the sort of field
38
+ def scope
39
+ @scope ||= @field_hash[SCOPE_KEY] || DEFAULT_SCOPE
40
+ end
41
+
42
+ # @return [Hash] The hash representation of the object
43
+ def to_hash
44
+ @to_hash ||= { @field_hash[NAME_KEY] => @field_hash.reject {|k,v| k == NAME_KEY } }
45
+ end
46
+
47
+ # @return [String] the yaml representation of the object
48
+ def to_yaml
49
+ @to_yaml ||= YAML.dump(to_hash)
50
+ end
51
+
52
+ # @return [Array] who's elements are all [Hash] objects
53
+ def validators
54
+ @validators ||= if @field_hash.has_key?(VALIDATORS_KEY)
55
+ @field_hash[VALIDATORS_KEY].map { |h| h.instance_of?(Symbol) ? {h => h} : h }
56
+ else
57
+ []
58
+ end
59
+ end
60
+
61
+ # @return [Array] who's elements are all Representors::Options objects
62
+ # TODO Not an array. Its an Options, which should be an array?
63
+ def options
64
+ @options ||= Options.new(@field_hash[OPTIONS_KEY], name)
65
+ end
66
+
67
+ # @returns the value of the field
68
+ def call
69
+ value
70
+ end
71
+
72
+ # The Parameters (i.e. GET variables)
73
+ #
74
+ # @return [Array] who's elements are all <Crichton:Field> objects
75
+ def parameters
76
+ @parameters ||= get_field_by_type(PARAMETER_FIELDS)
77
+ end
78
+
79
+ # The Parameters (i.e. POST variables)
80
+ #
81
+ # @return [Array] who's elements are all <Crichton:Field> objects
82
+ def attributes
83
+ @attributes ||= get_field_by_type(ATTRIBUTE_FIELDS)
84
+ end
85
+ # The Parameters (i.e. GET variables)
86
+ #
87
+ # @return [Array] who's elements are all <Crichton:Field> objects
88
+ def descriptors
89
+ @descriptors ||= (attributes + parameters)
90
+ end
91
+
92
+ private
93
+
94
+ def filtered_fields(fields, scope)
95
+ fields.select { |field| field.scope == scope }
96
+ end
97
+
98
+ def descriptor_fields(hash)
99
+ hash[DESCRIPTORS_KEY].map { |k, v| Field.new({k => v }) }
100
+ end
101
+
102
+ def get_field_by_type(field_type)
103
+ fields = @field_hash.has_key?(DESCRIPTORS_KEY) ? descriptor_fields(@field_hash) : []
104
+ filtered_fields(fields, field_type)
105
+ end
106
+
107
+ end
108
+ end
@@ -0,0 +1,67 @@
1
+ module Representors
2
+ ##
3
+ # Manages the respresentation of
4
+ # field options for a hypermedia message.
5
+ class Options
6
+ TYPE_KEYS = %w(external hash list)
7
+ LIST_TYPE = 'list'
8
+ EXTERNAL_TYPE = 'external'
9
+ HASH_TYPE = 'hash'
10
+ DEFAULT_TYPE = LIST_TYPE
11
+ ID_KEY = 'id'
12
+ ID_TEMPLATE = "%s_options"
13
+
14
+ # @param [Hash] the abstract representation of Field Options
15
+ def initialize(options_hash = {}, field_name = '')
16
+ @options_hash = options_hash || {}
17
+ @field_name = field_name
18
+ end
19
+
20
+ # @return [String] delineating the Options type
21
+ def type
22
+ @type ||= begin
23
+ type_keys = TYPE_KEYS.detect { |key| @options_hash.has_key?(key) }
24
+ type_keys || DEFAULT_TYPE
25
+ end
26
+ end
27
+
28
+ # @return [Bool] indicating whether the Options can be treated as a datalist
29
+ def datalist?
30
+ type == EXTERNAL_TYPE || @options_hash.has_key?(ID_KEY)
31
+ end
32
+
33
+ def empty?
34
+ to_hash.empty?
35
+ end
36
+
37
+ # @return [String] representing a unique id for the options
38
+ def id
39
+ @options_hash[ID_KEY] || ID_TEMPLATE % @field_name
40
+ end
41
+
42
+ # @return [Hash] version of the Options
43
+ def to_hash
44
+ type == LIST_TYPE ? Hash[(@options_hash[LIST_TYPE] || {}).map { |x| [x, x] }] : @options_hash[type]
45
+ end
46
+
47
+ # @return [Array] hash keys
48
+ def keys
49
+ to_hash.keys
50
+ end
51
+
52
+ # @return [Array] hash values
53
+ def values
54
+ to_hash.values
55
+ end
56
+
57
+ # @return [Array] version of the Options
58
+ def to_list
59
+ keys
60
+ end
61
+
62
+ def to_data
63
+ type == HASH_TYPE || type == EXTERNAL_TYPE ? to_hash : to_list
64
+ end
65
+
66
+ end
67
+ end