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,86 @@
1
+ # Like ActiveModel::Errors
2
+ class LHS::Errors
3
+ include Enumerable
4
+
5
+ attr_reader :messages, :message
6
+
7
+ def initialize(response)
8
+ @messages = messages_from_response(response)
9
+ @message = message_from_response(response)
10
+ rescue JSON::ParserError
11
+ end
12
+
13
+ def include?(attribute)
14
+ messages[attribute].present?
15
+ end
16
+ alias :has_key? :include?
17
+ alias :key? :include?
18
+
19
+ def get(key)
20
+ messages[key]
21
+ end
22
+
23
+ def set(key, value)
24
+ messages[key] = value
25
+ end
26
+
27
+ def delete(key)
28
+ messages.delete(key)
29
+ end
30
+
31
+ def [](attribute)
32
+ get(attribute.to_sym) || set(attribute.to_sym, [])
33
+ end
34
+
35
+ def []=(attribute, error)
36
+ self[attribute] << error
37
+ end
38
+
39
+ def each
40
+ messages.each_key do |attribute|
41
+ self[attribute].each { |error| yield attribute, error }
42
+ end
43
+ end
44
+
45
+ def size
46
+ values.flatten.size
47
+ end
48
+
49
+ def values
50
+ messages.values
51
+ end
52
+
53
+ def keys
54
+ messages.keys
55
+ end
56
+
57
+ def count
58
+ to_a.size
59
+ end
60
+
61
+ def empty?
62
+ all? { |k, v| v && v.empty? && !v.is_a?(String) }
63
+ end
64
+
65
+ private
66
+
67
+ def messages_from_response(response)
68
+ messages = {}
69
+ return messages if !response.body.is_a?(String) || response.body.length.zero?
70
+ json = JSON.parse(response.body)
71
+ return messages unless json['fields']
72
+ json['fields'].each do |field|
73
+ name = field['name'].to_sym
74
+ messages[name] ||= []
75
+ field['details'].each do |detail|
76
+ messages[name].push(detail['code'])
77
+ end
78
+ end
79
+ messages
80
+ end
81
+
82
+ def message_from_response(response)
83
+ json = JSON.parse(response.body)
84
+ json['message']
85
+ end
86
+ end
@@ -0,0 +1,83 @@
1
+ require File.join(__dir__, 'proxy.rb')
2
+ Dir[File.dirname(__FILE__) + '/concerns/item/*.rb'].each {|file| require file }
3
+
4
+ # An item is a concrete record.
5
+ # It can be part of another proxy like collection.
6
+ class LHS::Item < LHS::Proxy
7
+ include Destroy
8
+ include Save
9
+ include Update
10
+
11
+ # prevent clashing with attributes of underlying data
12
+ attr_accessor :errors
13
+
14
+ def _raw
15
+ _data._raw
16
+ end
17
+
18
+ protected
19
+
20
+ def method_missing(name, *args, &block)
21
+ return set(name, args.try(&:first)) if name.to_s[/=$/]
22
+ name = args.first if name == :[]
23
+ value = _data._raw[name.to_s]
24
+ value = _data._raw[name.to_sym] if value.nil?
25
+ if value.is_a?(Hash)
26
+ handle_hash(value)
27
+ elsif value.is_a?(Array)
28
+ data = LHS::Data.new(value, _data)
29
+ collection = LHS::Collection.new(data)
30
+ LHS::Data.new(collection, _data)
31
+ else
32
+ convert(value)
33
+ end
34
+ end
35
+
36
+ def respond_to_missing?(name, include_all = false)
37
+ # We accept every message that does not belong to set of keywords
38
+ BLACKLISTED_KEYWORDS.exclude?(name.to_s)
39
+ end
40
+
41
+ private
42
+
43
+ # FIXME: Extend the set of keywords
44
+ BLACKLISTED_KEYWORDS = %w( new proxy_association )
45
+
46
+ def convert(value)
47
+ return value unless value.is_a?(String)
48
+ if date_time?(value)
49
+ DateTime.parse(value)
50
+ elsif date?(value)
51
+ Date.parse(value)
52
+ else
53
+ value
54
+ end
55
+ end
56
+
57
+ def handle_hash(value)
58
+ LHS::Data.new(value, _data)
59
+ end
60
+
61
+ def set(name, value)
62
+ key = name.to_s.gsub(/=$/, '')
63
+ _data._raw[key] = value
64
+ end
65
+
66
+ private
67
+
68
+ def date?(value)
69
+ value[date_time_regex, :date].presence
70
+ end
71
+
72
+ def time?(value)
73
+ value[date_time_regex, :time].presence
74
+ end
75
+
76
+ def date_time?(value)
77
+ date?(value) && time?(value)
78
+ end
79
+
80
+ def date_time_regex
81
+ /(?<date>\d{4}-\d{2}-\d{2})?(?<time>T\d{2}:\d{2}:\d{2}\.\d*.\d{2}:\d{2})?/
82
+ end
83
+ end
@@ -0,0 +1,26 @@
1
+ # Proxy makes different kind of data accessible
2
+ # If href is present it also alows loading/reloading
3
+ class LHS::Proxy
4
+
5
+ # prevent clashing with attributes of underlying data
6
+ attr_accessor :_data, :_loaded
7
+
8
+ def initialize(data)
9
+ self._data = data
10
+ self._loaded = false
11
+ end
12
+
13
+ def load!
14
+ return self if _loaded
15
+ reload!
16
+ end
17
+
18
+ def reload!
19
+ fail 'No href found' unless _data.href
20
+ service = _data._root._service
21
+ data = service.instance.request(url: _data.href, method: :get)
22
+ merge!(data)
23
+ self._loaded = true
24
+ self
25
+ end
26
+ end
@@ -0,0 +1,20 @@
1
+ require 'singleton'
2
+ Dir[File.dirname(__FILE__) + '/concerns/service/*.rb'].each {|file| require file }
3
+
4
+ # A Service makes data available using multiple endpoints.
5
+ class LHS::Service
6
+ include All
7
+ include Batch
8
+ include Build
9
+ include Create
10
+ include Endpoints
11
+ include Find
12
+ include FindBy
13
+ include First
14
+ include Mapping
15
+ include Model
16
+ include Includes
17
+ include Request
18
+ include Singleton
19
+ include Where
20
+ end
@@ -0,0 +1,3 @@
1
+ module LHS
2
+ VERSION = "0.3.0"
3
+ end
@@ -0,0 +1,19 @@
1
+ #!/bin/bash -e
2
+
3
+ # custom vars
4
+ # RUBY = ruby-1.9.3-p125
5
+ # GEMSET = location-app-${GitHub pull request id|branch name}
6
+ # standard vars
7
+ # https://jenkins.intra.local.ch/env-vars.html/
8
+
9
+ if [ -z "$SKIP_RVM" ]; then
10
+ [[ -s "/usr/local/rvm/scripts/rvm" ]] && . "/usr/local/rvm/scripts/rvm"
11
+ RUBY=${RUBY:-ruby-2.1.2}
12
+ GEMSET=${GEMSET:-LHS}
13
+ rvm use ${RUBY}@${GEMSET} --create
14
+ gem install bundler --no-rdoc --no-ri
15
+ fi
16
+
17
+ bundle install --local || bundle install
18
+ RAILS_ENV=test bundle exec rspec
19
+ rvm --force gemset delete ${RUBY}@${GEMSET}
@@ -0,0 +1,54 @@
1
+ require 'rails_helper'
2
+
3
+ describe LHS::Collection do
4
+
5
+ let(:total) { 0 }
6
+
7
+ let(:limit) { 50 }
8
+
9
+ let(:offset) { 0 }
10
+
11
+ let(:collection) do
12
+ {
13
+ href: "#{datastore}/feedbacks",
14
+ items: [],
15
+ total: total,
16
+ limit: limit,
17
+ offset: offset
18
+ }
19
+ end
20
+
21
+ let(:item) do
22
+ {
23
+ href: "#{datastore}/users/1",
24
+ feedbacks: collection
25
+ }
26
+ end
27
+
28
+ let(:datastore) { 'http://datastore-stg.lb-service.sunrise.intra.local.ch/v2' }
29
+
30
+ before(:each) do
31
+ LHC.config.placeholder('datastore', datastore)
32
+ class Feedback < LHS::Service
33
+ endpoint ':datastore/feedbacks'
34
+ end
35
+ class User < LHS::Service
36
+ endpoint ':datastore/users'
37
+ end
38
+ end
39
+
40
+ it 'provides meta data for collections' do
41
+ stub_request(:get, "#{datastore}/feedbacks").to_return(status: 200, body: collection.to_json)
42
+ feedbacks = Feedback.where
43
+ expect(feedbacks.total).to eq total
44
+ expect(feedbacks.limit).to eq limit
45
+ expect(feedbacks.offset).to eq offset
46
+ expect(feedbacks.href).to eq "#{datastore}/feedbacks"
47
+ end
48
+
49
+ it 'provides meta data also when navigating' do
50
+ stub_request(:get, "#{datastore}/users/1").to_return(status: 200, body: item.to_json)
51
+ user = User.find(1)
52
+ expect(user.feedbacks.href).to eq "#{datastore}/feedbacks"
53
+ end
54
+ end
@@ -0,0 +1,19 @@
1
+ require 'rails_helper'
2
+
3
+ describe LHS::Collection do
4
+
5
+ let(:data) {
6
+ ['ROLE_USER', 'ROLE_LOCALCH_ACCOUNT']
7
+ }
8
+
9
+ let(:collection){
10
+ described_class.new(LHS::Data.new(data))
11
+ }
12
+
13
+ context '#respond_to?' do
14
+ # In this case raw collection is an Array implementing first
15
+ it 'forwards calls to raw collection' do
16
+ expect(collection.respond_to?(:first)).to be(true)
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,26 @@
1
+ require 'rails_helper'
2
+
3
+ describe LHS::Collection do
4
+
5
+ let(:datastore) { 'http://datastore-stg.lb-service.sunrise.intra.local.ch/v2' }
6
+
7
+ before(:each) do
8
+ LHC.config.placeholder('datastore', datastore)
9
+ class Account < LHS::Service
10
+ endpoint ':datastore/accounts'
11
+ end
12
+ end
13
+
14
+ let(:data) {
15
+ {
16
+ 'authorities' => ['ROLE_USER', 'ROLE_LOCALCH_ACCOUNT']
17
+ }
18
+ }
19
+
20
+ it 'lets you access items of an array if they are not objects' do
21
+ stub_request(:get, "#{datastore}/accounts/1").to_return(status: 200, body: data.to_json)
22
+ feedback = Account.find(1)
23
+ expect(feedback.authorities.first).to eq 'ROLE_USER'
24
+ expect(feedback.authorities[1]).to eq 'ROLE_LOCALCH_ACCOUNT'
25
+ end
26
+ end
@@ -0,0 +1,36 @@
1
+ require 'rails_helper'
2
+
3
+ describe LHS::Data do
4
+
5
+ let(:json) do
6
+ load_json(:feedbacks)
7
+ end
8
+
9
+ let(:data) do
10
+ LHS::Data.new(json)
11
+ end
12
+
13
+ context 'collections' do
14
+
15
+ it 'forwards calls to the collection' do
16
+ expect(data.count).to be_kind_of Fixnum
17
+ expect(data[0]).to be_kind_of LHS::Data
18
+ expect(data.sample).to be_kind_of LHS::Data
19
+ end
20
+
21
+ it 'provides a total method to get the number of total records' do
22
+ expect(data.total).to be_kind_of Fixnum
23
+ end
24
+ end
25
+
26
+ context 'collections from arrays' do
27
+
28
+ let(:data) do
29
+ LHS::Data.new([1,2,3,4])
30
+ end
31
+
32
+ it 'uses collection as proxy for arrays' do
33
+ expect(data._proxy).to be_kind_of LHS::Collection
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,44 @@
1
+ require 'rails_helper'
2
+
3
+ describe LHS::Data do
4
+
5
+ before(:each) do
6
+ class SomeService < LHS::Service
7
+ endpoint ':datastore/v2/:campaign_id/feedbacks'
8
+ endpoint ':datastore/v2/feedbacks'
9
+ end
10
+ end
11
+
12
+ let(:json) do
13
+ load_json(:feedbacks)
14
+ end
15
+
16
+ let(:data) do
17
+ LHS::Data.new(json, nil, SomeService)
18
+ end
19
+
20
+ let(:item) do
21
+ data[0]
22
+ end
23
+
24
+ context 'item' do
25
+
26
+ it 'makes data accessible' do
27
+ expect(item.href).to be_kind_of String
28
+ expect(item.recommended).to be_kind_of TrueClass
29
+ expect(item.average_rating).to be_kind_of Float
30
+ end
31
+
32
+ it 'returns nil if no data is present' do
33
+ expect(item.something).to eq nil
34
+ end
35
+
36
+ it 'returns datetime if string can be parsed as date_time' do
37
+ expect(item.created_date).to be_kind_of DateTime
38
+ end
39
+
40
+ it 'returns date if string can be parsed as date' do
41
+ expect(item.valid_from).to be_kind_of Date
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,32 @@
1
+ require 'rails_helper'
2
+
3
+ describe LHS::Data do
4
+
5
+ before(:each) do
6
+ class SomeService < LHS::Service
7
+ endpoint ':datastore/v2/:campaign_id/feedbacks'
8
+ endpoint ':datastore/v2/feedbacks'
9
+ end
10
+ end
11
+
12
+ let(:data) do
13
+ LHS::Data.new({
14
+ href: 'http://www.local.ch/v2/stuff'
15
+ }, nil, SomeService)
16
+ end
17
+
18
+ let(:loaded_data) do
19
+ LHS::Data.new({
20
+ href: 'http://www.local.ch/v2/stuff',
21
+ id: '123123123'
22
+ }, nil, SomeService)
23
+ end
24
+
25
+ context 'merging' do
26
+
27
+ it 'merges data' do
28
+ data.merge!(loaded_data)
29
+ expect(data.id).to eq loaded_data.id
30
+ end
31
+ end
32
+ end