barkibu-kb 0.16.1

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 (72) hide show
  1. checksums.yaml +7 -0
  2. data/.env.example +3 -0
  3. data/.gitignore +20 -0
  4. data/.rspec +3 -0
  5. data/.rubocop.yml +45 -0
  6. data/.ruby-version +2 -0
  7. data/.travis.yml +7 -0
  8. data/CHANGELOG.md +185 -0
  9. data/CODE_OF_CONDUCT.md +74 -0
  10. data/Gemfile +7 -0
  11. data/Gemfile.lock +182 -0
  12. data/LICENSE.txt +21 -0
  13. data/README.md +243 -0
  14. data/Rakefile +28 -0
  15. data/barkibu-kb-fake.gemspec +41 -0
  16. data/barkibu-kb.gemspec +54 -0
  17. data/bin/console +14 -0
  18. data/bin/setup +8 -0
  19. data/docker-compose.yaml +15 -0
  20. data/lib/barkibu-kb-fake.rb +1 -0
  21. data/lib/barkibu-kb.rb +33 -0
  22. data/lib/kb/cache.rb +23 -0
  23. data/lib/kb/client.rb +85 -0
  24. data/lib/kb/client_resolver.rb +62 -0
  25. data/lib/kb/concerns/as_kb_wrapper.rb +67 -0
  26. data/lib/kb/concerns.rb +1 -0
  27. data/lib/kb/errors/client_error.rb +9 -0
  28. data/lib/kb/errors/conflict_error.rb +3 -0
  29. data/lib/kb/errors/error.rb +26 -0
  30. data/lib/kb/errors/resource_not_found.rb +3 -0
  31. data/lib/kb/errors/unprocessable_entity_error.rb +3 -0
  32. data/lib/kb/errors.rb +6 -0
  33. data/lib/kb/fake/api.rb +72 -0
  34. data/lib/kb/fake/bounded_context/pet_family/breeds.rb +15 -0
  35. data/lib/kb/fake/bounded_context/pet_family/hubspot_relationship.rb +17 -0
  36. data/lib/kb/fake/bounded_context/pet_family/pet_contracts.rb +24 -0
  37. data/lib/kb/fake/bounded_context/pet_family/pet_parents.rb +98 -0
  38. data/lib/kb/fake/bounded_context/pet_family/pets.rb +84 -0
  39. data/lib/kb/fake/bounded_context/pet_family/products.rb +28 -0
  40. data/lib/kb/fake/bounded_context/rest_resource.rb +134 -0
  41. data/lib/kb/fake.rb +6 -0
  42. data/lib/kb/inflections.rb +3 -0
  43. data/lib/kb/models/assessment.rb +58 -0
  44. data/lib/kb/models/base_model.rb +40 -0
  45. data/lib/kb/models/breed.rb +39 -0
  46. data/lib/kb/models/concerns/creatable.rb +18 -0
  47. data/lib/kb/models/concerns/destroyable.rb +17 -0
  48. data/lib/kb/models/concerns/find_or_creatable.rb +19 -0
  49. data/lib/kb/models/concerns/findable.rb +19 -0
  50. data/lib/kb/models/concerns/inspectionable.rb +13 -0
  51. data/lib/kb/models/concerns/listable.rb +21 -0
  52. data/lib/kb/models/concerns/queryable.rb +34 -0
  53. data/lib/kb/models/concerns/updatable.rb +18 -0
  54. data/lib/kb/models/concerns/upsertable.rb +17 -0
  55. data/lib/kb/models/concerns.rb +10 -0
  56. data/lib/kb/models/condition.rb +32 -0
  57. data/lib/kb/models/hubspot_relationship.rb +34 -0
  58. data/lib/kb/models/pet.rb +68 -0
  59. data/lib/kb/models/pet_contract.rb +77 -0
  60. data/lib/kb/models/pet_parent.rb +111 -0
  61. data/lib/kb/models/plan.rb +44 -0
  62. data/lib/kb/models/product.rb +34 -0
  63. data/lib/kb/models/symptom.rb +25 -0
  64. data/lib/kb/models.rb +15 -0
  65. data/lib/kb/type/array_of_conditions_type.rb +13 -0
  66. data/lib/kb/type/array_of_strings_type.rb +9 -0
  67. data/lib/kb/type/array_of_symptoms_type.rb +13 -0
  68. data/lib/kb/types.rb +7 -0
  69. data/lib/kb/validators/uniqueness_validator.rb +26 -0
  70. data/lib/kb/validators.rb +1 -0
  71. data/lib/kb/version.rb +3 -0
  72. metadata +325 -0
data/README.md ADDED
@@ -0,0 +1,243 @@
1
+ # Barkibu's Knowledge Base API sdk
2
+
3
+ A wrapper of Barkibu's Knowledge Base Endpoint to make those entities and their respective CRUD operations available to a ruby app.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'barkibu-kb'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install kb
20
+
21
+ ## Usage
22
+
23
+ This gem wraps the Knowledge Base Api and exposes CRUD-_able_ entities into the requiring application.
24
+
25
+ ### Configuration
26
+
27
+ The configuration of the connection to the Knowledge Base is done using ENV variable:
28
+
29
+ - **KB_API_KEY**: the Knowledge Base API Key
30
+ - **KB_PARTNER_KEY**: the Partner KB Key
31
+ - **KB_API_URL_TEMPLATE**: the template url of the Knowledge Base where will be extrapolated _bounded_context_, _version_ and _entity_.
32
+
33
+ For instance: `https://dev.api.%{bounded_context}.barkkb.com/%{version}/%{entity}`.
34
+
35
+ #### Cache configuration
36
+
37
+ We add the ability to cache GET responses. To enable it, just set KB.config.cache properties:
38
+ ```ruby
39
+ # config/initializers/kb_ruby.rb
40
+ KB.config.cache.instance = Rails.cache # ActiveSupport::Cache::NullStore.new
41
+ KB.config.cache.expires_in = 1.second # 0 by default
42
+ KB.config.log_level = :debugger # :info by default
43
+ ```
44
+
45
+ ### Exposed Entities
46
+
47
+ #### Pet Parent 🧍🏾
48
+
49
+ `KB::PetParent` acts almost like an `ActiveRecord` implementing `ActiveModel::Model` exposing:
50
+
51
+ - `find`
52
+ - arg: `key` string
53
+ - returns: a PetParent instance when provided an existing key or raise `ActiveRecord::RecordNotFound`
54
+ - `create`
55
+ - arg: `attributes` to initialize the entity
56
+ - returns: the raw attributes of the created PetParent instance
57
+ - throws an `KB::Error` exception if something went wrong
58
+ - `find_or_create_by`
59
+ - arg: `attributes`, `additional_attributes` to look for or initialize the entity
60
+ - returns: look for a PetParent matching the passed attributes or initialize and persist one with the given attributes and launching the block provided
61
+ - throws an `KB::Error` exception if something went wrong
62
+ - `all`
63
+ - arg: `filters` hash of filters
64
+ - returns: an array of PetParent instances matching the filters
65
+ - `save!`
66
+ - persists (create or update) the entity to the Knowledge Base
67
+ - throws an `KB::Error` exception if something went wrong
68
+ - `destroy!`
69
+ - deletes the entity in the Knowledge Base
70
+ - throws a `KB::Error` exception if something went wrong
71
+ - `contracts`
72
+ - returns all the KB::PetContract associated with this pet parent
73
+
74
+ #### Assessment 📄
75
+
76
+ `KB::Assessment` represents a read-only resource exposing:
77
+
78
+ - `find`
79
+ - arg: `key` string
80
+ - returns: an Assessment instance when provided an existing key or raise `ActiveRecord::RecordNotFound`
81
+ - `all`
82
+ - arg: `filters` hash of filters
83
+ - returns: an array of Assessment instances matching the filters
84
+
85
+ #### Condition 🏷
86
+
87
+ `KB::Condition` represents a read-only resource.
88
+
89
+ #### Symptom 🩺
90
+
91
+ `KB::Symptom` represents a read-only resource.
92
+
93
+ #### Pet 🐶🐱
94
+
95
+ `KB::Pet` represents a resource exposing:
96
+
97
+ - `all`
98
+ - arg: `filters` hash of filters
99
+ - returns: an array of Pet instances matching the filters
100
+ - `create`
101
+ - arg: `attributes` to initialize the entity
102
+ - returns: the raw attributes of the Pet instance
103
+ - throws an `KB::Error` exception if something went wrong
104
+ - `find_or_create_by`
105
+ - arg: `attributes`, `additional_attributes` to look for or initialize the entity
106
+ - returns: look for a Pet matching the passed attributes or initialize and persist one with the given attributes and launching the block provided
107
+ - throws an `KB::Error` exception if something went wrong
108
+ - `save!`
109
+ - persists (create or update) the entity to the Knowledge Base
110
+ - throws an `KB::Error` exception if something went wrong
111
+ - `destroy!`
112
+ - deletes the entity in the Knowledge Base
113
+ - throws a `KB::Error` exception if something went wrong
114
+ - `contracts`
115
+ - returns all the KB::PetContract associated with this pet
116
+ - `upsert`
117
+ - updates KB:Pet if exists a Pet with same name for its PetParent
118
+ - creates a new KB:Pet if not exists a Pet with same name for its PetParent
119
+
120
+ #### PetContract 📝
121
+
122
+ `KB::PetContract` represents a resource exposing:
123
+
124
+ - `find`
125
+ - arg: `key` the key of the contract in the Knowledge Base
126
+ - returns the contract with the matching key
127
+ - throws an `KB::Error` or `KB::ResourceNotFound` exception if something went wrong
128
+ - `find_by_contract_number`
129
+ - arg: `contract_number` the contract number to find
130
+ - returns the identified KB::PetContract
131
+ - throws an `KB::Error` or `KB::ResourceNotFound` exception if something went wrong
132
+ - `create`
133
+ - arg: `attributes` to initialize the entity
134
+ - returns: the raw attributes of the PetContract instance
135
+ - throws an `KB::Error` exception if something went wrong
136
+ - `save!`
137
+ - persists (create or update) the entity to the Knowledge Base
138
+ - throws an `KB::Error` exception if something went wrong
139
+
140
+ #### Plan 🗺
141
+
142
+ `KB::Plan` represents a resource exposing:
143
+
144
+ - `all`
145
+ - returns: the array of available plans
146
+
147
+ #### Breed
148
+
149
+ ```
150
+ > KB Breed endpoint requires `locale` as param. By default is set to 'es-es' but can be override setting **KB_BREEDS_DEFAULT_LOCALE** ENV var
151
+ ```
152
+
153
+ `KB::Breed` represents a resource exposing:
154
+
155
+ - `all`
156
+ - arg: `filters` hash of filters
157
+ - returns: and array of Breed instances matching the filters
158
+ - `dogs` (alias for all(species: 'dog'))
159
+ - arg: `filters` hash of filters
160
+ - returns: and array of Dog Breed instances matching the filters
161
+ - `cats` (alias for all(species: 'cat'))
162
+ - arg: `filters` hash of filters
163
+ - returns: and array of Cat Breed instances matching the filters
164
+
165
+ ### Make an ActiveRecord wrap a KB entity
166
+
167
+ The `KB::Concerns::AsKBWrapper` concern has been created in order to easily make an ActiveRecord model wrap a KB model.
168
+
169
+ To use it:
170
+
171
+ - include it into your wrapping model, define an attribute `kb_key` on your wrapping model
172
+ - call `wrap_kb` with the wrapped KB model class (available option: `skip_callback`)
173
+
174
+ You have then access to the wrapped model under `kb_model` and can delegate attributes to it, for instance:
175
+
176
+ ```ruby
177
+ class User < ActiveRecord::Base
178
+ include KB::Concerns::AsKBWrapper
179
+
180
+ wrap_kb model: KB::PetParent
181
+
182
+ KB_DELEGATED_ATTRIBUTES = %i[email first_name].freeze
183
+
184
+ KB_DELEGATED_ATTRIBUTES.each do |attribute|
185
+ delegate attribute, to: :kb_model, prefix: false
186
+ delegate "#{attribute}=", to: :kb_model, prefix: false
187
+ end
188
+ end
189
+
190
+ user = User.create(first_name: 'Léo', email: 'leo@barkibu.com')
191
+ p user.kb_model
192
+ # => #<KB::PetParent: 0x000055fd72d32c30 key: "373ad90e-c2ce-46cb-9749-deb2b03be995", first_name: "Léo", ..., email: "leo@barkibu.com">
193
+ ```
194
+
195
+ ### Mock the API calls in test environment
196
+
197
+ For testing purposes, you may want to fake Knowledge Base’s API calls. We provide a FakeApi app that you can use including the `kb-fake` gem in your dev/test dependencies.
198
+
199
+ Add gem webmock and gem sinatra to your Gemfile to use the following configuration:
200
+
201
+ ```ruby
202
+ # ...
203
+
204
+ RSpec.configure do |config|
205
+ # ...
206
+ config.before(:all) do
207
+ stub_request(:any, /test_api_barkkb.com/).to_rack(KB::Fake::Api)
208
+ end
209
+
210
+ config.around(:each) do |example|
211
+ snapshot = KB::Fake::Api.snapshot()
212
+ stub_request(:any, /test_api_barkkb.com/).to_rack(KB::Fake::Api)
213
+ example.run
214
+ KB::Fake::Api.restore snapshot
215
+ end
216
+ # ...
217
+ end
218
+ ```
219
+
220
+ Make sure to set the `KB_API_URL_TEMPLATE` to something that will match above the request interceptor, for instance: `http://test_api_barkkb.com/%{version}/%{entity}`
221
+
222
+ You should be able to use the API seemlessly and the calls to the API will be intercepted and a local one used instead in a similar fashion to how ActiveRecord operations are wrapped into a transaction in a rails app with `use_transactional_fixtures` activated.
223
+
224
+ ## Development & Testing
225
+
226
+ ```bash
227
+ docker run -it --rm -v $(pwd):/app --workdir=/app ruby:2.6.5 bash
228
+
229
+ > bundle install
230
+ > rspec
231
+ ```
232
+
233
+ ## Contributing
234
+
235
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/kb. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
236
+
237
+ ## License
238
+
239
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
240
+
241
+ ## Code of Conduct
242
+
243
+ Everyone interacting in the KB project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/kb/blob/master/CODE_OF_CONDUCT.md).
data/Rakefile ADDED
@@ -0,0 +1,28 @@
1
+ lib = File.expand_path('lib', __dir__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'rspec/core/rake_task'
4
+ require 'kb/version'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ desc 'Build gem into the pkg directory'
9
+ task :build do
10
+ FileUtils.rm_rf('pkg')
11
+ Dir['*.gemspec'].each do |gemspec|
12
+ system "gem build #{gemspec}"
13
+ end
14
+ FileUtils.mkdir_p('pkg')
15
+ FileUtils.mv(Dir['*.gem'], 'pkg')
16
+ end
17
+
18
+ desc 'Tags version, pushes to remote, and pushes gem'
19
+ task release: :build do
20
+ sh 'git', 'tag', '-m', changelog, "v#{KB::VERSION}"
21
+ sh 'git push origin master'
22
+ sh "git push origin v#{KB::VERSION}"
23
+ sh 'ls pkg/*.gem | xargs -n 1 gem push'
24
+ end
25
+
26
+ def changelog
27
+ File.read('CHANGELOG.md').split(/##.*$\n/)[1].gsub("\n\n", "\n")
28
+ end
@@ -0,0 +1,41 @@
1
+ lib = File.expand_path('lib', __dir__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'kb/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'barkibu-kb-fake'
7
+ spec.version = KB::VERSION
8
+ spec.authors = ['Léo Figea']
9
+ spec.email = ['leo@barkibu.com']
10
+
11
+ spec.summary = "Barkibu's Knowledge Base Fake API"
12
+ spec.description = "A fake API of Barkibu's Knowledge Base destined to be used in tests for application using the kb-ruby gem." # rubocop:disable Layout/LineLength
13
+ spec.homepage = 'https://app.barkibu.com'
14
+ spec.license = 'MIT'
15
+
16
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
17
+ # to allow pushing to a single host or delete this section to allow pushing to any host.
18
+ if spec.respond_to?(:metadata)
19
+ # spec.metadata['allowed_push_host'] = 'http://mygemserver.com'
20
+
21
+ spec.metadata['homepage_uri'] = spec.homepage
22
+ # spec.metadata["source_code_uri"] = "TODO: Put your gem's public repo URL here."
23
+ # spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
24
+ else
25
+ raise 'RubyGems 2.0 or newer is required to protect against ' \
26
+ 'public gem pushes.'
27
+ end
28
+
29
+ # Specify which files should be added to the gem when it is released.
30
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
31
+ spec.files = `git ls-files -- lib | grep fake`.split("\n").reject { |f| f.match(%r{^(test|spec|features)/}) }
32
+ spec.bindir = 'exe'
33
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
34
+ spec.require_paths = ['lib']
35
+ spec.required_ruby_version = '>= 2.6'
36
+
37
+ spec.add_runtime_dependency 'countries'
38
+ spec.add_runtime_dependency 'barkibu-kb', KB::VERSION
39
+ spec.add_runtime_dependency 'sinatra'
40
+ spec.add_runtime_dependency 'webmock'
41
+ end
@@ -0,0 +1,54 @@
1
+ lib = File.expand_path('lib', __dir__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'kb/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'barkibu-kb'
7
+ spec.version = KB::VERSION
8
+ spec.authors = ['Léo Figea']
9
+ spec.email = ['leo@barkibu.com']
10
+
11
+ spec.summary = "Barkibu's Knowledge Base API sdk"
12
+ spec.description = "A wrapper of Barkibu's Knowledge Base Endpoint to make those entities and their respective CRUD operations available to a ruby app" # rubocop:disable Layout/LineLength
13
+ spec.homepage = 'https://app.barkibu.com'
14
+ spec.license = 'MIT'
15
+
16
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
17
+ # to allow pushing to a single host or delete this section to allow pushing to any host.
18
+ if spec.respond_to?(:metadata)
19
+ # spec.metadata['allowed_push_host'] = 'http://mygemserver.com'
20
+
21
+ spec.metadata['homepage_uri'] = spec.homepage
22
+ # spec.metadata["source_code_uri"] = "TODO: Put your gem's public repo URL here."
23
+ # spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
24
+ else
25
+ raise 'RubyGems 2.0 or newer is required to protect against ' \
26
+ 'public gem pushes.'
27
+ end
28
+
29
+ # Specify which files should be added to the gem when it is released.
30
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
31
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
32
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
33
+ end
34
+ spec.bindir = 'exe'
35
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
36
+ spec.require_paths = ['lib']
37
+ spec.required_ruby_version = '>= 2.6'
38
+
39
+ spec.add_dependency 'dry-configurable', '~> 0.9'
40
+ spec.add_development_dependency 'bundler', '~> 1.17'
41
+ spec.add_development_dependency 'byebug'
42
+ spec.add_development_dependency 'rake', '>= 12.3.3'
43
+ spec.add_development_dependency 'rspec', '~> 3.0'
44
+ spec.add_development_dependency 'rubocop'
45
+ spec.add_development_dependency 'rubocop-rspec'
46
+ spec.add_development_dependency 'simplecov'
47
+ spec.add_runtime_dependency 'activemodel', '>= 4.0.2'
48
+ spec.add_runtime_dependency 'activerecord'
49
+ spec.add_runtime_dependency 'activesupport', '>= 3.0.0'
50
+ spec.add_runtime_dependency 'faraday'
51
+ spec.add_runtime_dependency 'faraday-http'
52
+ spec.add_runtime_dependency 'faraday_middleware'
53
+ spec.add_runtime_dependency 'i18n'
54
+ end
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'barkibu-kb'
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require 'irb'
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,15 @@
1
+ version: "3.0"
2
+ services:
3
+ kb:
4
+ image: ruby:2.7.1-buster
5
+ working_dir: /app
6
+ volumes:
7
+ - .:/app
8
+ - bundle:/usr/local/bundle
9
+ environment:
10
+ - KB_API_URL_TEMPLATE=$KB_API_URL_TEMPLATE
11
+ - KB_PARTNER_KEY=$KB_PARTNER_KEY
12
+ - KB_API_KEY=$KB_API_KEY
13
+
14
+ volumes:
15
+ bundle:
@@ -0,0 +1 @@
1
+ require 'kb/fake'
data/lib/barkibu-kb.rb ADDED
@@ -0,0 +1,33 @@
1
+ require'byebug'
2
+ require 'kb/version'
3
+ require 'active_model'
4
+ require 'active_record'
5
+ require 'active_support'
6
+ require 'active_support/core_ext/array'
7
+ require 'faraday'
8
+ require 'faraday_middleware'
9
+ require 'faraday/http'
10
+ require 'dry/configurable'
11
+
12
+ module KB
13
+ extend Dry::Configurable
14
+
15
+ setting :cache do
16
+ setting :instance, default: ActiveSupport::Cache::NullStore.new
17
+ setting :expires_in, default: 0
18
+ end
19
+
20
+ setting :log_level, default: :info
21
+ end
22
+
23
+ require 'kb/inflections'
24
+
25
+ require 'kb/cache'
26
+ require 'kb/client_resolver'
27
+ require 'kb/errors'
28
+ require 'kb/client'
29
+
30
+ require 'kb/concerns'
31
+ require 'kb/models'
32
+
33
+ require 'kb/validators'
data/lib/kb/cache.rb ADDED
@@ -0,0 +1,23 @@
1
+ module KB
2
+ class Cache
3
+ class << self
4
+ def delete(key)
5
+ instance.delete(key)
6
+ end
7
+
8
+ def fetch(key, &block)
9
+ instance.fetch(key, expires_in: cache_expiration, &block)
10
+ end
11
+
12
+ private
13
+
14
+ def instance
15
+ KB.config.cache.instance
16
+ end
17
+
18
+ def cache_expiration
19
+ KB.config.cache.expires_in.to_f
20
+ end
21
+ end
22
+ end
23
+ end
data/lib/kb/client.rb ADDED
@@ -0,0 +1,85 @@
1
+ module KB
2
+ class Client
3
+ attr_reader :api_key, :base_url
4
+
5
+ def initialize(base_url, api_key: ENV['KB_API_KEY'])
6
+ @api_key = api_key
7
+ @base_url = base_url
8
+ end
9
+
10
+ def request(sub_path, filters: nil, method: :get)
11
+ return connection.public_send(method, sub_path, filters).body if method != :get
12
+
13
+ cache_key = "#{@base_url}/#{sub_path}/#{(filters || {}).sort.to_h}"
14
+ KB::Cache.fetch(cache_key) do
15
+ connection.public_send(method, sub_path, filters).body
16
+ end
17
+ end
18
+
19
+ def all(filters = {})
20
+ cache_key = "#{@base_url}/#{filters.sort.to_h}"
21
+
22
+ KB::Cache.fetch(cache_key) do
23
+ connection.get('', attributes_case_transform(filters)).body
24
+ end
25
+ end
26
+
27
+ def find(key, params = {})
28
+ raise Faraday::ResourceNotFound, {} if key.blank?
29
+
30
+ KB::Cache.fetch("#{@base_url}/#{key}") do
31
+ connection.get(key, attributes_case_transform(params)).body
32
+ end
33
+ end
34
+
35
+ def create(attributes)
36
+ connection.post('', attributes_to_json(attributes)).body
37
+ end
38
+
39
+ def update(key, attributes)
40
+ KB::Cache.delete("#{@base_url}/#{key}")
41
+ connection.patch(key.to_s, attributes_to_json(attributes)).body
42
+ end
43
+
44
+ def destroy(key)
45
+ KB::Cache.delete("#{@base_url}/#{key}")
46
+ connection.delete(key.to_s).body
47
+ end
48
+
49
+ def upsert(attributes)
50
+ connection.put('', attributes_to_json(attributes)).body
51
+ end
52
+
53
+ private
54
+
55
+ def headers
56
+ {
57
+ 'Content-Type': 'application/json',
58
+ 'x-api-key': api_key
59
+ }
60
+ end
61
+
62
+ def attributes_case_transform(attributes)
63
+ attributes.transform_keys do |key|
64
+ key.to_s.camelize(:lower)
65
+ end
66
+ end
67
+
68
+ def attributes_to_json(attributes)
69
+ attributes_case_transform(attributes).to_json
70
+ end
71
+
72
+ def connection
73
+ @connection ||= Faraday.new(url: base_url, headers: headers) do |conn|
74
+ conn.response :json
75
+ conn.response :raise_error
76
+ if KB.config.log_level == :debugger
77
+ conn.response :logger do |logger|
78
+ logger.filter(/(X-api-key:\s)("\w+")/, '\1[API_KEY_SCRUBBED]')
79
+ end
80
+ end
81
+ conn.adapter :http
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,62 @@
1
+ module KB
2
+ class ClientResolver
3
+ class << self
4
+ def consultation
5
+ call :petfamily, :consultations, :v1
6
+ end
7
+
8
+ def admin
9
+ call :petfamily, :admin
10
+ end
11
+
12
+ def pet_parent
13
+ call :petfamily, :petparents
14
+ end
15
+
16
+ def pet
17
+ call :petfamily, :pets
18
+ end
19
+
20
+ def pet_contract
21
+ call :petfamily, :petcontracts
22
+ end
23
+
24
+ def breed
25
+ call :petfamily, :breeds
26
+ end
27
+
28
+ def plan
29
+ call :petfamily, :plans
30
+ end
31
+
32
+ def product
33
+ call :petfamily, :products
34
+ end
35
+
36
+ def hubspot
37
+ call :petfamily, :hubspot
38
+ end
39
+
40
+ def call(*args, &block)
41
+ new(*args, &block).call
42
+ end
43
+ end
44
+
45
+ attr_reader :template, :bounded_context, :entity, :version
46
+
47
+ def initialize(bounded_context, entity, version = 'v1', template: ENV['KB_API_URL_TEMPLATE'])
48
+ @bounded_context = bounded_context
49
+ @entity = entity
50
+ @version = version
51
+ @template = template
52
+ end
53
+
54
+ def call
55
+ KB::Client.new base_url
56
+ end
57
+
58
+ def base_url
59
+ format(template, bounded_context: bounded_context, entity: entity, version: version).gsub('..', '.')
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,67 @@
1
+ module KB
2
+ module Concerns
3
+ module AsKBWrapper
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ attr_writer :kb_model
8
+
9
+ def save_underlying_kb_entity!
10
+ kb_model.save!
11
+ self.kb_key = kb_model.key if kb_model.key.present?
12
+ end
13
+
14
+ def destroy_underlying_kb_entity
15
+ kb_model.destroy!
16
+ end
17
+
18
+ def reload(**options)
19
+ @kb_model = nil
20
+ super(**options)
21
+ end
22
+ end
23
+
24
+ # rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/BlockLength
25
+ class_methods do
26
+ def wrap_kb(model:, skip_callback: false)
27
+ before_save :save_underlying_kb_entity! unless skip_callback
28
+ before_destroy :destroy_underlying_kb_entity unless skip_callback
29
+
30
+ define_method(:kb_model) do
31
+ underlying_kb_entity = @kb_model
32
+
33
+ if underlying_kb_entity.blank?
34
+ underlying_kb_entity = begin
35
+ model.find kb_key
36
+ rescue KB::ResourceNotFound
37
+ model.new
38
+ end
39
+
40
+ @kb_model = underlying_kb_entity
41
+ end
42
+
43
+ underlying_kb_entity
44
+ end
45
+
46
+ singleton_class.instance_eval do
47
+ define_method(:kb_find_by) do |**attributes|
48
+ kb_model = model.all(attributes).first
49
+ return nil if kb_model.nil?
50
+
51
+ local_model = find_by(kb_key: kb_model.key)
52
+ return nil if local_model.nil?
53
+
54
+ local_model.kb_model = kb_model
55
+ local_model
56
+ end
57
+ end
58
+ end
59
+
60
+ def kb_find_by!(**attributes)
61
+ kb_find_by(attributes).presence || raise(ActiveRecord::RecordNotFound)
62
+ end
63
+ end
64
+ # rubocop:enable Metrics/MethodLength, Metrics/AbcSize, Metrics/BlockLength
65
+ end
66
+ end
67
+ end
@@ -0,0 +1 @@
1
+ require 'kb/concerns/as_kb_wrapper'