renderful 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/renderful.rb CHANGED
@@ -1,14 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'contentful'
4
-
5
- require 'renderful/no_renderer_error'
6
- require 'renderful/cache'
3
+ require 'renderful/error/base'
4
+ require 'renderful/error/entry_not_found_error'
5
+ require 'renderful/error/no_component_error'
6
+ require 'renderful/cache/base'
7
7
  require 'renderful/cache/redis'
8
- require 'renderful/cache_invalidator'
8
+ require 'renderful/cache/null'
9
+ require 'renderful/content_entry'
10
+ require 'renderful/provider/base'
11
+ require 'renderful/provider/contentful'
12
+ require 'renderful/provider/prismic'
9
13
  require 'renderful/client'
10
- require 'renderful/renderer'
11
- require 'renderful/renderer/rails'
14
+ require 'renderful/component/base'
12
15
  require 'renderful/version'
13
16
 
14
17
  module Renderful
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Renderful
4
+ module Cache
5
+ class Base
6
+ def exist?(_key)
7
+ raise NotImplementedError
8
+ end
9
+
10
+ def read(_key)
11
+ raise NotImplementedError
12
+ end
13
+
14
+ def write(_key, _value)
15
+ raise NotImplementedError
16
+ end
17
+
18
+ def delete(*_keys)
19
+ raise NotImplementedError
20
+ end
21
+
22
+ def delete_matched(_pattern)
23
+ raise NotImplementedError
24
+ end
25
+
26
+ def fetch(_key)
27
+ raise NotImplementedError
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Renderful
4
+ module Cache
5
+ class Null < Base
6
+ def exist?(_key)
7
+ false
8
+ end
9
+
10
+ def read(_key)
11
+ nil
12
+ end
13
+
14
+ def write(key, value)
15
+ # noop
16
+ end
17
+
18
+ def delete(*keys)
19
+ # noop
20
+ end
21
+
22
+ def delete_matched(pattern)
23
+ # noop
24
+ end
25
+
26
+ def fetch(_key)
27
+ yield
28
+ end
29
+ end
30
+ end
31
+ end
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Renderful
4
- class Cache
5
- class Redis < Cache
4
+ module Cache
5
+ class Redis < Base
6
6
  attr_reader :redis
7
7
 
8
8
  def initialize(redis)
@@ -21,8 +21,21 @@ module Renderful
21
21
  redis.set(key, value)
22
22
  end
23
23
 
24
- def delete(key)
25
- redis.del(key)
24
+ def delete(*keys)
25
+ redis.del(*keys)
26
+ end
27
+
28
+ def delete_matched(pattern)
29
+ keys = redis.scan_each(match: pattern).to_a
30
+ delete(*keys)
31
+ end
32
+
33
+ def fetch(key)
34
+ return read(key) if exist?(key)
35
+
36
+ yield.tap do |value|
37
+ write(key, value)
38
+ end
26
39
  end
27
40
  end
28
41
  end
@@ -2,34 +2,45 @@
2
2
 
3
3
  module Renderful
4
4
  class Client
5
- attr_reader :contentful, :renderers, :cache
5
+ attr_reader :provider, :components, :cache
6
6
 
7
- def initialize(contentful:, renderers:, cache: nil)
8
- @contentful = contentful
9
- @renderers = renderers
7
+ def initialize(provider:, components:, cache: Cache::Null.new)
8
+ @provider = provider
9
+ @components = components
10
10
  @cache = cache
11
11
  end
12
12
 
13
- def render(entry)
14
- renderer = renderers[entry.content_type.id]
15
- fail(NoRendererError, entry) unless renderer
13
+ def render(entry_id, options = {})
14
+ cache.fetch(ContentEntry.build_cache_key(provider, id: entry_id)) do
15
+ content_entry = provider.find_entry(entry_id)
16
+ component = component_for_entry(content_entry)
16
17
 
17
- return cache.read(cache_key_for(entry)) if cache&.exist?(cache_key_for(entry))
18
-
19
- renderer.new(entry, client: self).render.tap do |output|
20
- cache&.write(cache_key_for(entry), output)
18
+ if component.respond_to?(:render_in)
19
+ component.render_in(options.fetch(:view_context))
20
+ else
21
+ component.render
22
+ end
21
23
  end
22
24
  end
23
25
 
24
- def cache_key_for(entry)
25
- if entry.respond_to?(:content_type)
26
- cache_key_for(
27
- content_type_id: entry.content_type.id,
28
- entry_id: entry.id,
29
- )
30
- else
31
- "contentful/#{entry.fetch(:content_type_id)}/#{entry.fetch(:entry_id)}"
26
+ def invalidate_cache_from_webhook(body)
27
+ result = provider.cache_keys_to_invalidate(body)
28
+
29
+ cache.delete(*result[:keys])
30
+
31
+ result[:patterns].each do |pattern|
32
+ cache.delete_matched(pattern)
32
33
  end
33
34
  end
35
+
36
+ private
37
+
38
+ def component_klass_for_entry(content_entry)
39
+ components[content_entry.content_type] || fail(Error::NoComponentError, content_entry)
40
+ end
41
+
42
+ def component_for_entry(content_entry)
43
+ component_klass_for_entry(content_entry).new(entry: content_entry, client: self)
44
+ end
34
45
  end
35
46
  end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Renderful
4
+ module Component
5
+ class Base
6
+ attr_reader :entry, :client
7
+
8
+ def initialize(entry:, client:)
9
+ @entry = entry
10
+ @client = client
11
+ end
12
+
13
+ def render
14
+ fail NotImplementedError
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Renderful
4
+ class ContentEntry
5
+ attr_reader :provider, :id, :content_type, :fields
6
+
7
+ class << self
8
+ def build_cache_key(provider, id: nil)
9
+ ['renderful', provider.cache_prefix, id || '*'].join('/')
10
+ end
11
+ end
12
+
13
+ def initialize(provider:, id:, content_type: nil, fields: {})
14
+ @provider = provider
15
+ @id = id
16
+ @content_type = content_type
17
+ @fields = fields
18
+ end
19
+
20
+ def cache_key
21
+ self.class.build_cache_key(provider, id: id)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Renderful
4
+ module Error
5
+ class Base < StandardError; end
6
+ end
7
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Renderful
4
+ module Error
5
+ class EntryNotFoundError < Base
6
+ attr_reader :entry_id
7
+
8
+ def initialize(entry_id, *args)
9
+ @entry_id = entry_id
10
+
11
+ super "Cannot find entry #{@entry_id}", *args
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Renderful
4
+ module Error
5
+ class NoComponentError < Base
6
+ attr_reader :entry
7
+
8
+ def initialize(entry, *args)
9
+ @entry = entry
10
+
11
+ super "Cannot find component for content type #{entry.content_type}", *args
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Renderful
4
+ module Provider
5
+ class Base
6
+ attr_reader :options
7
+
8
+ def initialize(options)
9
+ @options = options
10
+ end
11
+
12
+ def cache_prefix
13
+ fail NotImplementedError
14
+ end
15
+
16
+ def find_entry(_entry_id)
17
+ fail NotImplementedError
18
+ end
19
+
20
+ def cache_keys_to_invalidate(_webhook_body)
21
+ fail NotImplementedError
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Renderful
4
+ module Provider
5
+ class Contentful < Base
6
+ def initialize(options)
7
+ super
8
+
9
+ fail ArgumentError, 'contentful option is required!' unless contentful
10
+ end
11
+
12
+ def cache_prefix
13
+ :contentful
14
+ end
15
+
16
+ def find_entry(entry_id)
17
+ entry = contentful.entry(entry_id)
18
+ raise Error::EntryNotFoundError, entry_id unless entry
19
+
20
+ wrap_entry(entry)
21
+ end
22
+
23
+ def cache_keys_to_invalidate(webhook_body)
24
+ params = webhook_body.is_a?(String) ? JSON.parse(webhook_body) : webhook_body
25
+
26
+ keys_to_invalidate = [ContentEntry.build_cache_key(self, id: params['sys']['id'])]
27
+ keys_to_invalidate += contentful.entries(links_to_entry: params['sys']['id']).map do |entry|
28
+ ContentEntry.build_cache_key(self, id: entry.id)
29
+ end
30
+
31
+ {
32
+ keys: keys_to_invalidate,
33
+ patterns: [],
34
+ }
35
+ end
36
+
37
+ private
38
+
39
+ def wrap_entry(entry)
40
+ ContentEntry.new(
41
+ provider: self,
42
+ id: entry.id,
43
+ content_type: entry.content_type.id,
44
+ fields: entry.fields,
45
+ )
46
+ end
47
+
48
+ def entries_linking_to(entry_id); end
49
+
50
+ def contentful
51
+ options[:contentful]
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Renderful
4
+ module Provider
5
+ class Prismic < Base
6
+ def initialize(options)
7
+ super
8
+
9
+ fail ArgumentError, 'prismic option is required!' unless prismic
10
+ end
11
+
12
+ def cache_prefix
13
+ :prismic
14
+ end
15
+
16
+ def find_entry(entry_id)
17
+ entry = prismic.getByID(entry_id)
18
+ raise Error::EntryNotFoundError, entry_id unless entry
19
+
20
+ wrap_entry(entry)
21
+ end
22
+
23
+ def cache_keys_to_invalidate(_webhook_body)
24
+ {
25
+ keys: [],
26
+ patterns: ['renderful/prismic/*'],
27
+ }
28
+ end
29
+
30
+ private
31
+
32
+ def wrap_entry(entry)
33
+ ContentEntry.new(
34
+ provider: self,
35
+ id: entry.id,
36
+ content_type: entry.type,
37
+ fields: entry.fragments,
38
+ )
39
+ end
40
+
41
+ def prismic
42
+ options[:prismic]
43
+ end
44
+ end
45
+ end
46
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Renderful
4
- VERSION = "0.2.0"
4
+ VERSION = '0.3.0'
5
5
  end
data/renderful.gemspec CHANGED
@@ -27,16 +27,22 @@ Gem::Specification.new do |spec|
27
27
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
28
28
  spec.require_paths = ['lib']
29
29
 
30
- spec.add_dependency 'contentful', '~> 2.11'
31
- spec.add_dependency 'rails', ['>= 5.0.0', '< 7']
32
-
33
30
  spec.add_development_dependency 'appraisal', '~> 2.2'
34
31
  spec.add_development_dependency 'bundler', '~> 2.1'
32
+ spec.add_development_dependency 'capybara', '~> 3.32'
35
33
  spec.add_development_dependency 'combustion', '~> 1.1'
34
+ spec.add_development_dependency 'contentful', '~> 2.11'
35
+ spec.add_development_dependency 'gem-release', '~> 2.1'
36
+ spec.add_development_dependency 'github_changelog_generator', '~> 1.15'
37
+ spec.add_development_dependency 'prismic.io', '~> 1.7'
38
+ spec.add_development_dependency 'rails', ['>= 5.0.0', '< 7']
36
39
  spec.add_development_dependency 'rake', '~> 10.0'
37
40
  spec.add_development_dependency 'redis', '~> 4.1'
38
41
  spec.add_development_dependency 'rspec-rails', '~> 4.0.0.beta4'
39
42
  spec.add_development_dependency 'rspec_junit_formatter', '~> 0.4.1'
40
43
  spec.add_development_dependency 'rubocop', '~> 0.79.0'
41
44
  spec.add_development_dependency 'rubocop-rspec', '~> 1.37'
45
+ spec.add_development_dependency 'vcr', '~> 5.1'
46
+ spec.add_development_dependency 'view_component', '~> 2.2'
47
+ spec.add_development_dependency 'webmock', '~> 3.8'
42
48
  end