lhs 0.3.0

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 (122) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +38 -0
  3. data/Gemfile +11 -0
  4. data/README.md +67 -0
  5. data/Rakefile +26 -0
  6. data/docs/collections.md +28 -0
  7. data/docs/data.md +39 -0
  8. data/docs/examples/claim_no_include.json +16 -0
  9. data/docs/examples/claim_with_include.json +47 -0
  10. data/docs/items.md +55 -0
  11. data/docs/service.jpg +0 -0
  12. data/docs/service.pdf +649 -3
  13. data/docs/services.md +191 -0
  14. data/lhs.gemspec +31 -0
  15. data/lib/lhs.rb +6 -0
  16. data/lib/lhs/collection.rb +78 -0
  17. data/lib/lhs/concerns/data/json.rb +12 -0
  18. data/lib/lhs/concerns/item/destroy.rb +15 -0
  19. data/lib/lhs/concerns/item/save.rb +29 -0
  20. data/lib/lhs/concerns/item/update.rb +24 -0
  21. data/lib/lhs/concerns/service/all.rb +24 -0
  22. data/lib/lhs/concerns/service/batch.rb +37 -0
  23. data/lib/lhs/concerns/service/build.rb +17 -0
  24. data/lib/lhs/concerns/service/create.rb +26 -0
  25. data/lib/lhs/concerns/service/endpoints.rb +82 -0
  26. data/lib/lhs/concerns/service/find.rb +36 -0
  27. data/lib/lhs/concerns/service/find_by.rb +35 -0
  28. data/lib/lhs/concerns/service/first.rb +19 -0
  29. data/lib/lhs/concerns/service/includes.rb +21 -0
  30. data/lib/lhs/concerns/service/mapping.rb +23 -0
  31. data/lib/lhs/concerns/service/model.rb +16 -0
  32. data/lib/lhs/concerns/service/request.rb +96 -0
  33. data/lib/lhs/concerns/service/where.rb +16 -0
  34. data/lib/lhs/data.rb +103 -0
  35. data/lib/lhs/errors.rb +86 -0
  36. data/lib/lhs/item.rb +83 -0
  37. data/lib/lhs/proxy.rb +26 -0
  38. data/lib/lhs/service.rb +20 -0
  39. data/lib/lhs/version.rb +3 -0
  40. data/script/ci/build.sh +19 -0
  41. data/spec/collection/meta_data_spec.rb +54 -0
  42. data/spec/collection/respond_to_spec.rb +19 -0
  43. data/spec/collection/without_object_items_spec.rb +26 -0
  44. data/spec/data/collection_spec.rb +36 -0
  45. data/spec/data/item_spec.rb +44 -0
  46. data/spec/data/merge_spec.rb +32 -0
  47. data/spec/data/raw_spec.rb +39 -0
  48. data/spec/data/respond_to_spec.rb +26 -0
  49. data/spec/data/root_spec.rb +25 -0
  50. data/spec/data/to_json_spec.rb +39 -0
  51. data/spec/dummy/README.rdoc +28 -0
  52. data/spec/dummy/Rakefile +6 -0
  53. data/spec/dummy/app/assets/images/.keep +0 -0
  54. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  55. data/spec/dummy/app/assets/stylesheets/application.css +15 -0
  56. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  57. data/spec/dummy/app/controllers/concerns/.keep +0 -0
  58. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  59. data/spec/dummy/app/mailers/.keep +0 -0
  60. data/spec/dummy/app/models/.keep +0 -0
  61. data/spec/dummy/app/models/concerns/.keep +0 -0
  62. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  63. data/spec/dummy/bin/bundle +3 -0
  64. data/spec/dummy/bin/rails +4 -0
  65. data/spec/dummy/bin/rake +4 -0
  66. data/spec/dummy/config.ru +4 -0
  67. data/spec/dummy/config/application.rb +14 -0
  68. data/spec/dummy/config/boot.rb +5 -0
  69. data/spec/dummy/config/environment.rb +5 -0
  70. data/spec/dummy/config/environments/development.rb +34 -0
  71. data/spec/dummy/config/environments/production.rb +75 -0
  72. data/spec/dummy/config/environments/test.rb +39 -0
  73. data/spec/dummy/config/initializers/assets.rb +8 -0
  74. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  75. data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
  76. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  77. data/spec/dummy/config/initializers/inflections.rb +16 -0
  78. data/spec/dummy/config/initializers/mime_types.rb +4 -0
  79. data/spec/dummy/config/initializers/session_store.rb +3 -0
  80. data/spec/dummy/config/initializers/wrap_parameters.rb +9 -0
  81. data/spec/dummy/config/locales/en.yml +23 -0
  82. data/spec/dummy/config/routes.rb +56 -0
  83. data/spec/dummy/config/secrets.yml +22 -0
  84. data/spec/dummy/lib/assets/.keep +0 -0
  85. data/spec/dummy/public/404.html +67 -0
  86. data/spec/dummy/public/422.html +67 -0
  87. data/spec/dummy/public/500.html +66 -0
  88. data/spec/dummy/public/favicon.ico +0 -0
  89. data/spec/item/destroy_spec.rb +39 -0
  90. data/spec/item/getter_spec.rb +24 -0
  91. data/spec/item/respond_to_spec.rb +29 -0
  92. data/spec/item/save_errors_spec.rb +48 -0
  93. data/spec/item/save_spec.rb +58 -0
  94. data/spec/item/setter_spec.rb +38 -0
  95. data/spec/item/update_spec.rb +56 -0
  96. data/spec/proxy/load_spec.rb +47 -0
  97. data/spec/rails_helper.rb +9 -0
  98. data/spec/service/all_spec.rb +31 -0
  99. data/spec/service/build_spec.rb +25 -0
  100. data/spec/service/create_spec.rb +81 -0
  101. data/spec/service/creation_failed_spec.rb +54 -0
  102. data/spec/service/endpoint_misconfiguration_spec.rb +26 -0
  103. data/spec/service/endpoint_options_spec.rb +23 -0
  104. data/spec/service/endpoints_spec.rb +57 -0
  105. data/spec/service/find_by_spec.rb +49 -0
  106. data/spec/service/find_each_spec.rb +47 -0
  107. data/spec/service/find_in_batches_spec.rb +68 -0
  108. data/spec/service/find_spec.rb +71 -0
  109. data/spec/service/first_spec.rb +39 -0
  110. data/spec/service/includes_spec.rb +61 -0
  111. data/spec/service/mapping_spec.rb +72 -0
  112. data/spec/service/model_name_spec.rb +17 -0
  113. data/spec/service/request_spec.rb +22 -0
  114. data/spec/service/where_spec.rb +33 -0
  115. data/spec/spec_helper.rb +4 -0
  116. data/spec/support/cleanup_configuration.rb +17 -0
  117. data/spec/support/cleanup_services.rb +20 -0
  118. data/spec/support/fixtures/json/feedback.json +11 -0
  119. data/spec/support/fixtures/json/feedbacks.json +174 -0
  120. data/spec/support/fixtures/json/localina_content_ad.json +23 -0
  121. data/spec/support/load_json.rb +3 -0
  122. metadata +346 -0
@@ -0,0 +1,17 @@
1
+ require 'active_support'
2
+
3
+ class LHS::Service
4
+
5
+ module Build
6
+ extend ActiveSupport::Concern
7
+
8
+ module ClassMethods
9
+
10
+ def build(data = {})
11
+ data = LHS::Data.new(data, nil, self)
12
+ item = LHS::Item.new(data)
13
+ LHS::Data.new(item, nil, self)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,26 @@
1
+ require 'active_support'
2
+
3
+ class LHS::Service
4
+
5
+ module Create
6
+ extend ActiveSupport::Concern
7
+
8
+ module ClassMethods
9
+
10
+ def create(data = {})
11
+ create!(data)
12
+ rescue LHC::Error => e
13
+ json = JSON.parse(data.to_json)
14
+ data = LHS::Data.new(json, nil, self.class, e.response.request)
15
+ item = LHS::Item.new(data)
16
+ item.errors = LHS::Errors.new(e.response)
17
+ LHS::Data.new(item, data)
18
+ end
19
+
20
+ def create!(data = {})
21
+ url = instance.compute_url!(data)
22
+ instance.request(url: url, method: :post, body: data.to_json, headers: {'Content-Type' => 'application/json'})
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,82 @@
1
+ require 'active_support'
2
+
3
+ class LHS::Service
4
+
5
+ # An endpoint is an url that leads to a backend resource.
6
+ # A service can contain multiple endpoints.
7
+ # The endpoint that is used to request data is choosen
8
+ # based on the provided parameters.
9
+ module Endpoints
10
+ extend ActiveSupport::Concern
11
+
12
+ attr_accessor :endpoints
13
+
14
+ module ClassMethods
15
+
16
+ # Adds the endpoint to the list of endpoints.
17
+ def endpoint(url, options = nil)
18
+ endpoint = LHC::Endpoint.new(url, options)
19
+ instance.sanity_check(endpoint)
20
+ instance.endpoints.push(endpoint)
21
+ end
22
+ end
23
+
24
+ def initialize
25
+ self.endpoints = []
26
+ super
27
+ end
28
+
29
+ # Find an endpoint based on the provided parameters.
30
+ # If no parameters are provided it finds the base endpoint
31
+ # otherwise it finds the endpoint that matches the parameters best.
32
+ def find_endpoint(params = {})
33
+ endpoint = find_best_endpoint(params) if params && params.keys.count > 0
34
+ endpoint ||= find_base_endpoint
35
+ endpoint
36
+ end
37
+
38
+ # Prevent clashing endpoints.
39
+ def sanity_check(endpoint)
40
+ placeholders = endpoint.placeholders
41
+ fail 'Clashing endpoints.' if endpoints.any? { |e| e.placeholders.sort == placeholders.sort }
42
+ end
43
+
44
+ # Computes the url from params
45
+ # by identifiying endpoint and compiles it if necessary.
46
+ # Id in params is threaded in a special way.
47
+ def compute_url!(params)
48
+ endpoint = find_endpoint(params)
49
+ url = endpoint.compile(params)
50
+ url += "/#{params.delete(:id)}" if params && params[:id]
51
+ endpoint.remove_interpolated_params!(params)
52
+ url
53
+ end
54
+
55
+ private
56
+
57
+ # Finds the best endpoint.
58
+ # The best endpoint is the one where all placeholders are interpolated.
59
+ def find_best_endpoint(params)
60
+ sorted_endpoints.find do |endpoint|
61
+ endpoint.placeholders.all? { |match| endpoint.find_value(match, params) }
62
+ end
63
+ end
64
+
65
+ # Sort endpoints by number of placeholders, heighest first
66
+ def sorted_endpoints
67
+ endpoints.sort{|a, b| b.placeholders.count <=> a.placeholders.count }
68
+ end
69
+
70
+ # Finds the base endpoint.
71
+ # A base endpoint is the one thats has the least amont of placeholers.
72
+ # There cannot be multiple base endpoints.
73
+ def find_base_endpoint
74
+ endpoints = self.endpoints.group_by do |endpoint|
75
+ endpoint.placeholders.length
76
+ end
77
+ bases = endpoints[endpoints.keys.min]
78
+ fail 'Multiple base endpoints found' if bases.count > 1
79
+ bases.first
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,36 @@
1
+ require 'active_support'
2
+
3
+ class LHS::Service
4
+
5
+ module Find
6
+ extend ActiveSupport::Concern
7
+
8
+ module ClassMethods
9
+
10
+ # Find a single uniqe record
11
+ def find(args)
12
+ if args.is_a? Hash
13
+ find_with_parameters(args)
14
+ else
15
+ find_by_id(args)
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def find_with_parameters(params)
22
+ data = instance.request(params: params)
23
+ if data._proxy.is_a?(LHS::Collection)
24
+ fail LHC::NotFound.new('Requested unique item. Multiple were found.', data._request.response) if data.count > 1
25
+ data.first || fail(LHC::NotFound.new('No item was found.', data._request.response))
26
+ else
27
+ data
28
+ end
29
+ end
30
+
31
+ def find_by_id(id)
32
+ instance.request(params: { id: id })
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,35 @@
1
+ require 'active_support'
2
+
3
+ class LHS::Service
4
+
5
+ module FindBy
6
+ extend ActiveSupport::Concern
7
+
8
+ module ClassMethods
9
+
10
+ # Fetch some record by parameters
11
+ def find_by(params = {})
12
+ _find_by(params)
13
+ rescue LHC::NotFound
14
+ nil
15
+ end
16
+
17
+ # Raise if no record was found
18
+ def find_by!(params = {})
19
+ _find_by(params)
20
+ end
21
+
22
+ private
23
+
24
+ def _find_by(params)
25
+ params = params.dup.merge(limit: 1)
26
+ data = instance.request(params: params)
27
+ if data._proxy.is_a?(LHS::Collection)
28
+ data.first || fail(LHC::NotFound.new('No item was found.', data._request.response))
29
+ else
30
+ data
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,19 @@
1
+ require 'active_support'
2
+
3
+ class LHS::Service
4
+
5
+ module First
6
+ extend ActiveSupport::Concern
7
+
8
+ module ClassMethods
9
+
10
+ def first
11
+ find_by
12
+ end
13
+
14
+ def first!
15
+ find_by!
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,21 @@
1
+ require 'active_support'
2
+
3
+ class LHS::Service
4
+
5
+ module Includes
6
+ extend ActiveSupport::Concern
7
+
8
+ attr_accessor :includes
9
+
10
+ module ClassMethods
11
+
12
+ def includes(args)
13
+ class_clone = clone
14
+ class_clone.instance.endpoints = instance.endpoints
15
+ class_clone.instance.mapping = instance.mapping
16
+ class_clone.instance.includes = args
17
+ class_clone
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,23 @@
1
+ require 'active_support'
2
+
3
+ class LHS::Service
4
+
5
+ # Mapping allows to configure some accessors that access data using a provided proc
6
+ module Mapping
7
+ extend ActiveSupport::Concern
8
+
9
+ attr_accessor :mapping
10
+
11
+ module ClassMethods
12
+
13
+ def map(name, block)
14
+ instance.mapping[name] = block
15
+ end
16
+ end
17
+
18
+ def initialize
19
+ self.mapping = {}
20
+ super
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,16 @@
1
+ require 'active_support'
2
+ require 'active_model'
3
+
4
+ class LHS::Service
5
+
6
+ module Model
7
+ extend ActiveSupport::Concern
8
+
9
+ module ClassMethods
10
+
11
+ def model_name
12
+ ActiveModel::Name.new(self)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,96 @@
1
+ require 'active_support'
2
+
3
+ class LHS::Service
4
+
5
+ module Request
6
+ extend ActiveSupport::Concern
7
+
8
+ def request(options)
9
+ if options.is_a? Array
10
+ multiple_requests(options)
11
+ else
12
+ single_request(options)
13
+ end
14
+ end
15
+
16
+ private
17
+
18
+ def multiple_requests(options)
19
+ options.map { |options| process_options(options) }
20
+ responses = LHC.request(options)
21
+ data = responses.map{ |response| LHS::Data.new(response.body, nil, self.class, response.request) }
22
+ data = LHS::Data.new(data, nil, self.class)
23
+ handle_includes(data) if includes
24
+ data
25
+ end
26
+
27
+ def single_request(options)
28
+ response = LHC.request(process_options(options))
29
+ data = LHS::Data.new(response.body, nil, self.class, response.request)
30
+ handle_includes(data) if includes
31
+ data
32
+ end
33
+
34
+ # Merge explicit params and take configured endpoints options as base
35
+ def process_options(options)
36
+ endpoint = find_endpoint(options[:params])
37
+ options = (endpoint.options || {}).merge(options)
38
+ options[:url] = compute_url!(options[:params]) unless options.key?(:url)
39
+ merge_explicit_params!(options[:params])
40
+ options.delete(:params) if options[:params] && options[:params].empty?
41
+ options
42
+ end
43
+
44
+ # Merge explicit params nested in 'params' namespace with original hash.
45
+ def merge_explicit_params!(params)
46
+ return true unless params
47
+ explicit_params = params[:params]
48
+ params.delete(:params)
49
+ params.merge!(explicit_params) if explicit_params
50
+ end
51
+
52
+ def handle_includes(data)
53
+ if includes.is_a? Hash
54
+ includes.keys.each { |key| handle_include(data, key) }
55
+ else
56
+ handle_include(data, includes)
57
+ end
58
+ end
59
+
60
+ def handle_include(data, key)
61
+ options = if data._proxy.is_a? LHS::Collection
62
+ include_multiple(data, key)
63
+ else
64
+ include_single(data, key)
65
+ end
66
+ addition = if (further_keys = includes.fetch(key, nil) if includes.is_a? Hash)
67
+ self.class.includes(further_keys).instance.request(options)
68
+ else
69
+ self.class.includes(nil).instance.request(options)
70
+ end
71
+ extend(data, addition, key)
72
+ end
73
+
74
+ def extend(data, addition, key)
75
+ if data._proxy.is_a? LHS::Collection
76
+ data.each_with_index do |item, i|
77
+ item = item[i] if item.is_a? LHS::Collection
78
+ item._raw[key.to_s].merge!(addition[i]._raw)
79
+ end
80
+ elsif data._proxy.is_a? LHS::Item
81
+ data._raw[key.to_s].merge!(addition._raw)
82
+ end
83
+ end
84
+
85
+ def include_multiple(data, key)
86
+ data.map do |item|
87
+ include_single(item, key)
88
+ end
89
+ end
90
+
91
+ def include_single(item, key)
92
+ link = item[key]
93
+ { url: link.href }
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,16 @@
1
+ require 'active_support'
2
+
3
+ class LHS::Service
4
+
5
+ module Where
6
+ extend ActiveSupport::Concern
7
+
8
+ module ClassMethods
9
+
10
+ # Used to query data from the service.
11
+ def where(params = {})
12
+ instance.request(params: params)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,103 @@
1
+ require File.join(__dir__, 'proxy.rb')
2
+ Dir[File.dirname(__FILE__) + '/concerns/data/*.rb'].each {|file| require file }
3
+
4
+ # Data provides functionalities to accesses information
5
+ class LHS::Data
6
+ include Json
7
+
8
+ # prevent clashing with attributes of underlying data
9
+ attr_accessor :_proxy, :_raw, :_parent, :_service, :_request
10
+
11
+ def initialize(input, parent = nil, service = nil, request = nil)
12
+ self._raw = raw_from_input(input)
13
+ self._proxy = proxy_from_input(input)
14
+ self._service = service
15
+ self._parent = parent
16
+ self._request = request
17
+ end
18
+
19
+ # merging data
20
+ # e.g. when loading remote data via link
21
+ def merge!(data)
22
+ return false unless data._raw.is_a?(Hash)
23
+ _raw.merge! data._raw
24
+ end
25
+
26
+ def _root
27
+ root = self
28
+ root = root._parent while root && root._parent
29
+ root
30
+ end
31
+
32
+ def class
33
+ _root._service
34
+ end
35
+
36
+ protected
37
+
38
+ # Use existing mapping to provide data
39
+ # or forward to proxy
40
+ def method_missing(name, *args, &block)
41
+ if root_item? && mapping = _root._service.instance.mapping[name]
42
+ mapping.call(self)
43
+ else
44
+ _proxy.send(name, *args, &block)
45
+ end
46
+ end
47
+
48
+ def respond_to_missing?(name, include_all = false)
49
+ (root_item? && _root._service.instance.mapping.keys.map(&:to_s).include?(name.to_s)) ||
50
+ _proxy.respond_to?(name, include_all)
51
+ end
52
+
53
+ private
54
+
55
+ def collection_proxy?(input)
56
+ (_raw.is_a?(Hash) && _raw['items']) ||
57
+ input.is_a?(Array) || _raw.is_a?(Array)
58
+ end
59
+
60
+ def root_item
61
+ return if self._proxy.class != LHS::Item
62
+ root = root_item = self
63
+ loop do
64
+ root = root._parent
65
+ root_item = root if root && root._proxy.is_a?(LHS::Item)
66
+ if !(root && root._parent)
67
+ break
68
+ else
69
+ end
70
+ end
71
+ root_item
72
+ end
73
+
74
+ def root_item?
75
+ root_item == self
76
+ end
77
+
78
+ def root?
79
+ _root == self
80
+ end
81
+
82
+ def proxy_from_input(input)
83
+ if input.is_a? LHS::Proxy
84
+ input
85
+ elsif collection_proxy?(input)
86
+ LHS::Collection.new(self)
87
+ else
88
+ LHS::Item.new(self)
89
+ end
90
+ end
91
+
92
+ def raw_from_input(input)
93
+ if input.is_a?(String) && input.length > 0
94
+ JSON.parse(input)
95
+ elsif defined?(input._raw)
96
+ input._raw
97
+ elsif defined?(input._data)
98
+ input._data._raw
99
+ else
100
+ input
101
+ end
102
+ end
103
+ end