jahuty 3.0.0 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f16f42cd41d86fdcdbd27591e637d82c324828f5cf60b3388afd7f1d439f9bee
4
- data.tar.gz: 53ef3b027c56ac0a00a09ab8ba0a10a3794b0d47bab1975ceb64dfbce74a244c
3
+ metadata.gz: 88cfffd7646bfc5849b4f45076f7dba175a65b8a5a89734e00db137dc49ee6e4
4
+ data.tar.gz: 85d9a607c72e94f041adc4fe22da7309f6228f6c07b50b49a1ad3f6c768be67c
5
5
  SHA512:
6
- metadata.gz: 24affb2a47535cd18148a52a51ed5f67120032ff859f54995433c10f1348cc72db3d17017b58e3e90a473678c7639acecc14832fe6b6cea302342baa46bc8821
7
- data.tar.gz: f4429c45c905b4dd9336d1126d4256dc9bdbf0f0c548f52bc7c93a8dc7371882bafdb11b29e18644816b1378a1d4691c7c7c6796c5a1c9a7cd68a6b90ab445a6
6
+ metadata.gz: d0292d5ee64c8d715a9506ef3586c0bcacddacf9c5b97a384b6bc608b119152220f3eda45f6a4b35792a7aab0bfa6895db0ed747b842ab4d45d561d0bdd7a271
7
+ data.tar.gz: dba24f33415884a663f959c9a8ebf7b443773b88ade368d46a2c7026c8cc1ead6b60c481b4d5fb9f4e956ba6ef2d41dbdf12b20fb9a333a4e75544cdd9c00759
@@ -10,3 +10,6 @@ Metrics/BlockLength:
10
10
  - 'Rakefile'
11
11
  - '**/*.rake'
12
12
  - 'spec/**/*.rb'
13
+ Metrics/ModuleLength:
14
+ Exclude:
15
+ - 'spec/**/*.rb'
@@ -5,6 +5,11 @@ 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
+
8
13
  ## 3.0.0 - 2020-12-30
9
14
 
10
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.
data/README.md CHANGED
@@ -10,20 +10,20 @@ This library requires [Ruby 2.6+](https://www.ruby-lang.org/en/downloads/release
10
10
 
11
11
  It is multi-platform, and we strive to make it run equally well on Windows, Linux, and OSX.
12
12
 
13
- Add this line to your application's `Gemfile`:
13
+ To install, add this line to your application's `Gemfile` and run `bundle install`:
14
14
 
15
15
  ```ruby
16
- gem 'jahuty', '~> 3.0'
16
+ gem 'jahuty', '~> 3.1'
17
17
  ```
18
18
 
19
19
  ## Usage
20
20
 
21
- Instantiate the client with your [API key](https://docs.jahuty.com/api#authentication) and use `snippets.render()` to render your snippet:
21
+ Instantiate the client with your [API key](https://docs.jahuty.com/api#authentication) and use `snippets.render` to render your snippet:
22
22
 
23
23
  ```ruby
24
24
  jahuty = Jahuty::Client.new(api_key: 'YOUR_API_KEY')
25
25
 
26
- puts jahuty.snippets.render(YOUR_SNIPPET_ID)
26
+ puts jahuty.snippets.render YOUR_SNIPPET_ID
27
27
  ```
28
28
 
29
29
  You can also access the render's content with `to_s` or `content`:
@@ -31,7 +31,7 @@ You can also access the render's content with `to_s` or `content`:
31
31
  ```ruby
32
32
  jahuty = Jahuty::Client.new(api_key: 'YOUR_API_KEY')
33
33
 
34
- render = jahuty.snippets.render(YOUR_SNIPPET_ID)
34
+ render = jahuty.snippets.render YOUR_SNIPPET_ID
35
35
 
36
36
  a = render.to_s
37
37
 
@@ -43,16 +43,14 @@ a == b # returns true
43
43
  In an HTML view:
44
44
 
45
45
  ```html+erb
46
- <%-
47
- jahuty = Jahuty::Client.new(api_key: 'YOUR_API_KEY')
48
- %>
46
+ <%- jahuty = Jahuty::Client.new(api_key: 'YOUR_API_KEY') -%>
49
47
  <!doctype html>
50
48
  <html>
51
49
  <head>
52
50
  <title>Awesome example</title>
53
51
  </head>
54
52
  <body>
55
- <%= jahuty.snippets.render YOUR_SNIPPET_ID %>
53
+ <%== jahuty.snippets.render YOUR_SNIPPET_ID %>
56
54
  </body>
57
55
  ```
58
56
 
@@ -63,7 +61,7 @@ You can [pass parameters](https://docs.jahuty.com/liquid/parameters) into your s
63
61
  ```ruby
64
62
  jahuty = Jahuty::Client.new(api_key: 'YOUR_API_KEY')
65
63
 
66
- jahuty.snippets.render(YOUR_SNIPPET_ID, params: { foo: 'bar' });
64
+ jahuty.snippets.render YOUR_SNIPPET_ID, params: { foo: 'bar' }
67
65
  ```
68
66
 
69
67
  The parameters above would be equivalent to [assigning the variable](https://docs.jahuty.com/liquid/variables) below in your snippet:
@@ -72,13 +70,103 @@ The parameters above would be equivalent to [assigning the variable](https://doc
72
70
  {% assign foo = "bar" %}
73
71
  ```
74
72
 
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
+ ```
109
+
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:
141
+
142
+ ```ruby
143
+ # Default to the caching implementation's :expires_in for all renders.
144
+ jahuty = Jahuty::Client.new(api_key: 'YOUR_API_KEY', cache: cache)
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
+
75
165
  ## Errors
76
166
 
77
167
  If an error occurs with [Jahuty's API](https://docs.jahuty.com/api#errors), a `Jahuty::Exception::Error` will be raised:
78
168
 
79
169
  ```ruby
80
- require 'jahuty'
81
-
82
170
  begin
83
171
  jahuty = Jahuty::Client.new(api_key: 'YOUR_API_KEY')
84
172
  jahuty.snippets.render YOUR_SNIPPET_ID
@@ -32,6 +32,7 @@ Gem::Specification.new do |spec|
32
32
  spec.required_ruby_version = '~> 2.6'
33
33
 
34
34
  spec.add_dependency 'faraday', '~> 1.0'
35
+ spec.add_dependency 'mini_cache', '~> 1.1'
35
36
 
36
37
  spec.add_development_dependency 'bundler', '~> 2.0'
37
38
  spec.add_development_dependency 'rake', '~> 12.3'
@@ -7,6 +7,9 @@ require 'jahuty/action/show'
7
7
 
8
8
  require 'jahuty/api/client'
9
9
 
10
+ require 'jahuty/cache/facade'
11
+ require 'jahuty/cache/manager'
12
+
10
13
  require 'jahuty/exception/error'
11
14
 
12
15
  require 'jahuty/request/base'
@@ -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
@@ -1,14 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'mini_cache'
4
+
3
5
  module Jahuty
4
6
  # Executes requests against Jahuty's API and returns resources.
5
7
  class Client
6
- def initialize(api_key:)
7
- @api_key = api_key
8
- @services = Service::Factory.new(client: self)
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)
9
13
  end
10
14
 
11
- # Allows services to appear as properties (e.g., jahuty.snippets).
15
+ # Allows services to be accessed as properties (e.g., jahuty.snippets).
12
16
  def method_missing(name, *args, &block)
13
17
  if args.empty? && @services.respond_to?(name)
14
18
  @services.send(name)
@@ -17,6 +21,16 @@ module Jahuty
17
21
  end
18
22
  end
19
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
+
20
34
  def request(action)
21
35
  @requests ||= Request::Factory.new
22
36
 
@@ -2,9 +2,7 @@
2
2
 
3
3
  module Jahuty
4
4
  module Resource
5
- # A snippet's rendered content. Remember, renders are unique by the
6
- # combination of id and params (i.e., the same id can produce different
7
- # renders with different params).
5
+ # A snippet's rendered content.
8
6
  class Render
9
7
  attr_accessor :content
10
8
 
@@ -4,16 +4,12 @@ module Jahuty
4
4
  module Service
5
5
  # A service for interacting with snippets.
6
6
  class Snippet < Base
7
- def render(id, options = {})
8
- params = { params: options[:params].to_json } unless options[:params].nil?
7
+ def render(id, params: {}, expires_in: nil)
8
+ params = { params: params.to_json } unless params.empty?
9
9
 
10
- action = ::Jahuty::Action::Show.new(
11
- id: id,
12
- resource: 'render',
13
- params: params || {}
14
- )
10
+ action = ::Jahuty::Action::Show.new(id: id, resource: 'render', params: params)
15
11
 
16
- @client.request(action)
12
+ @client.fetch action, expires_in: expires_in
17
13
  end
18
14
  end
19
15
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Jahuty
4
- VERSION = '3.0.0'
4
+ VERSION = '3.1.0'
5
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: 3.0.0
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-12-30 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
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
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
@@ -187,6 +201,8 @@ files:
187
201
  - lib/jahuty/action/base.rb
188
202
  - lib/jahuty/action/show.rb
189
203
  - lib/jahuty/api/client.rb
204
+ - lib/jahuty/cache/facade.rb
205
+ - lib/jahuty/cache/manager.rb
190
206
  - lib/jahuty/client.rb
191
207
  - lib/jahuty/exception/error.rb
192
208
  - lib/jahuty/request/base.rb