lhs 0.3.0

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