renderful 0.2.0 → 0.3.0

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