Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for multiple providers #16

Merged
merged 9 commits into from
Apr 1, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
- renderful-v1-
- run:
name: Bundle Install
command: bundle check || bundle install
command: bundle exec appraisal install
- save_cache:
key: renderful-v1-{{ .Branch }}-{{ .Revision }}
paths:
Expand Down
158 changes: 40 additions & 118 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

[![CircleCI](https://circleci.com/gh/nebulab/renderful.svg?style=svg)](https://circleci.com/gh/nebulab/renderful)

Welcome! Renderful is a rendering engine for [Contentful](https://www.contentful.com) spaces. It
allows you to map your content types to Ruby objects that take care of rendering your content.
Welcome! Renderful is a rendering engine for headless CMSs. It allows you to map your content types
to Ruby objects that take care of rendering your content.

## Installation

Expand All @@ -28,16 +28,16 @@ $ gem install renderful
Once you have installed the gem, you can configure it like this:

```ruby
contentful = Contentful::Client.new(
space: 'CONTENTFUL_SPACE_ID',
access_token: 'CONTENTFUL_ACCESS_TOKEN',
)

renderful = Renderful.new(
contentful: contentful,
renderers: {
'jumbotron' => JumbotronRenderer,
}
RenderfulClient = Renderful::Client.new(
provider: Renderful::Provider::Contentful.new(
contentful: Contentful::Client.new(
space: 'YOUR_SPACE_ID',
access_key: 'YOUR_ACCESS_KEY',
),
),
components: {
'jumbotron' => JumbotronComponent,
},
)
```

Expand All @@ -46,100 +46,38 @@ renderful = Renderful.new(
Suppose you have the `jumbotron` content type in your Contentful space. This content type has the
`title` and `content` fields, both strings.

Let's create the `app/renderers/jumbotron_renderer.rb` file:
Let's create the `app/components/jumbotron_component.rb` file:

```ruby
class JumbotronRenderer < Renderful::Renderer
class JumbotronComponent < Renderful::Component
def render
<<~HTML
<div class="jumbotron">
<h1 class="display-4"><%= entry.title %></h1>
<p class="lead"><%= entry.content %></p>
<h1 class="display-4"><%= entry.fields[:title] %></h1>
<p class="lead"><%= entry.fields[:content] %></p>
</div>
HTML
end
end
```

You can now render this component by retrieving it from Contentful and rendering it with Renderful:

```ruby
entry = contentful.entry('jumbotron_entry_id')
renderful.render(entry)
```

### Rich text rendering

If you have rich-text fields, you can leverage Contentful's [rich_text_renderer](https://github.com/contentful/rich-text-renderer.rb)
along with a custom local variable:

```ruby
class TextBlockRenderer < Renderful::Renderer::Rails
def html_body
RichTextRenderer::Renderer.new.render(entry.body)
end

def locals
{ html_body: html_body }
end
end
```

Then, just reference the `html_body` variable as usual:

```erb
<%# app/views/renderful/_text_block.html.erb %>
<%= raw html_body %>
```

### Nested components

What if you want to have a `Grid` component that can contain references to other components? It's
actually quite simple! Simply create a _References_ field for your content, then recursively render
all of the content entries contained in that field:
You can now render this component like this:

```ruby
# app/components/grid.rb
class Grid < Renderful::Renderer
# This will define a `resolved_blocks` method that reads external references
# from the `blocks` fields and turns them into Contentful::Entry instances
resolve :blocks

def render
entries = blocks.map do |block|
# `client` can be used to access the Renderful::Client instance
<<~HTML
<div class="grid-entry">
#{client.render(block)}
</div>
HTML
end

<<~HTML
<div class="grid">#{entries}</div>
HTML
end
end
RenderfulClient.render('my_entry_id')
```

### Caching

You can easily cache the output of your renderers by passing a `cache` key when instantiating the
client. The value of this key should be an object that responds to the following methods:

- `#read(key)`
- `#write(key, value)`
- `#delete(key)`
- `#exist?(key)`

A Redis cache implementation is included out of the box. Here's an example:
You can easily cache the output of your components. A Redis cache implementation is included out of
the box. Here's an example:

```ruby
renderful = Renderful.new(
contentful: contentful,
cache: Renderful::Cache::Redis.new(Redis.new(url: 'redis://localhost:6379')),
renderers: {
'jumbotron' => JumbotronRenderer
components: {
'jumbotron' => JumbotronComponent
}
)
```
Expand All @@ -151,31 +89,31 @@ If you are using Rails and want to use the Rails cache store for Renderful, you
renderful = Renderful.new(
contentful: contentful,
cache: Rails.cache,
renderers: {
'jumbotron' => JumbotronRenderer
components: {
'jumbotron' => JumbotronComponent
}
)
```

#### Cache invalidation

The best way to invalidate the cache is through [Contentful webhooks](https://www.contentful.com/developers/docs/concepts/webhooks/).
The best way to invalidate the cache is through [webhooks](https://www.contentful.com/developers/docs/concepts/webhooks/).

Renderful ships with a framework-agnostic webhook processor you can use to automatically invalidate
the cache for all updated content:

```ruby
Renderful::CacheInvalidator.new(renderful).process_webhook(json_body)
RenderfulClient.invalidate_cache_from_webhook(json_body)
```

This is how you could use it in a Rails controller:

```ruby
class ContentfulWebhooksController < ApplicationController
class WebhooksController < ApplicationController
skip_before_action :verify_authenticity_token

def create
Renderful::CacheInvalidator.new(RenderfulClient).process_webhook(request.raw_post)
RenderfulClient.invalidate_cache_from_webhook(request.raw_post)
head :no_content
end
end
Expand All @@ -188,33 +126,33 @@ components is updated, you want the page to be re-rendered.

### Rails integration

If you are using Ruby on Rails and you want to use ERB instead of including HTML in your renderers,
you can inherit from the Rails renderer:
If you are using Ruby on Rails and you want to use ERB instead of including HTML in your components,
you can inherit from the Rails component:

```ruby
class JumbotronRenderer < Renderful::Renderer::Rails
class JumbotronComponent < Renderful::Component::Rails
end
```

Then, create an `app/views/renderful/_jumbotron.html.erb` partial:

```erb
<div class="jumbotron">
<h1 class="display-4"><%= entry.title %></h1>
<p class="lead"><%= entry.content %></p>
<h1 class="display-4"><%= entry.fields[:title] %></h1>
<p class="lead"><%= entry.fields[:content] %></p>
</div>
```

As you can see, you can access the Contentful entry via the `entry` local variable.
As you can see, you can access the content entry via the `entry` local variable.

#### Custom renderer

The Rails renderer uses `ActionController::Base.renderer` by default, but this prevents you from
Rails components use `ActionController::Base.renderer` by default, but this prevents you from
using your own helpers in components. If you want to use a different renderer instead, you can
override the `renderer` method:

```ruby
class JumbotronRenderer < Renderful::Renderer::Rails
class JumbotronComponent < Renderful::Component::Rails
def renderer
ApplicationController.renderer
end
Expand All @@ -226,7 +164,7 @@ end
If you want, you can also add your own locals:

```ruby
class JumbotronRenderer < Renderful::Renderer::Rails
class JumbotronComponent < Renderful::Component::Rails
def locals
italian_title = entry.title.gsub(/hello/, 'ciao')
{ italian_title: italian_title }
Expand All @@ -239,26 +177,10 @@ You would then access them like regular locals:
```erb
<div class="jumbotron">
<h1 class="display-4">
<%= entry.title %>
(<%= italian_title %>)
<%= entry.fields[:title] %>
(<%= italian_title %>)
</h1>
<p class="lead"><%= entry.content %></p>
</div>
```

#### Resolution in ERB views

If you need to render resolved fields (as in our `Grid` example), you can use `renderer` and
`client` to access the `Renderful::Renderer` and `Renderful::Client` objects:

```erb
<%# app/views/renderful/_grid.html.erb %>
<div class="grid">
<% renderer.blocks.each do |block| %>
<div class="grid-entry">
<%= client.render(block) %>
</div>
<% end %>
<p class="lead"><%= entry.fields[:content] %></p>
</div>
```

Expand Down
14 changes: 9 additions & 5 deletions lib/renderful.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@

require 'contentful'

require 'renderful/no_renderer_error'
require 'renderful/cache'
require 'renderful/error/base'
require 'renderful/error/no_component_error'
require 'renderful/cache/base'
require 'renderful/cache/redis'
require 'renderful/cache_invalidator'
require 'renderful/cache/null'
require 'renderful/content_entry'
require 'renderful/provider/base'
require 'renderful/provider/contentful'
require 'renderful/client'
require 'renderful/renderer'
require 'renderful/renderer/rails'
require 'renderful/component/base'
require 'renderful/component/rails'
require 'renderful/version'

module Renderful
Expand Down
21 changes: 0 additions & 21 deletions lib/renderful/cache.rb

This file was deleted.

27 changes: 27 additions & 0 deletions lib/renderful/cache/base.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# frozen_string_literal: true

module Renderful
module Cache
class Base
def exist?(_key)
raise NotImplementedError
end

def read(_key)
raise NotImplementedError
end

def write(_key, _value)
raise NotImplementedError
end

def delete(_key)
raise NotImplementedError
end

def fetch(_key)
raise NotImplementedError
end
end
end
end
27 changes: 27 additions & 0 deletions lib/renderful/cache/null.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# frozen_string_literal: true

module Renderful
module Cache
class Null < Base
def exist?(_key)
false
end

def read(_key)
nil
end

def write(key, value)
# noop
end

def delete(key)
# noop
end

def fetch(_key)
yield
end
end
end
end
Loading