jahuty 1.1.1 → 3.1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ff13faee32c61d478e8fabba2b5dd1167580ecac2eb1a422fadae594ccd08658
4
- data.tar.gz: d2c91dce7f2bf07882d7630165369442fc069e2e3bb642e7efb61100e3ac23a6
3
+ metadata.gz: 88cfffd7646bfc5849b4f45076f7dba175a65b8a5a89734e00db137dc49ee6e4
4
+ data.tar.gz: 85d9a607c72e94f041adc4fe22da7309f6228f6c07b50b49a1ad3f6c768be67c
5
5
  SHA512:
6
- metadata.gz: 368bbb9bada206a90887549a7757449ff8d8ac4371ba3004d483f87bfcb088c7cd27ed2597ab09e90250dafdd8ac7e5174d7021f79b03efd305c00bd154d5e79
7
- data.tar.gz: 8ceda7e751c0c5c6f3fce92e9274545ae02d55afd3f71f8d752ccac440056ed6cdcf7c9129d89cf46015eccb633a6ac28d08c9c9fd2975e306d4075517139024
6
+ metadata.gz: d0292d5ee64c8d715a9506ef3586c0bcacddacf9c5b97a384b6bc608b119152220f3eda45f6a4b35792a7aab0bfa6895db0ed747b842ab4d45d561d0bdd7a271
7
+ data.tar.gz: dba24f33415884a663f959c9a8ebf7b443773b88ade368d46a2c7026c8cc1ead6b60c481b4d5fb9f4e956ba6ef2d41dbdf12b20fb9a333a4e75544cdd9c00759
@@ -0,0 +1,33 @@
1
+ version: 2.1
2
+ orbs:
3
+ ruby: circleci/ruby@0.1.2
4
+ codecov: codecov/codecov@1.1.3
5
+ jobs:
6
+ build:
7
+ docker:
8
+ - image: circleci/ruby:2.6.3-stretch-node
9
+ executor: ruby/default
10
+ steps:
11
+ - checkout
12
+ - run:
13
+ name: Update bundler
14
+ command: gem install bundler
15
+ - run:
16
+ name: Which bundler?
17
+ command: bundle -v
18
+ - ruby/bundle-install
19
+ - run:
20
+ name: Run rubocop
21
+ command: bundle exec rake rubocop
22
+ - run:
23
+ name: Run specs
24
+ command: |
25
+ bundle exec rspec --profile 10 \
26
+ --format RspecJunitFormatter \
27
+ --out test_results/rspec.xml \
28
+ --format progress \
29
+ $(circleci tests glob "spec/**/*_spec.rb" | circleci tests split --split-by=timings)
30
+ - store_test_results:
31
+ path: test_results
32
+ - codecov/upload:
33
+ file: ./coverage/coverage.xml
@@ -0,0 +1,15 @@
1
+ require:
2
+ - rubocop-performance
3
+ - rubocop-rspec
4
+ AllCops:
5
+ TargetRubyVersion: 2.6
6
+ NewCops: enable
7
+ Metrics/BlockLength:
8
+ Exclude:
9
+ - 'jahuty.gemspec'
10
+ - 'Rakefile'
11
+ - '**/*.rake'
12
+ - 'spec/**/*.rb'
13
+ Metrics/ModuleLength:
14
+ Exclude:
15
+ - 'spec/**/*.rb'
@@ -5,6 +5,32 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## 3.1.0 - 2020-01-04
9
+
10
+ - Add caching support for any cache implementation that supports `get/set` or `read/write` methods.
11
+ - Default to using in-memory [mini-cache](https://github.com/derrickreimer/mini_cache) storage.
12
+
13
+ ## 3.0.0 - 2020-12-30
14
+
15
+ - Change from a static-based architecture (e.g., `Jahuty::Snippet.render(1)`) to an instance-based one (e.g., `jahuty.snippets.render(1)`) to make the library easier to develop, test, and use.
16
+ - Change `README` and fix broken links from `www.jahuty.com/docs` to `docs.jahuty.com`.
17
+ - Add [CircleCI](https://circleci.com/gh/jahuty/jahuty-ruby) continuous integration.
18
+ - Add [Rubocop](https://github.com/rubocop-hq/rubocop) code analyzer and formatter, based on the community style guide.
19
+ - Add code coverage analysis with [Simplecov](https://github.com/simplecov-ruby/simplecov) and [Codecov.io](https://codecov.io/gh/jahuty/jahuty-ruby).
20
+
21
+ ## 2.0.0 - 2020-07-04
22
+
23
+ - Change `Service::Get` to `Service::Render`.
24
+ - Change `Snippet.get` to `Snippet.render`.
25
+ - Change optional second argument of `Snippet.render` from `params` hash to options hash with `params` key.
26
+ - Change `Data::Snippet` to `Data::Render`.
27
+ - Remove `id` attribute from `Data::Render`.
28
+ - Change API endpoint to `snippets/:id/render`.
29
+
30
+ ## 1.1.2 - 2020-07-04
31
+
32
+ - Change the Faraday gem from `~> 0.1` to `~> 1.0`.
33
+
8
34
  ## 1.1.1 - 2020-03-15
9
35
 
10
36
  - Change snippet parameters to JSON query string parameter (e.g., `params={"foo":"bar"}`) from serialized query string parameter (e.g.,`params[foo]=bar`).
data/Gemfile CHANGED
@@ -1,4 +1,6 @@
1
- source "https://rubygems.org"
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
2
4
 
3
5
  # Specify your gem's dependencies in jahuty.gemspec
4
6
  gemspec
data/README.md CHANGED
@@ -1,109 +1,177 @@
1
+ [![CircleCI](https://circleci.com/gh/jahuty/jahuty-ruby.svg?style=svg)](https://circleci.com/gh/jahuty/jahuty-ruby) [![codecov](https://codecov.io/gh/jahuty/jahuty-ruby/branch/master/graph/badge.svg?token=NLDCGGYB8S)](https://codecov.io/gh/jahuty/jahuty-ruby) [![Ruby Style Guide](https://img.shields.io/badge/code_style-rubocop-brightgreen.svg)](https://github.com/rubocop-hq/rubocop)
2
+
1
3
  # jahuty-ruby
2
- Welcome [Jahuty's](https://www.jahuty.com) Ruby SDK!
4
+
5
+ Welcome to the [Ruby SDK](https://docs.jahuty.com/sdks/ruby) for [Jahuty's API](https://docs.jahuty.com/api)!
3
6
 
4
7
  ## Installation
5
8
 
6
- This library requires [Ruby 2.3+](https://www.ruby-lang.org/en/downloads/releases/).
9
+ This library requires [Ruby 2.6+](https://www.ruby-lang.org/en/downloads/releases/).
7
10
 
8
11
  It is multi-platform, and we strive to make it run equally well on Windows, Linux, and OSX.
9
12
 
10
- Add this line to your application's `Gemfile`, where `x` is the latest major version number:
13
+ To install, add this line to your application's `Gemfile` and run `bundle install`:
11
14
 
12
15
  ```ruby
13
- gem "jahuty", "~> x"
14
- ```
15
-
16
- And then execute:
17
-
18
- ```bash
19
- $ bundle
16
+ gem 'jahuty', '~> 3.1'
20
17
  ```
21
18
 
22
19
  ## Usage
23
20
 
24
- Before use, the library needs to be configured with your [API key](https://www.jahuty.com/docs/api#authentication) (ideally, once during startup):
21
+ Instantiate the client with your [API key](https://docs.jahuty.com/api#authentication) and use `snippets.render` to render your snippet:
25
22
 
26
23
  ```ruby
27
- require "jahuty"
24
+ jahuty = Jahuty::Client.new(api_key: 'YOUR_API_KEY')
28
25
 
29
- Jahuty.key = "YOUR_API_KEY"
26
+ puts jahuty.snippets.render YOUR_SNIPPET_ID
30
27
  ```
31
28
 
32
- With the API key set, you can use the `get()` method to retrieve a snippet:
33
-
34
- Then, use the `.get` method to fetch a snippet:
29
+ You can also access the render's content with `to_s` or `content`:
35
30
 
36
31
  ```ruby
37
- require "jahuty"
32
+ jahuty = Jahuty::Client.new(api_key: 'YOUR_API_KEY')
33
+
34
+ render = jahuty.snippets.render YOUR_SNIPPET_ID
38
35
 
39
- # retrieve the snippet
40
- snippet = Snippet.get YOUR_SNIPPET_ID
36
+ a = render.to_s
41
37
 
42
- # convert it to a string
43
- snippet.to_s
38
+ b = render.content
44
39
 
45
- # or, access its attributes
46
- snippet.id
47
- snippet.content
40
+ a == b # returns true
48
41
  ```
49
42
 
50
43
  In an HTML view:
51
44
 
52
45
  ```html+erb
53
- <%-
54
- require "jahuty"
55
-
56
- Jahuty.key = "YOUR_API_KEY"
57
- %>
46
+ <%- jahuty = Jahuty::Client.new(api_key: 'YOUR_API_KEY') -%>
58
47
  <!doctype html>
59
48
  <html>
60
49
  <head>
61
50
  <title>Awesome example</title>
62
51
  </head>
63
52
  <body>
64
- <%= Snippet.get YOUR_SNIPPET_ID %>
53
+ <%== jahuty.snippets.render YOUR_SNIPPET_ID %>
65
54
  </body>
66
55
  ```
67
56
 
68
57
  ## Parameters
69
58
 
70
- You can [pass parameters](https://www.jahuty.com/docs/passing-a-parameter) into your snippet with an optional second argument:
59
+ You can [pass parameters](https://docs.jahuty.com/liquid/parameters) into your snippet using the `params` option:
71
60
 
72
61
  ```ruby
73
- require "jahuty"
74
-
75
- Snippet.get(YOUR_SNIPPET_ID, {
76
- foo: "bar",
77
- baz: ["qux", "quux"],
78
- corge: {
79
- grault: {
80
- garply: "waldo"
81
- }
82
- }
83
- });
62
+ jahuty = Jahuty::Client.new(api_key: 'YOUR_API_KEY')
63
+
64
+ jahuty.snippets.render YOUR_SNIPPET_ID, params: { foo: 'bar' }
84
65
  ```
85
66
 
86
- The parameters above would be equivalent to [assigning the variables](https://www.jahuty.com/docs/assigning-a-variable) below in your snippet:
67
+ The parameters above would be equivalent to [assigning the variable](https://docs.jahuty.com/liquid/variables) below in your snippet:
87
68
 
88
69
  ```html
89
70
  {% assign foo = "bar" %}
90
- {% assign baz = ["qux", "quux"] %}
91
- {% assign corge.grault.garply = "waldo" %}
92
71
  ```
93
72
 
94
- ## Errors
73
+ ## Caching
74
+
75
+ You can use caching to control how frequently this library requests the latest content from Jahuty's API.
76
+
77
+ * When content is in _development_ (i.e., frequently changing and low traffic), you can use the default in-memory store to view content changes instantaneously with slower response times.
78
+ * When content is in _production_ (i.e., more stable and high traffic), you can use persistent caching to update content less frequently and improve your application's response time.
79
+
80
+ ### Caching in memory (default)
81
+
82
+ By default, this library uses an in-memory cache to avoid requesting the same render more than once during the same request lifecycle. For example:
83
+
84
+ ```ruby
85
+ jahuty = Jahuty::Client.new(api_key: 'YOUR_API_KEY')
86
+
87
+ # This call will send a synchronous API request; cache the result in memory;
88
+ # and, return the result to the caller.
89
+ render1 = jahuty.snippets.render YOUR_SNIPPET_ID
90
+
91
+ # This call skips sending an API request and uses the cached value instead.
92
+ render2 = jahuty.snippets.render YOUR_SNIPPET_ID
93
+ ```
94
+
95
+ The in-memory cache only persists for the duration of the original request, however. At the end of the request's lifecycle, the cache will be discarded. To store renders across requests, you need a persistent cache.
96
+
97
+ ### Caching persistently
98
+
99
+ A persistent cache allows renders to be cached across multiple requests. This reduces the number of synchronous network requests to Jahuty's API and improves your application's average response time.
100
+
101
+ To configure Jahuty to use your persistent cache, pass a cache implementation to the client via the `cache` configuration option:
102
+
103
+ ```ruby
104
+ jahuty = new Jahuty::Client.new(
105
+ api_key: 'YOUR_API_KEY',
106
+ cache: cache
107
+ )
108
+ ```
95
109
 
96
- If you don't set your API key before calling `Snippet.get`, a `StandardError` will be raised. If an error occurs with [Jahuty's API](https://www.jahuty.com/docs/api), a `NotOk` exception will be raised:
110
+ The persistent cache implementation you choose and configure is up to you. There are many libraries available, and most frameworks provide their own. At this time, we support any object which responds to `get(key)`/`set(key, value, expires_in:)` or `read(key)`/`write(key, value, expires_in:)` including [ActiveSupport::Cache::Store](https://api.rubyonrails.org/classes/ActiveSupport/Cache/Store.html#method-i-fetch).
111
+
112
+ ### Expiring
113
+
114
+ There are three methods for configuring this library's `:expires_in`, the amount of time between when a render is stored and when it's considered stale. From lowest-to-highest precedence, the methods are:
115
+
116
+ 1. configuring your caching implementation,
117
+ 1. configuring this library's default `:expires_in`, and
118
+ 1. configuring a render's `:expires_in`.
119
+
120
+ #### Configuring your caching implementation
121
+
122
+ You can usually configure your caching implementation with a default `:expires_in`. If no other `:expires_in` is configured, this library will defer to the caching implementation's default `:expires_in`.
123
+
124
+ #### Configuring this library's default `:expires_in`
125
+
126
+ You can configure a default `:expires_in` for all of this library's renders by passing an integer number of seconds via the client's `:expires_in` configuration option:
127
+
128
+ ```ruby
129
+ jahuty = Jahuty::Client.new(
130
+ api_key: 'YOUR_API_KEY',
131
+ cache: cache,
132
+ expires_in: 60 # <- Cache all renders for sixty seconds
133
+ )
134
+ ```
135
+
136
+ If this library's default `:expires_in` is set, it will take precedence over the default `:expires_is` of the caching implementation.
137
+
138
+ #### Configuring a render's `:expires_in`
139
+
140
+ You can configure a single render's `:expires_in` by passing an integer number of seconds via its `:expires_in` configuration option:
97
141
 
98
142
  ```ruby
99
- require "jahuty"
143
+ # Default to the caching implementation's :expires_in for all renders.
144
+ jahuty = Jahuty::Client.new(api_key: 'YOUR_API_KEY', cache: cache)
100
145
 
146
+ # Except, cache this render for 60 seconds.
147
+ render = jahuty.snippets.render(1, expires_in: 60)
148
+ ```
149
+
150
+ If a render's `:expires_in` is set, it will take precedence over the library's default `:expires_in` and the caching implementation's `:expires_in`.
151
+
152
+ ### Disabling caching
153
+
154
+ You can disable caching, even the default in-memory caching, by passing an `:expires_in` of zero (`0`) or a negative integer (e.g., `-1`) via any of the methods described above. For example:
155
+
156
+ ```ruby
157
+ # Disable all caching.
158
+ jahuty1 = Jahuty::Client.new(api_key: 'YOUR_API_KEY', expires_in: 0)
159
+
160
+ # Disable caching for this render.
161
+ jahuty2 = Jahuty::Client.new(api_key: 'YOUR_API_KEY', expires_in: 60)
162
+ jahuty2.snippets.render(1, expires_in: 0)
163
+ ```
164
+
165
+ ## Errors
166
+
167
+ If an error occurs with [Jahuty's API](https://docs.jahuty.com/api#errors), a `Jahuty::Exception::Error` will be raised:
168
+
169
+ ```ruby
101
170
  begin
102
- Snippet.get YOUR_SNIPPET_ID
103
- rescue StandardError => e
104
- # hmm, did you set the API key first?
105
- rescue Jahuty::Exception::NotOk => e
106
- # hmm, the API returned something besides 2xx status code
171
+ jahuty = Jahuty::Client.new(api_key: 'YOUR_API_KEY')
172
+ jahuty.snippets.render YOUR_SNIPPET_ID
173
+ rescue Jahuty::Exception::Error => e
174
+ # The API returned an error. See the error's problem for details.
107
175
  puts e.problem.type # a URL to more information
108
176
  puts e.problem.status # the status code
109
177
  puts e.problem.detail # a description of the error
data/Rakefile CHANGED
@@ -1,6 +1,14 @@
1
- require "bundler/gem_tasks"
2
- require "rspec/core/rake_task"
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+ require 'rubocop/rake_task'
3
6
 
4
7
  RSpec::Core::RakeTask.new(:spec)
5
8
 
6
- task :default => :spec
9
+ task default: :spec
10
+
11
+ RuboCop::RakeTask.new do |task|
12
+ task.requires << 'rubocop-performance'
13
+ task.requires << 'rubocop-rspec'
14
+ end
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
- require "bundler/setup"
4
- require "jahuty/snippets"
4
+ require 'bundler/setup'
5
+ require 'jahuty/snippets'
5
6
 
6
7
  # You can add fixtures and/or initialization code here to make experimenting
7
8
  # with your gem easier. You can also use a different console, if you like.
@@ -10,5 +11,5 @@ require "jahuty/snippets"
10
11
  # require "pry"
11
12
  # Pry.start
12
13
 
13
- require "irb"
14
+ require 'irb'
14
15
  IRB.start(__FILE__)
data/bin/setup CHANGED
@@ -1,4 +1,6 @@
1
1
  #!/usr/bin/env bash
2
+ # frozen_string_literal: true
3
+
2
4
  set -euo pipefail
3
5
  IFS=$'\n\t'
4
6
  set -vx
@@ -1,30 +1,47 @@
1
+ # frozen_string_literal: true
1
2
 
2
- lib = File.expand_path("../lib", __FILE__)
3
+ lib = File.expand_path('lib', __dir__)
3
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
5
 
5
- require "jahuty"
6
+ require 'jahuty/version'
6
7
 
7
8
  Gem::Specification.new do |spec|
8
- spec.name = "jahuty"
9
+ spec.name = 'jahuty'
9
10
  spec.version = Jahuty::VERSION
10
- spec.authors = ["Jack Clayton"]
11
- spec.email = ["jack@jahuty.com"]
11
+ spec.authors = ['Jack Clayton']
12
+ spec.email = ['jack@jahuty.com']
12
13
 
13
- spec.summary = %q{Jahuty's Ruby SDK.}
14
- spec.description = %q{Turn any page into a content-managed page.}
15
- spec.homepage = "https://github.com/jahuty/jahuty-ruby"
16
- spec.license = "MIT"
14
+ spec.summary = 'Jahuty\'s Ruby SDK.'
15
+ spec.description = 'Turn any page into a content-managed page.'
16
+ spec.homepage = 'https://www.jahuty.com'
17
+ spec.license = 'MIT'
17
18
 
18
- spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
19
+ spec.metadata['allowed_push_host'] = 'https://rubygems.org'
20
+
21
+ spec.metadata['homepage_uri'] = spec.homepage
22
+ spec.metadata['source_code_uri'] = 'https://github.com/jahuty/jahuty-ruby'
23
+ spec.metadata['changelog_uri'] = 'https://github.com/jahuty/jahuty-ruby/blob/master/CHANGELOG.md'
24
+
25
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
19
26
  `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
20
27
  end
21
- spec.bindir = "exe"
28
+ spec.bindir = 'exe'
22
29
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
23
- spec.require_paths = ["lib"]
30
+ spec.require_paths = ['lib']
31
+
32
+ spec.required_ruby_version = '~> 2.6'
24
33
 
25
- spec.add_dependency 'faraday', '~> 0.1'
34
+ spec.add_dependency 'faraday', '~> 1.0'
35
+ spec.add_dependency 'mini_cache', '~> 1.1'
26
36
 
27
- spec.add_development_dependency "bundler", "~> 2.0"
28
- spec.add_development_dependency "rake", "~> 12.3"
29
- spec.add_development_dependency "rspec", "~> 3.0"
37
+ spec.add_development_dependency 'bundler', '~> 2.0'
38
+ spec.add_development_dependency 'rake', '~> 12.3'
39
+ spec.add_development_dependency 'rspec', '~> 3.0'
40
+ spec.add_development_dependency 'rspec_junit_formatter', '~>0.4'
41
+ spec.add_development_dependency 'rubocop', '~> 1.7'
42
+ spec.add_development_dependency 'rubocop-performance', '~> 1.9'
43
+ spec.add_development_dependency 'rubocop-rspec', '~> 2.1'
44
+ spec.add_development_dependency 'simplecov', '~>0.20'
45
+ spec.add_development_dependency 'simplecov-cobertura', '~> 1.4'
46
+ spec.add_development_dependency 'webmock', '~> 3.11'
30
47
  end
@@ -1,23 +1,30 @@
1
- require "jahuty/version"
1
+ # frozen_string_literal: true
2
2
 
3
- require "jahuty/snippet"
3
+ require 'jahuty/version'
4
4
 
5
- require "jahuty/data/problem"
6
- require "jahuty/data/snippet"
5
+ require 'jahuty/action/base'
6
+ require 'jahuty/action/show'
7
7
 
8
- require "jahuty/exception/not_ok"
8
+ require 'jahuty/api/client'
9
9
 
10
- require "jahuty/service/connect"
11
- require "jahuty/service/get"
10
+ require 'jahuty/cache/facade'
11
+ require 'jahuty/cache/manager'
12
12
 
13
- module Jahuty
14
- @key
13
+ require 'jahuty/exception/error'
14
+
15
+ require 'jahuty/request/base'
16
+ require 'jahuty/request/factory'
17
+
18
+ require 'jahuty/resource/problem'
19
+ require 'jahuty/resource/render'
20
+ require 'jahuty/resource/factory'
15
21
 
16
- class << self
17
- attr_accessor :key
22
+ require 'jahuty/service/base'
23
+ require 'jahuty/service/snippet'
24
+ require 'jahuty/service/factory'
18
25
 
19
- def key?
20
- !(@key.nil? || @key.empty?)
21
- end
22
- end
26
+ require 'jahuty/client'
27
+
28
+ module Jahuty
29
+ BASE_URI = 'https://api.jahuty.com'
23
30
  end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jahuty
4
+ module Action
5
+ # Provides common logic for service actions.
6
+ class Base
7
+ attr_accessor :resource, :params
8
+
9
+ def initialize(resource:, params: {})
10
+ @resource = resource
11
+ @params = params
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jahuty
4
+ module Action
5
+ # Displays a specific resource.
6
+ class Show < Base
7
+ attr_accessor :id
8
+
9
+ def initialize(resource:, id:, params: {})
10
+ @id = id
11
+
12
+ super(resource: resource, params: params)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'faraday'
4
+
5
+ module Jahuty
6
+ module Api
7
+ # Handles HTTP requests and responses.
8
+ class Client
9
+ HEADERS = {
10
+ 'Accept' => 'application/json;q=0.9,*/*;q=0.8',
11
+ 'Accept-Encoding' => 'gzip, deflate',
12
+ 'Content-Type' => 'application/json; charset=utf-8',
13
+ 'User-Agent' => "Jahuty Ruby SDK v#{::Jahuty::VERSION}"
14
+ }.freeze
15
+
16
+ def initialize(api_key:)
17
+ @api_key = api_key
18
+ end
19
+
20
+ def send(request)
21
+ @client ||= Faraday.new(url: ::Jahuty::BASE_URI, headers: headers)
22
+
23
+ @client.send(
24
+ request.method.to_sym,
25
+ request.path,
26
+ { params: request.params }
27
+ )
28
+ end
29
+
30
+ private
31
+
32
+ def headers
33
+ { 'Authorization' => "Bearer #{@api_key}" }.merge(HEADERS)
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jahuty
4
+ module Cache
5
+ # Abstracts away the differences in cache implementation methods and
6
+ # argument lists.
7
+ class Facade
8
+ def initialize(cache)
9
+ @cache = cache
10
+ end
11
+
12
+ def delete(key)
13
+ if @cache.respond_to? :delete
14
+ @cache.delete key
15
+ elsif @cache.respond_to? :unset
16
+ @cache.unset key
17
+ else
18
+ raise NoMethodError, 'Cache must respond to :delete or :unset'
19
+ end
20
+ end
21
+
22
+ def read(key)
23
+ if @cache.respond_to? :read
24
+ @cache.read key
25
+ elsif @cache.respond_to? :get
26
+ @cache.get key
27
+ else
28
+ raise NoMethodError, 'Cache must respond to :read or :get'
29
+ end
30
+ end
31
+
32
+ def write(key, value, expires_in: nil)
33
+ if Object.const_defined?('::ActiveSupport::Cache::Store') &&
34
+ @cache.is_a?(::ActiveSupport::Cache::Store)
35
+ @cache.write key, value, expires_in: expires_in, race_condition_ttl: 10
36
+ elsif @cache.respond_to? :write
37
+ @cache.write key, value, expires_in: expires_in
38
+ elsif @cache.respond_to? :set
39
+ @cache.set key, value, expires_in: expires_in
40
+ else
41
+ raise NoMethodError, 'Cache must respond to :write or :set'
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jahuty
4
+ module Cache
5
+ # Fetches the requested action from the cache or API.
6
+ class Manager
7
+ def initialize(cache:, client:, expires_in: nil)
8
+ @client = client
9
+ @cache = Facade.new(cache)
10
+ @expires_in = expires_in
11
+ end
12
+
13
+ def fetch(action, expires_in: nil)
14
+ key = key action
15
+ value = @cache.read key
16
+
17
+ @cache.delete key unless value.nil? || cacheable(expires_in)
18
+
19
+ if value.nil?
20
+ value = @client.request action
21
+ @cache.write key, value, expires_in: expires_in || @expires_in if cacheable(expires_in)
22
+ end
23
+
24
+ value
25
+ end
26
+
27
+ private
28
+
29
+ def cacheable(expires_in)
30
+ expires_in.nil? || expires_in.positive?
31
+ end
32
+
33
+ def key(action)
34
+ # We only build cache keys for show-render actions at this time.
35
+ unless action.is_a?(::Jahuty::Action::Show) && action.resource == 'render'
36
+ raise ArgumentError, 'Action must be show render'
37
+ end
38
+
39
+ fingerprint = Digest::MD5.new
40
+ fingerprint << "snippets/#{action.id}/render/"
41
+ fingerprint << action.params.to_json
42
+
43
+ "jahuty_#{fingerprint.hexdigest}"
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'mini_cache'
4
+
5
+ module Jahuty
6
+ # Executes requests against Jahuty's API and returns resources.
7
+ class Client
8
+ def initialize(api_key:, cache: nil, expires_in: nil)
9
+ @api_key = api_key
10
+ @cache = cache || ::MiniCache::Store.new
11
+ @expires_in = expires_in
12
+ @services = Service::Factory.new(client: self)
13
+ end
14
+
15
+ # Allows services to be accessed as properties (e.g., jahuty.snippets).
16
+ def method_missing(name, *args, &block)
17
+ if args.empty? && @services.respond_to?(name)
18
+ @services.send(name)
19
+ else
20
+ super
21
+ end
22
+ end
23
+
24
+ def fetch(action, expires_in: nil)
25
+ @manager ||= Cache::Manager.new(
26
+ client: self,
27
+ cache: @cache,
28
+ expires_in: expires_in || @expires_in
29
+ )
30
+
31
+ @manager.fetch(action)
32
+ end
33
+
34
+ def request(action)
35
+ @requests ||= Request::Factory.new
36
+
37
+ request = @requests.call(action)
38
+
39
+ @client ||= Api::Client.new(api_key: @api_key)
40
+
41
+ response = @client.send(request)
42
+
43
+ @resources ||= Resource::Factory.new
44
+
45
+ resource = @resources.call(action, response)
46
+
47
+ raise Exception::Error.new(resource), 'API responded with a problem' if resource.is_a?(Resource::Problem)
48
+
49
+ resource
50
+ end
51
+
52
+ def respond_to_missing?(name, include_private = false)
53
+ @services.respond_to?(name, include_private) || super
54
+ end
55
+ end
56
+ end
@@ -1,10 +1,15 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Jahuty
2
4
  module Exception
3
- class NotOk < ::StandardError
5
+ # Thrown when a client- or server-error occurs.
6
+ class Error < ::StandardError
4
7
  attr_reader :problem
5
8
 
6
9
  def initialize(problem)
7
10
  @problem = problem
11
+
12
+ super
8
13
  end
9
14
 
10
15
  def message
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jahuty
4
+ module Request
5
+ # Provides common logic for all requests.
6
+ class Base
7
+ attr_accessor :method, :path, :params
8
+
9
+ def initialize(method:, path:, params: {})
10
+ @method = method
11
+ @path = path
12
+ @params = params
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jahuty
4
+ module Request
5
+ # Instantiates a request from an action. Currently, this is elementary. As
6
+ # we add actions, it will become more complicated.
7
+ class Factory
8
+ def call(action)
9
+ Base.new(
10
+ method: 'get',
11
+ path: "snippets/#{action.id}/render",
12
+ params: action.params
13
+ )
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module Jahuty
6
+ module Resource
7
+ # Negotiates the resource to return given the requested action and the
8
+ # server's response.
9
+ class Factory
10
+ CLASSES = {
11
+ problem: Problem.name,
12
+ render: Render.name
13
+ }.freeze
14
+
15
+ def call(action, response)
16
+ if success? response
17
+ resource_name = action.resource
18
+ elsif problem? response
19
+ resource_name = 'problem'
20
+ else
21
+ raise ArgumentError, 'Unexpected response'
22
+ end
23
+
24
+ resource_class = class_name(resource_name.to_sym)
25
+
26
+ payload = parse(response)
27
+
28
+ Object.const_get(resource_class).send(:new, **payload)
29
+ end
30
+
31
+ private
32
+
33
+ def class_name(resource_name)
34
+ CLASSES[resource_name.to_sym]
35
+ end
36
+
37
+ def problem?(response)
38
+ response.headers['Content-Type'] == 'application/problem+json'
39
+ end
40
+
41
+ def parse(response)
42
+ JSON.parse(response.body, symbolize_names: true)
43
+ end
44
+
45
+ def success?(response)
46
+ response.status.between?(200, 299)
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jahuty
4
+ module Resource
5
+ # An application/problem+json response. The API should respond with a
6
+ # problem whenever a client- or server-error occurs.
7
+ class Problem
8
+ attr_accessor :status, :type, :detail
9
+
10
+ def initialize(status:, type:, detail:)
11
+ @status = status
12
+ @type = type
13
+ @detail = detail
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jahuty
4
+ module Resource
5
+ # A snippet's rendered content.
6
+ class Render
7
+ attr_accessor :content
8
+
9
+ def initialize(content:)
10
+ @content = content
11
+ end
12
+
13
+ def to_s
14
+ @content
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jahuty
4
+ module Service
5
+ # Provides common logic to services.
6
+ class Base
7
+ def initialize(client:)
8
+ @client = client
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jahuty
4
+ module Service
5
+ # Instantiates the requested service and memoizes it for subsequent
6
+ # requests.
7
+ class Factory
8
+ CLASSES = {
9
+ snippets: Snippet.name
10
+ }.freeze
11
+
12
+ def initialize(client:)
13
+ @client = client
14
+ @services = {}
15
+ end
16
+
17
+ def method_missing(name, *args, &block)
18
+ if args.empty? && class_name?(name)
19
+ unless @services.key?(name)
20
+ klass = class_name(name)
21
+ service = Object.const_get(klass).send(:new, client: @client)
22
+ @services[name] = service
23
+ end
24
+
25
+ @services[name]
26
+ else
27
+ super
28
+ end
29
+ end
30
+
31
+ def respond_to_missing?(name, include_private = false)
32
+ class_name(name) || super
33
+ end
34
+
35
+ private
36
+
37
+ def class_name(service_name)
38
+ CLASSES[service_name]
39
+ end
40
+
41
+ def class_name?(service_name)
42
+ CLASSES.key?(service_name)
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jahuty
4
+ module Service
5
+ # A service for interacting with snippets.
6
+ class Snippet < Base
7
+ def render(id, params: {}, expires_in: nil)
8
+ params = { params: params.to_json } unless params.empty?
9
+
10
+ action = ::Jahuty::Action::Show.new(id: id, resource: 'render', params: params)
11
+
12
+ @client.fetch action, expires_in: expires_in
13
+ end
14
+ end
15
+ end
16
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Jahuty
2
- VERSION = "1.1.1"
4
+ VERSION = '3.1.0'
3
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jahuty
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.1
4
+ version: 3.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jack Clayton
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-03-15 00:00:00.000000000 Z
11
+ date: 2021-01-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -16,14 +16,28 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0.1'
19
+ version: '1.0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '0.1'
26
+ version: '1.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: mini_cache
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.1'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.1'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: bundler
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -66,6 +80,104 @@ dependencies:
66
80
  - - "~>"
67
81
  - !ruby/object:Gem::Version
68
82
  version: '3.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec_junit_formatter
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0.4'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '0.4'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rubocop
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '1.7'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '1.7'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rubocop-performance
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '1.9'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '1.9'
125
+ - !ruby/object:Gem::Dependency
126
+ name: rubocop-rspec
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '2.1'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '2.1'
139
+ - !ruby/object:Gem::Dependency
140
+ name: simplecov
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: '0.20'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: '0.20'
153
+ - !ruby/object:Gem::Dependency
154
+ name: simplecov-cobertura
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: '1.4'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - "~>"
165
+ - !ruby/object:Gem::Version
166
+ version: '1.4'
167
+ - !ruby/object:Gem::Dependency
168
+ name: webmock
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - "~>"
172
+ - !ruby/object:Gem::Version
173
+ version: '3.11'
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - "~>"
179
+ - !ruby/object:Gem::Version
180
+ version: '3.11'
69
181
  description: Turn any page into a content-managed page.
70
182
  email:
71
183
  - jack@jahuty.com
@@ -73,8 +185,10 @@ executables: []
73
185
  extensions: []
74
186
  extra_rdoc_files: []
75
187
  files:
188
+ - ".circleci/config.yml"
76
189
  - ".gitignore"
77
190
  - ".rspec"
191
+ - ".rubocop.yml"
78
192
  - CHANGELOG.md
79
193
  - Gemfile
80
194
  - LICENSE
@@ -84,33 +198,46 @@ files:
84
198
  - bin/setup
85
199
  - jahuty.gemspec
86
200
  - lib/jahuty.rb
87
- - lib/jahuty/data/problem.rb
88
- - lib/jahuty/data/snippet.rb
89
- - lib/jahuty/exception/not_ok.rb
90
- - lib/jahuty/service/connect.rb
91
- - lib/jahuty/service/get.rb
92
- - lib/jahuty/snippet.rb
201
+ - lib/jahuty/action/base.rb
202
+ - lib/jahuty/action/show.rb
203
+ - lib/jahuty/api/client.rb
204
+ - lib/jahuty/cache/facade.rb
205
+ - lib/jahuty/cache/manager.rb
206
+ - lib/jahuty/client.rb
207
+ - lib/jahuty/exception/error.rb
208
+ - lib/jahuty/request/base.rb
209
+ - lib/jahuty/request/factory.rb
210
+ - lib/jahuty/resource/factory.rb
211
+ - lib/jahuty/resource/problem.rb
212
+ - lib/jahuty/resource/render.rb
213
+ - lib/jahuty/service/base.rb
214
+ - lib/jahuty/service/factory.rb
215
+ - lib/jahuty/service/snippet.rb
93
216
  - lib/jahuty/version.rb
94
- homepage: https://github.com/jahuty/jahuty-ruby
217
+ homepage: https://www.jahuty.com
95
218
  licenses:
96
219
  - MIT
97
- metadata: {}
220
+ metadata:
221
+ allowed_push_host: https://rubygems.org
222
+ homepage_uri: https://www.jahuty.com
223
+ source_code_uri: https://github.com/jahuty/jahuty-ruby
224
+ changelog_uri: https://github.com/jahuty/jahuty-ruby/blob/master/CHANGELOG.md
98
225
  post_install_message:
99
226
  rdoc_options: []
100
227
  require_paths:
101
228
  - lib
102
229
  required_ruby_version: !ruby/object:Gem::Requirement
103
230
  requirements:
104
- - - ">="
231
+ - - "~>"
105
232
  - !ruby/object:Gem::Version
106
- version: '0'
233
+ version: '2.6'
107
234
  required_rubygems_version: !ruby/object:Gem::Requirement
108
235
  requirements:
109
236
  - - ">="
110
237
  - !ruby/object:Gem::Version
111
238
  version: '0'
112
239
  requirements: []
113
- rubygems_version: 3.0.3
240
+ rubygems_version: 3.1.4
114
241
  signing_key:
115
242
  specification_version: 4
116
243
  summary: Jahuty's Ruby SDK.
@@ -1,21 +0,0 @@
1
- module Jahuty
2
- module Data
3
- class Problem
4
- attr_accessor :status, :type, :detail
5
-
6
- def initialize(status, type, detail)
7
- @status = status
8
- @type = type
9
- @detail = detail
10
- end
11
-
12
- def self.from(data)
13
- raise ArgumentError.new "Key :status does not exist" if !data.key?(:status)
14
- raise ArgumentError.new "Key :type does not exist" if !data.key?(:type)
15
- raise ArgumentError.new "Key :detail does not exist" if !data.key?(:detail)
16
-
17
- Problem.new(data[:status], data[:type], data[:detail])
18
- end
19
- end
20
- end
21
- end
@@ -1,23 +0,0 @@
1
- module Jahuty
2
- module Data
3
- class Snippet
4
- attr_accessor :id, :content
5
-
6
- def initialize(id, content)
7
- @id = id
8
- @content = content
9
- end
10
-
11
- def self.from(data)
12
- raise ArgumentError.new "Key :id does not exist" if !data.key?(:id)
13
- raise ArgumentError.new "Key :content does not exist" if !data.key?(:content)
14
-
15
- Snippet.new(data[:id], data[:content])
16
- end
17
-
18
- def to_s
19
- @content
20
- end
21
- end
22
- end
23
- end
@@ -1,23 +0,0 @@
1
- require "faraday"
2
-
3
- module Jahuty
4
- module Service
5
- class Connect
6
- URL = "https://www.jahuty.com/api"
7
-
8
- HEADERS = {
9
- "Accept": "application/json;q=0.9,*/*;q=0.8",
10
- "Accept-Encoding": "gzip, deflate",
11
- "Content-Type": "application/json; charset=utf-8",
12
- "User-Agent": "Jahuty Ruby client #{::Jahuty::VERSION}"
13
- }
14
-
15
- def call(key)
16
- Faraday.new(
17
- url: URL,
18
- headers: {"Authorization": "Bearer #{key}"}.merge(HEADERS)
19
- )
20
- end
21
- end
22
- end
23
- end
@@ -1,25 +0,0 @@
1
- require "json"
2
-
3
- module Jahuty
4
- class Service::Get
5
- @connection
6
-
7
- def initialize(connection)
8
- @connection = connection
9
- end
10
-
11
- def call(id, params = {})
12
- options = { params: params.to_json } if !params.empty?
13
-
14
- response = @connection.get("snippets/#{id}", options || {})
15
-
16
- payload = JSON.parse(response.body, symbolize_names: true)
17
-
18
- if response.status != 200
19
- raise Exception::NotOk.new(Data::Problem.from(payload))
20
- end
21
-
22
- return Data::Snippet.from(payload)
23
- end
24
- end
25
- end
@@ -1,15 +0,0 @@
1
- module Jahuty
2
- class Snippet
3
- @get
4
-
5
- class << self
6
- def get(id, params = {})
7
- raise "API key not set. Did you use Jahuty.key?" unless Jahuty.key?
8
-
9
- @get ||= Service::Get.new(Service::Connect.new.call(Jahuty.key))
10
-
11
- @get.call(id, params)
12
- end
13
- end
14
- end
15
- end