wcc-contentful 1.4.0 → 1.5.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +56 -4
- data/lib/tasks/rewrite_fixtures.rake +61 -0
- data/lib/wcc/contentful/action_view_rich_text_renderer.rb +12 -0
- data/lib/wcc/contentful/configuration.rb +13 -0
- data/lib/wcc/contentful/model_api.rb +1 -1
- data/lib/wcc/contentful/model_builder.rb +1 -1
- data/lib/wcc/contentful/rich_text/node.rb +30 -6
- data/lib/wcc/contentful/rich_text.rb +97 -13
- data/lib/wcc/contentful/rich_text_renderer.rb +300 -0
- data/lib/wcc/contentful/services.rb +37 -1
- data/lib/wcc/contentful/simple_client/response.rb +1 -1
- data/lib/wcc/contentful/version.rb +1 -1
- data/lib/wcc/contentful.rb +2 -0
- metadata +46 -20
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 81257133691833969ac9f77447b91d2698aa0738964b56f80e8533dd403e90d1
|
4
|
+
data.tar.gz: 424597dcc95a76513d70226d82aafbea7baf614dcc7c5ee55c405e2dc75d4337
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 23e37d1e5e4271c0f0a13f8e0aa495473b55164b855b58df1638de5fc88f50e00499a4726908f1bb59856075833ab275fa3a893233fc484ff6c839fd42f023b0
|
7
|
+
data.tar.gz: 139b207faa2d1f6a3ae44ddff0421d4572f7b93aab04c2fc63bd6136e05faa72886d639ac023c0009af0934a74d8c70b79d7ce23876903706dd9702c04fb5f8a
|
data/README.md
CHANGED
@@ -15,6 +15,7 @@ Table of Contents:
|
|
15
15
|
3. [Configuration](#configure)
|
16
16
|
4. [Usage](#usage)
|
17
17
|
1. [Model API](#wcccontentfulmodel-api)
|
18
|
+
* [Rich Text Support](#rich-text-support)
|
18
19
|
2. [Store API](#store-api)
|
19
20
|
3. [Direct CDN client](#direct-cdn-api-simpleclient)
|
20
21
|
4. [Accessing the APIs](#accessing-the-apis-within-application-code)
|
@@ -146,7 +147,7 @@ WCC::Contentful.init!
|
|
146
147
|
```
|
147
148
|
|
148
149
|
All configuration options can be found [in the rubydoc under
|
149
|
-
WCC::Contentful::Configuration](https://watermarkchurch.github.io/wcc-contentful/latest/wcc-contentful/WCC/Contentful/Configuration)
|
150
|
+
WCC::Contentful::Configuration](https://watermarkchurch.github.io/wcc-contentful/latest/wcc-contentful/WCC/Contentful/Configuration)
|
150
151
|
|
151
152
|
## Usage
|
152
153
|
|
@@ -198,6 +199,57 @@ preview_redirect_object.href
|
|
198
199
|
|
199
200
|
See the {WCC::Contentful::Model} documentation for more details.
|
200
201
|
|
202
|
+
#### Rich Text support
|
203
|
+
|
204
|
+
As of version 1.5.0, the Model API supports parsing and rendering Rich Text fields.
|
205
|
+
|
206
|
+
Rich Text fields are retrieved from the API and parsed into the WCC::Contentful::RichText::Document object model.
|
207
|
+
```rb
|
208
|
+
Page.find_by(slug: '/some-slug').my_rich_text
|
209
|
+
# => #<struct WCC::Contentful::RichText::Document ...
|
210
|
+
```
|
211
|
+
|
212
|
+
If you are using Rails, a rich text field can be rendered to HTML using the default renderer by
|
213
|
+
calling #to_html:
|
214
|
+
```rb
|
215
|
+
my_rich_text.to_html
|
216
|
+
# => "<div class=\"contentful-rich-text\"><h2>Dear Watermark Family,</h2>
|
217
|
+
```
|
218
|
+
|
219
|
+
If you are not using Rails, or if you want to override the default rendering behavior, you need to set the
|
220
|
+
WCC::Contentful::Configuration#rich_text_field configuration option:
|
221
|
+
```rb
|
222
|
+
# lib/my_rich_text_renderer
|
223
|
+
class MyRichTextRenderer < WCC::Contentful::ActionViewRichTextRenderer
|
224
|
+
def render_hyperlink(node)
|
225
|
+
# override the default logic for rendering hyperlinks
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
# config/initializers/wcc_contentful.rb
|
230
|
+
WCC::Contentful.configure do |config|
|
231
|
+
config.rich_text_renderer = MyRichTextRenderer
|
232
|
+
end
|
233
|
+
```
|
234
|
+
|
235
|
+
If you want to construct and render WCC::Contentful::RichText::Document objects directly, the #to_html method will
|
236
|
+
raise an error. Instead, you will need to construct and invoke your renderer directly.
|
237
|
+
```rb
|
238
|
+
my_document = WCC::Contentful::RichText.tokenize(JSON.parse(...contentful CDN rich text field representation...))
|
239
|
+
# => #<struct WCC::Contentful::RichText::Document ...
|
240
|
+
|
241
|
+
renderer = MyRichTextRenderer.new(my_document,
|
242
|
+
# (optional) inject services so the renderer can automatically resolve links to entries and assets.
|
243
|
+
# The renderer still works without this, but hyperlinks which reference Assets or Entries will raise an error.
|
244
|
+
config: WCC::Contentful.configuration,
|
245
|
+
store: WCC::Contentful::Services.instance.store,
|
246
|
+
model_namespace: WCC::Contentful::Model)
|
247
|
+
# => #<MyRichTextRenderer:0x0000000005c71a78
|
248
|
+
|
249
|
+
renderer.call
|
250
|
+
# => "<div class=\"contentful-rich-text\"><h2>Dear Watermark Family,</h2>
|
251
|
+
```
|
252
|
+
|
201
253
|
### Store API
|
202
254
|
|
203
255
|
The Store layer is used by the Model API to access Contentful data in a raw form.
|
@@ -359,7 +411,7 @@ From the bottom up:
|
|
359
411
|
|
360
412
|
### Client Layer
|
361
413
|
|
362
|
-
The {WCC::Contentful::SimpleClient} provides methods to access the [Contentful
|
414
|
+
The {WCC::Contentful::SimpleClient} provides methods to access the [Contentful
|
363
415
|
Content Delivery API](https://www.contentful.com/developers/docs/references/content-delivery-api/)
|
364
416
|
through your favorite HTTP client gem. The SimpleClient expects
|
365
417
|
an Adapter that conforms to the Faraday interface.
|
@@ -403,7 +455,7 @@ require 'wcc/contentful/store/rspec_examples'
|
|
403
455
|
|
404
456
|
RSpec.describe MyStore do
|
405
457
|
it_behaves_like 'contentful store', {
|
406
|
-
# Set which store features your store implements.
|
458
|
+
# Set which store features your store implements.
|
407
459
|
nested_queries: true, # Does your store implement JOINs?
|
408
460
|
include_param: true # Does your store resolve links when given the :include option?
|
409
461
|
}
|
@@ -695,7 +747,7 @@ defined inside the `app` directory, this will have the effect of deleting all co
|
|
695
747
|
as well as the constants generated from your schema.
|
696
748
|
This will result in one of two errors:
|
697
749
|
|
698
|
-
* `NameError (uninitialized constant MySecondSpace::MyContentType)`
|
750
|
+
* `NameError (uninitialized constant MySecondSpace::MyContentType)`
|
699
751
|
if you try to reference a subclass such as `MyContentType < MySecondSpace::MyContentType`
|
700
752
|
* `ArgumentError (Not yet configured!)`
|
701
753
|
if you try to `MySecondSpace.find('xxxx')` to load an Entry or Asset
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'wcc/contentful'
|
4
|
+
|
5
|
+
namespace :wcc_contentful do
|
6
|
+
desc 'Rewrites JSON fixtures from locale=* to locale=en-US'
|
7
|
+
task :rewrite_fixtures, %i[glob locale] => :environment do |_t, args|
|
8
|
+
glob = args[:glob] || 'spec/fixtures/**/*.json'
|
9
|
+
locale = args[:locale] || 'en-US'
|
10
|
+
|
11
|
+
Dir.glob(glob) do |filename|
|
12
|
+
next unless File.file?(filename)
|
13
|
+
|
14
|
+
contents = JSON.parse(File.read(filename))
|
15
|
+
next unless contents.is_a?(Hash) && contents['sys']
|
16
|
+
|
17
|
+
rewritten_contents =
|
18
|
+
case contents['sys']['type']
|
19
|
+
when 'Array'
|
20
|
+
rewrite_array(contents, locale: locale)
|
21
|
+
when 'Entry', 'Asset'
|
22
|
+
rewrite_entry(contents, locale: locale)
|
23
|
+
end
|
24
|
+
next unless rewritten_contents
|
25
|
+
|
26
|
+
File.write(filename, JSON.pretty_generate(rewritten_contents))
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def rewrite_array(contents, locale: 'en-US')
|
31
|
+
contents['items'] =
|
32
|
+
contents['items'].map do |item|
|
33
|
+
rewrite_entry(item, locale: locale)
|
34
|
+
end
|
35
|
+
if contents['includes']
|
36
|
+
if contents['includes']['Entry']
|
37
|
+
contents['includes']['Entry'] =
|
38
|
+
contents['includes']['Entry'].map do |item|
|
39
|
+
rewrite_entry(item, locale: locale)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
if contents['includes']['Asset']
|
43
|
+
contents['includes']['Asset'] =
|
44
|
+
contents['includes']['Asset'].map do |item|
|
45
|
+
rewrite_entry(item, locale: locale)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
contents
|
51
|
+
end
|
52
|
+
|
53
|
+
def rewrite_entry(contents, locale: 'en-US')
|
54
|
+
return contents unless contents['sys']
|
55
|
+
|
56
|
+
WCC::Contentful::EntryLocaleTransformer.transform_to_locale(
|
57
|
+
contents,
|
58
|
+
locale
|
59
|
+
)
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'action_view'
|
4
|
+
|
5
|
+
# An implementation of the RichTextRenderer that uses ActionView helpers to implement content_tag and concat.
|
6
|
+
class WCC::Contentful::ActionViewRichTextRenderer < WCC::Contentful::RichTextRenderer
|
7
|
+
include ActionView::Helpers::TagHelper
|
8
|
+
include ActionView::Helpers::TextHelper
|
9
|
+
include ActionView::Context
|
10
|
+
|
11
|
+
# TODO: use ActionView view context to render ERB templates for embedded entries?
|
12
|
+
end
|
@@ -14,6 +14,7 @@ class WCC::Contentful::Configuration
|
|
14
14
|
logger
|
15
15
|
management_token
|
16
16
|
preview_token
|
17
|
+
rich_text_renderer
|
17
18
|
schema_file
|
18
19
|
space
|
19
20
|
store
|
@@ -77,6 +78,12 @@ class WCC::Contentful::Configuration
|
|
77
78
|
# Default: 2.seconds
|
78
79
|
attr_accessor :sync_retry_wait
|
79
80
|
|
81
|
+
# Sets the rich text renderer implementation. This must be a class that accepts a WCC::Contentful::RichText::Document
|
82
|
+
# in the constructor, and responds to `:call` with a string containing the HTML.
|
83
|
+
# In a Rails context, the implementation defaults to WCC::Contentful::ActionViewRichTextRenderer.
|
84
|
+
# In a non-Rails context, you must provide your own implementation.
|
85
|
+
attr_accessor :rich_text_renderer
|
86
|
+
|
80
87
|
# Returns true if the currently configured environment is pointing at `master`.
|
81
88
|
def master?
|
82
89
|
!environment.present?
|
@@ -204,6 +211,12 @@ class WCC::Contentful::Configuration
|
|
204
211
|
}
|
205
212
|
@management_token = ENV.fetch('CONTENTFUL_MANAGEMENT_TOKEN', nil)
|
206
213
|
@preview_token = ENV.fetch('CONTENTFUL_PREVIEW_TOKEN', nil)
|
214
|
+
|
215
|
+
if defined?(ActionView)
|
216
|
+
require 'wcc/contentful/action_view_rich_text_renderer'
|
217
|
+
@rich_text_renderer = WCC::Contentful::ActionViewRichTextRenderer
|
218
|
+
end
|
219
|
+
|
207
220
|
@space = ENV.fetch('CONTENTFUL_SPACE_ID', nil)
|
208
221
|
@default_locale = 'en-US'
|
209
222
|
@locale_fallbacks = {}
|
@@ -37,7 +37,7 @@ module WCC::Contentful::ModelAPI
|
|
37
37
|
# try looking up the class heierarchy
|
38
38
|
(superclass.services if superclass.respond_to?(:services)) ||
|
39
39
|
# create it if we have a configuration
|
40
|
-
WCC::Contentful::Services.new(configuration)
|
40
|
+
WCC::Contentful::Services.new(configuration, model_namespace: self)
|
41
41
|
end
|
42
42
|
|
43
43
|
def store(preview = nil)
|
@@ -104,7 +104,7 @@ module WCC::Contentful
|
|
104
104
|
# when :DateTime
|
105
105
|
# raw_value = Time.parse(raw_value).localtime
|
106
106
|
when :RichText
|
107
|
-
raw_value = WCC::Contentful::RichText.tokenize(raw_value)
|
107
|
+
raw_value = WCC::Contentful::RichText.tokenize(raw_value, renderer: ns.services.rich_text_renderer)
|
108
108
|
when :Int
|
109
109
|
raw_value = Integer(raw_value)
|
110
110
|
when :Float
|
@@ -28,6 +28,24 @@ module WCC::Contentful::RichText
|
|
28
28
|
tuple
|
29
29
|
end
|
30
30
|
end
|
31
|
+
|
32
|
+
# Set the renderer to use when rendering this node to HTML.
|
33
|
+
# By default a node does not have a renderer, and #to_html will raise an ArgumentError.
|
34
|
+
# However if a WCC::Contentful::RichText::Document node is created by the WCC::Contentful::ModelBuilder,
|
35
|
+
# it will inject the renderer configured in WCC::Contentful::Services#rich_text_renderer into the node.
|
36
|
+
attr_accessor :renderer
|
37
|
+
|
38
|
+
# Render the node to HTML using the configured renderer.
|
39
|
+
# See WCC::Contentful::RichTextRenderer for more information.
|
40
|
+
def to_html
|
41
|
+
unless renderer
|
42
|
+
raise ArgumentError,
|
43
|
+
'No renderer provided during tokenization. ' \
|
44
|
+
'Please configure the rich_text_renderer in your WCC::Contentful configuration.'
|
45
|
+
end
|
46
|
+
|
47
|
+
renderer.call(self)
|
48
|
+
end
|
31
49
|
end
|
32
50
|
|
33
51
|
class_methods do
|
@@ -36,8 +54,12 @@ module WCC::Contentful::RichText
|
|
36
54
|
name.demodulize.underscore.dasherize
|
37
55
|
end
|
38
56
|
|
39
|
-
def
|
40
|
-
|
57
|
+
def matches?(node_type)
|
58
|
+
self.node_type == node_type
|
59
|
+
end
|
60
|
+
|
61
|
+
def tokenize(raw, renderer: nil)
|
62
|
+
raise ArgumentError, "Expected '#{node_type}', got '#{raw['nodeType']}'" unless matches?(raw['nodeType'])
|
41
63
|
|
42
64
|
values =
|
43
65
|
members.map do |symbol|
|
@@ -45,15 +67,17 @@ module WCC::Contentful::RichText
|
|
45
67
|
|
46
68
|
case symbol
|
47
69
|
when :content
|
48
|
-
WCC::Contentful::RichText.tokenize(val,
|
49
|
-
# when :data
|
50
|
-
# TODO: resolve links...
|
70
|
+
WCC::Contentful::RichText.tokenize(val, renderer: renderer)
|
51
71
|
else
|
52
72
|
val
|
53
73
|
end
|
54
74
|
end
|
55
75
|
|
56
|
-
new(*values)
|
76
|
+
new(*values).tap do |node|
|
77
|
+
next unless renderer
|
78
|
+
|
79
|
+
node.renderer = renderer
|
80
|
+
end
|
57
81
|
end
|
58
82
|
end
|
59
83
|
end
|
@@ -23,9 +23,11 @@ require_relative './rich_text/node'
|
|
23
23
|
module WCC::Contentful::RichText
|
24
24
|
##
|
25
25
|
# Recursively converts a raw JSON-parsed hash into the RichText object model.
|
26
|
-
|
26
|
+
# If renderer are provided, the model will be able to resolve links to entries
|
27
|
+
# and enable direct rendering of documents to HTML.
|
28
|
+
def self.tokenize(raw, renderer: nil)
|
27
29
|
return unless raw
|
28
|
-
return raw.map { |c| tokenize(c
|
30
|
+
return raw.map { |c| tokenize(c) } if raw.is_a?(Array)
|
29
31
|
|
30
32
|
klass =
|
31
33
|
case raw['nodeType']
|
@@ -33,10 +35,26 @@ module WCC::Contentful::RichText
|
|
33
35
|
Document
|
34
36
|
when 'paragraph'
|
35
37
|
Paragraph
|
38
|
+
when 'hr'
|
39
|
+
HR
|
36
40
|
when 'blockquote'
|
37
41
|
Blockquote
|
38
42
|
when 'text'
|
39
43
|
Text
|
44
|
+
when 'ordered-list'
|
45
|
+
OrderedList
|
46
|
+
when 'unordered-list'
|
47
|
+
UnorderedList
|
48
|
+
when 'list-item'
|
49
|
+
ListItem
|
50
|
+
when 'table'
|
51
|
+
Table
|
52
|
+
when 'table-row'
|
53
|
+
TableRow
|
54
|
+
when 'table-cell'
|
55
|
+
TableCell
|
56
|
+
when 'table-header-cell'
|
57
|
+
TableHeaderCell
|
40
58
|
when 'embedded-entry-inline'
|
41
59
|
EmbeddedEntryInline
|
42
60
|
when 'embedded-entry-block'
|
@@ -44,13 +62,17 @@ module WCC::Contentful::RichText
|
|
44
62
|
when 'embedded-asset-block'
|
45
63
|
EmbeddedAssetBlock
|
46
64
|
when /heading-(\d+)/
|
47
|
-
|
48
|
-
|
65
|
+
Heading
|
66
|
+
when /(\w+-)?hyperlink/
|
67
|
+
Hyperlink
|
49
68
|
else
|
69
|
+
# Future proofing for new node types introduced by Contentful.
|
70
|
+
# The best list of node types maintained by Contentful is here:
|
71
|
+
# https://github.com/contentful/rich-text/blob/master/packages/rich-text-types/src/blocks.ts
|
50
72
|
Unknown
|
51
73
|
end
|
52
74
|
|
53
|
-
klass.tokenize(raw,
|
75
|
+
klass.tokenize(raw, renderer: renderer)
|
54
76
|
end
|
55
77
|
|
56
78
|
Document =
|
@@ -63,6 +85,11 @@ module WCC::Contentful::RichText
|
|
63
85
|
include WCC::Contentful::RichText::Node
|
64
86
|
end
|
65
87
|
|
88
|
+
HR =
|
89
|
+
Struct.new(:nodeType, :data, :content) do
|
90
|
+
include WCC::Contentful::RichText::Node
|
91
|
+
end
|
92
|
+
|
66
93
|
Blockquote =
|
67
94
|
Struct.new(:nodeType, :data, :content) do
|
68
95
|
include WCC::Contentful::RichText::Node
|
@@ -73,6 +100,41 @@ module WCC::Contentful::RichText
|
|
73
100
|
include WCC::Contentful::RichText::Node
|
74
101
|
end
|
75
102
|
|
103
|
+
OrderedList =
|
104
|
+
Struct.new(:nodeType, :data, :content) do
|
105
|
+
include WCC::Contentful::RichText::Node
|
106
|
+
end
|
107
|
+
|
108
|
+
UnorderedList =
|
109
|
+
Struct.new(:nodeType, :data, :content) do
|
110
|
+
include WCC::Contentful::RichText::Node
|
111
|
+
end
|
112
|
+
|
113
|
+
ListItem =
|
114
|
+
Struct.new(:nodeType, :data, :content) do
|
115
|
+
include WCC::Contentful::RichText::Node
|
116
|
+
end
|
117
|
+
|
118
|
+
Table =
|
119
|
+
Struct.new(:nodeType, :data, :content) do
|
120
|
+
include WCC::Contentful::RichText::Node
|
121
|
+
end
|
122
|
+
|
123
|
+
TableRow =
|
124
|
+
Struct.new(:nodeType, :data, :content) do
|
125
|
+
include WCC::Contentful::RichText::Node
|
126
|
+
end
|
127
|
+
|
128
|
+
TableCell =
|
129
|
+
Struct.new(:nodeType, :data, :content) do
|
130
|
+
include WCC::Contentful::RichText::Node
|
131
|
+
end
|
132
|
+
|
133
|
+
TableHeaderCell =
|
134
|
+
Struct.new(:nodeType, :data, :content) do
|
135
|
+
include WCC::Contentful::RichText::Node
|
136
|
+
end
|
137
|
+
|
76
138
|
EmbeddedEntryInline =
|
77
139
|
Struct.new(:nodeType, :data, :content) do
|
78
140
|
include WCC::Contentful::RichText::Node
|
@@ -88,18 +150,40 @@ module WCC::Contentful::RichText
|
|
88
150
|
include WCC::Contentful::RichText::Node
|
89
151
|
end
|
90
152
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
153
|
+
EmbeddedResourceBlock =
|
154
|
+
Struct.new(:nodeType, :data, :content) do
|
155
|
+
include WCC::Contentful::RichText::Node
|
156
|
+
end
|
157
|
+
|
158
|
+
Heading =
|
159
|
+
Struct.new(:nodeType, :data, :content) do
|
160
|
+
include WCC::Contentful::RichText::Node
|
161
|
+
|
162
|
+
def self.matches?(node_type)
|
163
|
+
node_type =~ /heading-(\d+)/
|
95
164
|
end
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
165
|
+
|
166
|
+
def size
|
167
|
+
@size ||= /heading-(\d+)/.match(nodeType)[1]&.to_i
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
Hyperlink =
|
172
|
+
Struct.new(:nodeType, :data, :content) do
|
173
|
+
include WCC::Contentful::RichText::Node
|
174
|
+
|
175
|
+
def self.matches?(node_type)
|
176
|
+
node_type =~ /(\w+-)?hyperlink/
|
177
|
+
end
|
178
|
+
end
|
100
179
|
|
101
180
|
Unknown =
|
102
181
|
Struct.new(:nodeType, :data, :content) do
|
103
182
|
include WCC::Contentful::RichText::Node
|
183
|
+
|
184
|
+
# Unknown nodes are the catch all, so they always match anything that made it to the else case of the switch.
|
185
|
+
def self.matches?(_node_type)
|
186
|
+
true
|
187
|
+
end
|
104
188
|
end
|
105
189
|
end
|
@@ -0,0 +1,300 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# The abstract base class for rendering Rich Text.
|
4
|
+
# This base class implements much of the recursive logic necessary for rendering
|
5
|
+
# Rich Text nodes, but leaves the actual rendering of the HTML tags to the
|
6
|
+
# subclasses.
|
7
|
+
#
|
8
|
+
# Subclasses can override any method to customize the rendering behavior. At a minimum they must implement
|
9
|
+
# the #content_tag and #concat methods to take advantage of the recursive rendering logic in the base class.
|
10
|
+
# The API for these methods is assumed to be equivalent to the ActionView helpers of the same name.
|
11
|
+
#
|
12
|
+
# The canonical implementation is the WCC::Contentful::ActionViewRichTextRenderer, which uses the standard ActionView
|
13
|
+
# helpers as-is to render the HTML tags.
|
14
|
+
#
|
15
|
+
# @example
|
16
|
+
# class MyRichTextRenderer < WCC::Contentful::RichTextRenderer
|
17
|
+
# def content_tag(name, options, &block)
|
18
|
+
# # your implementation here
|
19
|
+
# # for reference of expected behavior see
|
20
|
+
# # https://api.rubyonrails.org/classes/ActionView/Helpers/TagHelper.html#method-i-content_tag
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# def concat(html_string)
|
24
|
+
# # your implementation here
|
25
|
+
# # for reference of expected behavior see
|
26
|
+
# # https://api.rubyonrails.org/classes/ActionView/Helpers/TextHelper.html#method-i-concat
|
27
|
+
# end
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# renderer = MyRichTextRenderer.new(document)
|
31
|
+
# renderer.call
|
32
|
+
#
|
33
|
+
# @abstract
|
34
|
+
class WCC::Contentful::RichTextRenderer
|
35
|
+
class << self
|
36
|
+
def call(document, *args, **kwargs)
|
37
|
+
new(document, *args, **kwargs).call
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
attr_reader :document
|
42
|
+
attr_accessor :config, :store, :model_namespace
|
43
|
+
|
44
|
+
def initialize(document, config: nil, store: nil, model_namespace: nil)
|
45
|
+
@document = document
|
46
|
+
@config = config if config.present?
|
47
|
+
@store = store if store.present?
|
48
|
+
@model_namespace = model_namespace if model_namespace.present?
|
49
|
+
end
|
50
|
+
|
51
|
+
def call
|
52
|
+
render.to_s
|
53
|
+
end
|
54
|
+
|
55
|
+
def render
|
56
|
+
content_tag(:div, class: 'contentful-rich-text') do
|
57
|
+
render_content(document.content)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def render_content(content)
|
62
|
+
content&.each do |node|
|
63
|
+
concat render_node(node)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def render_node(node)
|
68
|
+
if WCC::Contentful::RichText::Heading.matches?(node.node_type)
|
69
|
+
render_heading(node)
|
70
|
+
else
|
71
|
+
public_send(:"render_#{node.node_type.underscore}", node)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def render_text(node)
|
76
|
+
return node.value unless node.marks&.any?
|
77
|
+
|
78
|
+
node.marks.reduce(node.value) do |value, mark|
|
79
|
+
next value unless type = mark['type']&.underscore
|
80
|
+
|
81
|
+
render_mark(type, value)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
DEFAULT_MARKS = {
|
86
|
+
'bold' => 'strong',
|
87
|
+
'italic' => 'em',
|
88
|
+
'underline' => 'u',
|
89
|
+
'code' => 'code',
|
90
|
+
'superscript' => 'sup',
|
91
|
+
'subscript' => 'sub'
|
92
|
+
}.freeze
|
93
|
+
|
94
|
+
def render_mark(type, value)
|
95
|
+
return value unless tag = DEFAULT_MARKS[type]
|
96
|
+
|
97
|
+
content_tag(tag, value)
|
98
|
+
end
|
99
|
+
|
100
|
+
def render_paragraph(node)
|
101
|
+
content_tag(:p) do
|
102
|
+
render_content(node.content)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def render_heading(node)
|
107
|
+
content_tag(:"h#{node.size}") do
|
108
|
+
render_content(node.content)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def render_blockquote(node)
|
113
|
+
content_tag(:blockquote) do
|
114
|
+
render_content(node.content)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def render_hr(_node)
|
119
|
+
content_tag(:hr)
|
120
|
+
end
|
121
|
+
|
122
|
+
def render_unordered_list(node)
|
123
|
+
content_tag(:ul) do
|
124
|
+
render_content(node.content)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def render_ordered_list(node)
|
129
|
+
content_tag(:ol) do
|
130
|
+
render_content(node.content)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def render_list_item(node)
|
135
|
+
content_tag(:li) do
|
136
|
+
render_content(node.content)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def render_table(node)
|
141
|
+
content_tag(:table) do
|
142
|
+
# Check the first row - if it's a header row, render a <thead>
|
143
|
+
first, *rest = node.content
|
144
|
+
if first&.content&.all? { |cell| cell.node_type == 'table-header-cell' }
|
145
|
+
concat(content_tag(:thead) { render_content([first]) })
|
146
|
+
else
|
147
|
+
# Otherwise, render it inside the tbody with the rest
|
148
|
+
rest.unshift(first)
|
149
|
+
end
|
150
|
+
|
151
|
+
concat(content_tag(:tbody) { render_content(rest) })
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def render_table_row(node)
|
156
|
+
content_tag(:tr) do
|
157
|
+
render_content(node.content)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def render_table_cell(node)
|
162
|
+
content_tag(:td) do
|
163
|
+
render_content(node.content)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
def render_table_header_cell(node)
|
168
|
+
content_tag(:th) do
|
169
|
+
render_content(node.content)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def render_hyperlink(node)
|
174
|
+
content_tag(:a,
|
175
|
+
href: node.data['uri'],
|
176
|
+
# External links should be target="_blank" by default
|
177
|
+
target: ('_blank' if url_is_external?(node.data['uri']))) do
|
178
|
+
render_content(node.content)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def render_asset_hyperlink(node)
|
183
|
+
target = resolve_target(node.data['target'])
|
184
|
+
url = target&.dig('fields', 'file', 'url')
|
185
|
+
|
186
|
+
render_hyperlink(
|
187
|
+
WCC::Contentful::RichText::Hyperlink.tokenize(
|
188
|
+
node.as_json.merge(
|
189
|
+
'nodeType' => 'hyperlink',
|
190
|
+
'data' => node['data'].merge({
|
191
|
+
'uri' => url,
|
192
|
+
'target' => target.as_json
|
193
|
+
})
|
194
|
+
)
|
195
|
+
)
|
196
|
+
)
|
197
|
+
end
|
198
|
+
|
199
|
+
def render_entry_hyperlink(node)
|
200
|
+
unless model_namespace.present?
|
201
|
+
raise NotConnectedError,
|
202
|
+
'Rendering linked entries requires a connected RichTextRenderer. Please use the one configured in ' \
|
203
|
+
'WCC::Contentful::Services.instance or pass a model_namespace to the RichTextRenderer constructor.'
|
204
|
+
end
|
205
|
+
|
206
|
+
target = resolve_target(node.data['target'])
|
207
|
+
model_instance = model_namespace.new_from_raw(target)
|
208
|
+
unless model_instance.respond_to?(:href)
|
209
|
+
raise NotConnectedError,
|
210
|
+
"Entry hyperlinks are not supported for #{model_instance.class}. " \
|
211
|
+
'Please ensure your model defines an #href method, or override the ' \
|
212
|
+
'#render_entry_hyperlink method in your app-specific RichTextRenderer implementation.'
|
213
|
+
end
|
214
|
+
|
215
|
+
render_hyperlink(
|
216
|
+
WCC::Contentful::RichText::Hyperlink.tokenize(
|
217
|
+
node.as_json.merge(
|
218
|
+
'nodeType' => 'hyperlink',
|
219
|
+
'data' => node['data'].merge({
|
220
|
+
'uri' => model_instance.href,
|
221
|
+
'target' => target.as_json
|
222
|
+
})
|
223
|
+
)
|
224
|
+
)
|
225
|
+
)
|
226
|
+
end
|
227
|
+
|
228
|
+
def render_embedded_asset_block(node)
|
229
|
+
target = resolve_target(node.data['target'])
|
230
|
+
title = target&.dig('fields', 'title')
|
231
|
+
url = target&.dig('fields', 'file', 'url')
|
232
|
+
|
233
|
+
content_tag(:img, src: url, alt: title) do
|
234
|
+
render_content(node.content)
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
def render_embedded_entry_block(_node)
|
239
|
+
raise AbstractRendererError,
|
240
|
+
'Entry embeds are not supported. What should it look like? ' \
|
241
|
+
'Please override this in your app-specific RichTextRenderer implementation.'
|
242
|
+
end
|
243
|
+
|
244
|
+
def render_embedded_entry_inline(_node)
|
245
|
+
raise AbstractRendererError,
|
246
|
+
'Inline Entry embeds are not supported. What should it look like? ' \
|
247
|
+
'Please override this in your app-specific RichTextRenderer implementation.'
|
248
|
+
end
|
249
|
+
|
250
|
+
private
|
251
|
+
|
252
|
+
def resolve_target(target)
|
253
|
+
unless store.present?
|
254
|
+
raise NotConnectedError,
|
255
|
+
'Rendering embedded or linked entries requires a connected RichTextRenderer. Please use the one configured ' \
|
256
|
+
'in WCC::Contentful::Services.instance or pass a store to the RichTextRenderer constructor.'
|
257
|
+
end
|
258
|
+
|
259
|
+
if target&.dig('sys', 'type') == 'Link'
|
260
|
+
target = store.find(target.dig('sys', 'id'), hint: target.dig('sys', 'linkType'))
|
261
|
+
end
|
262
|
+
target
|
263
|
+
end
|
264
|
+
|
265
|
+
def url_is_external?(url)
|
266
|
+
return false unless url.present?
|
267
|
+
|
268
|
+
uri =
|
269
|
+
begin
|
270
|
+
URI(url)
|
271
|
+
rescue StandardError
|
272
|
+
nil
|
273
|
+
end
|
274
|
+
return false unless uri&.host.present?
|
275
|
+
|
276
|
+
app_uri =
|
277
|
+
if config&.app_url.present?
|
278
|
+
begin
|
279
|
+
URI(config.app_url)
|
280
|
+
rescue StandardError
|
281
|
+
nil
|
282
|
+
end
|
283
|
+
end
|
284
|
+
uri.host != app_uri&.host
|
285
|
+
end
|
286
|
+
|
287
|
+
def content_tag(*_args)
|
288
|
+
raise AbstractRendererError, 'RichTextRenderer is an abstract class, please use an implementation subclass'
|
289
|
+
end
|
290
|
+
|
291
|
+
def concat(*_args)
|
292
|
+
raise AbstractRendererError, 'RichTextRenderer is an abstract class, please use an implementation subclass'
|
293
|
+
end
|
294
|
+
|
295
|
+
class AbstractRendererError < StandardError
|
296
|
+
end
|
297
|
+
|
298
|
+
class NotConnectedError < AbstractRendererError
|
299
|
+
end
|
300
|
+
end
|
@@ -11,10 +11,11 @@ module WCC::Contentful
|
|
11
11
|
|
12
12
|
attr_reader :configuration
|
13
13
|
|
14
|
-
def initialize(configuration)
|
14
|
+
def initialize(configuration, model_namespace: nil)
|
15
15
|
raise ArgumentError, 'Not yet configured!' unless configuration
|
16
16
|
|
17
17
|
@configuration = configuration
|
18
|
+
@model_namespace = model_namespace
|
18
19
|
end
|
19
20
|
|
20
21
|
# Gets the data-store which executes the queries run against the dynamic
|
@@ -123,6 +124,41 @@ module WCC::Contentful
|
|
123
124
|
end
|
124
125
|
end
|
125
126
|
|
127
|
+
# Returns a callable object which can be used to render a rich text document.
|
128
|
+
# This object will have all the connected services injected into it.
|
129
|
+
# The implementation class is configured by {WCC::Contentful::Configuration#rich_text_renderer}.
|
130
|
+
# In a rails context the default implementation is {WCC::Contentful::ActionViewRichTextRenderer}.
|
131
|
+
def rich_text_renderer
|
132
|
+
@rich_text_renderer ||=
|
133
|
+
if implementation_class = configuration&.rich_text_renderer
|
134
|
+
store = self.store
|
135
|
+
config = configuration
|
136
|
+
model_namespace = @model_namespace || WCC::Contentful::Model
|
137
|
+
|
138
|
+
# Wrap the implementation in a subclass that injects the services
|
139
|
+
Class.new(implementation_class) do
|
140
|
+
define_method :initialize do |document, *args, **kwargs|
|
141
|
+
# Implementation might choose to override these, so call super last
|
142
|
+
@store = store
|
143
|
+
@config = config
|
144
|
+
@model_namespace = model_namespace
|
145
|
+
super(document, *args, **kwargs)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
else
|
149
|
+
# Create a renderer that renders a more helpful error message, but delay the error message until #to_html
|
150
|
+
# is actually invoked in case the user never actually uses the renderer.
|
151
|
+
Class.new(WCC::Contentful::RichTextRenderer) do
|
152
|
+
def call
|
153
|
+
raise WCC::Contentful::RichTextRenderer::AbstractRendererError,
|
154
|
+
'No rich text renderer implementation has been configured. ' \
|
155
|
+
'Please install a supported implementation such as ActionView, ' \
|
156
|
+
'or set WCC::Contentful.configuration.rich_text_renderer to a custom implementation.'
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
126
162
|
# Gets the configured instrumentation adapter, defaulting to ActiveSupport::Notifications
|
127
163
|
def instrumentation
|
128
164
|
@instrumentation ||=
|
@@ -104,7 +104,7 @@ class WCC::Contentful::SimpleClient
|
|
104
104
|
def includes
|
105
105
|
@includes ||=
|
106
106
|
raw['includes']&.each_with_object({}) do |(_t, entries), h|
|
107
|
-
entries
|
107
|
+
entries&.each { |e| h[e.dig('sys', 'id')] = e }
|
108
108
|
end || {}
|
109
109
|
end
|
110
110
|
end
|
data/lib/wcc/contentful.rb
CHANGED
@@ -20,6 +20,8 @@ require 'wcc/contentful/model'
|
|
20
20
|
require 'wcc/contentful/model_methods'
|
21
21
|
require 'wcc/contentful/model_singleton_methods'
|
22
22
|
require 'wcc/contentful/model_builder'
|
23
|
+
require 'wcc/contentful/rich_text'
|
24
|
+
require 'wcc/contentful/rich_text_renderer'
|
23
25
|
require 'wcc/contentful/sync_engine'
|
24
26
|
require 'wcc/contentful/events'
|
25
27
|
require 'wcc/contentful/middleware'
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: wcc-contentful
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.5.0.rc1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Watermark Dev
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-06-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: byebug
|
@@ -423,7 +423,9 @@ files:
|
|
423
423
|
- config/initializers/mime_types.rb
|
424
424
|
- config/routes.rb
|
425
425
|
- lib/tasks/download_schema.rake
|
426
|
+
- lib/tasks/rewrite_fixtures.rake
|
426
427
|
- lib/wcc/contentful.rb
|
428
|
+
- lib/wcc/contentful/action_view_rich_text_renderer.rb
|
427
429
|
- lib/wcc/contentful/active_record_shim.rb
|
428
430
|
- lib/wcc/contentful/configuration.rb
|
429
431
|
- lib/wcc/contentful/content_type_indexer.rb
|
@@ -451,6 +453,7 @@ files:
|
|
451
453
|
- lib/wcc/contentful/rake.rb
|
452
454
|
- lib/wcc/contentful/rich_text.rb
|
453
455
|
- lib/wcc/contentful/rich_text/node.rb
|
456
|
+
- lib/wcc/contentful/rich_text_renderer.rb
|
454
457
|
- lib/wcc/contentful/rspec.rb
|
455
458
|
- lib/wcc/contentful/services.rb
|
456
459
|
- lib/wcc/contentful/simple_client.rb
|
@@ -495,7 +498,7 @@ homepage: https://github.com/watermarkchurch/wcc-contentful/wcc-contentful
|
|
495
498
|
licenses:
|
496
499
|
- MIT
|
497
500
|
metadata:
|
498
|
-
documentation_uri: https://watermarkchurch.github.io/wcc-contentful/1.
|
501
|
+
documentation_uri: https://watermarkchurch.github.io/wcc-contentful/1.5/wcc-contentful
|
499
502
|
rubygems_mfa_required: 'true'
|
500
503
|
post_install_message:
|
501
504
|
rdoc_options: []
|
@@ -508,9 +511,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
508
511
|
version: '2.7'
|
509
512
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
510
513
|
requirements:
|
511
|
-
- - "
|
514
|
+
- - ">"
|
512
515
|
- !ruby/object:Gem::Version
|
513
|
-
version:
|
516
|
+
version: 1.3.1
|
514
517
|
requirements: []
|
515
518
|
rubygems_version: 3.3.7
|
516
519
|
signing_key:
|
@@ -523,11 +526,12 @@ summary: '[![Gem Version](https://badge.fury.io/rb/wcc-contentful.svg)](https://
|
|
523
526
|
[contentful_model](https://github.com/contentful/contentful_model), and [contentful_rails](https://github.com/contentful/contentful_rails)
|
524
527
|
gems all in one. Table of Contents: 1. [Why?](#why-did-you-rewrite-the-contentful-ruby-stack)
|
525
528
|
2. [Installation](#installation) 3. [Configuration](#configure) 4. [Usage](#usage)
|
526
|
-
1. [Model API](#wcccontentfulmodel-api)
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
529
|
+
1. [Model API](#wcccontentfulmodel-api) * [Rich Text Support](#rich-text-support)
|
530
|
+
2. [Store API](#store-api) 3. [Direct CDN client](#direct-cdn-api-simpleclient)
|
531
|
+
4. [Accessing the APIs](#accessing-the-apis-within-application-code) 5. [Architecture](#architecture)
|
532
|
+
1. [Client Layer](#client-layer) 2. [Store Layer](#store-layer) 3. [Model Layer](#model-layer)
|
533
|
+
6. [Test Helpers](#test-helpers) 7. [Advanced Configuration Example](#advanced-configuration-example)
|
534
|
+
8. [Connecting to Multiple Spaces](#connecting-to-multiple-spaces-or-environments)
|
531
535
|
9. [Development](#development) 10. [Contributing](#contributing) 11. [License](#license) ##
|
532
536
|
Why did you rewrite the Contentful ruby stack? We started working with Contentful
|
533
537
|
almost 5 years ago. Since that time, Contentful''s ruby stack has improved, but
|
@@ -619,7 +623,7 @@ summary: '[![Gem Version](https://badge.fury.io/rb/wcc-contentful.svg)](https://
|
|
619
623
|
$ gem install wcc-contentful ``` ## Configure Put this in an initializer: ```ruby
|
620
624
|
# config/initializers/wcc_contentful.rb WCC::Contentful.configure do |config| config.access_token
|
621
625
|
= <CONTENTFUL_ACCESS_TOKEN> config.space = <CONTENTFUL_SPACE_ID> end WCC::Contentful.init!
|
622
|
-
``` All configuration options can be found [in the rubydoc under WCC::Contentful::Configuration](https://watermarkchurch.github.io/wcc-contentful/latest/wcc-contentful/WCC/Contentful/Configuration)
|
626
|
+
``` All configuration options can be found [in the rubydoc under WCC::Contentful::Configuration](https://watermarkchurch.github.io/wcc-contentful/latest/wcc-contentful/WCC/Contentful/Configuration) ##
|
623
627
|
Usage ### WCC::Contentful::Model API The WCC::Contentful::Model API exposes Contentful
|
624
628
|
data as a set of dynamically generated Ruby objects. These objects are based on
|
625
629
|
the content types in your Contentful space. All these objects are generated by
|
@@ -640,12 +644,34 @@ summary: '[![Gem Version](https://badge.fury.io/rb/wcc-contentful.svg)](https://
|
|
640
644
|
= WCC::Contentful::Model::Redirect.find_by({ slug: ''draft-redirect'' }, preview:
|
641
645
|
true) # => #<WCC::Contentful::Model::Redirect:0x0000000005d879ad @created_at=2018-04-16
|
642
646
|
18:41:17 UTC...> preview_redirect_object.href # => ''http://www.somesite.com/slug-for-redirect''
|
643
|
-
``` See the {WCC::Contentful::Model} documentation for more details.
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
647
|
+
``` See the {WCC::Contentful::Model} documentation for more details. #### Rich
|
648
|
+
Text support As of version 1.5.0, the Model API supports parsing and rendering
|
649
|
+
Rich Text fields. Rich Text fields are retrieved from the API and parsed into the
|
650
|
+
WCC::Contentful::RichText::Document object model. ```rb Page.find_by(slug: ''/some-slug'').my_rich_text
|
651
|
+
# => #<struct WCC::Contentful::RichText::Document ... ``` If you are using Rails,
|
652
|
+
a rich text field can be rendered to HTML using the default renderer by calling
|
653
|
+
#to_html: ```rb my_rich_text.to_html # => "<div class=\"contentful-rich-text\"><h2>Dear
|
654
|
+
Watermark Family,</h2> ``` If you are not using Rails, or if you want to override
|
655
|
+
the default rendering behavior, you need to set the WCC::Contentful::Configuration#rich_text_field
|
656
|
+
configuration option: ```rb # lib/my_rich_text_renderer class MyRichTextRenderer
|
657
|
+
< WCC::Contentful::ActionViewRichTextRenderer def render_hyperlink(node) # override
|
658
|
+
the default logic for rendering hyperlinks end end # config/initializers/wcc_contentful.rb
|
659
|
+
WCC::Contentful.configure do |config| config.rich_text_renderer = MyRichTextRenderer
|
660
|
+
end ``` If you want to construct and render WCC::Contentful::RichText::Document
|
661
|
+
objects directly, the #to_html method will raise an error. Instead, you will need
|
662
|
+
to construct and invoke your renderer directly. ```rb my_document = WCC::Contentful::RichText.tokenize(JSON.parse(...contentful
|
663
|
+
CDN rich text field representation...)) # => #<struct WCC::Contentful::RichText::Document
|
664
|
+
... renderer = MyRichTextRenderer.new(my_document, # (optional) inject services
|
665
|
+
so the renderer can automatically resolve links to entries and assets. # The renderer
|
666
|
+
still works without this, but hyperlinks which reference Assets or Entries will
|
667
|
+
raise an error. config: WCC::Contentful.configuration, store: WCC::Contentful::Services.instance.store,
|
668
|
+
model_namespace: WCC::Contentful::Model) # => #<MyRichTextRenderer:0x0000000005c71a78 renderer.call
|
669
|
+
# => "<div class=\"contentful-rich-text\"><h2>Dear Watermark Family,</h2> ``` ###
|
670
|
+
Store API The Store layer is used by the Model API to access Contentful data in
|
671
|
+
a raw form. The Store layer returns entries as hashes parsed from JSON, conforming
|
672
|
+
to the object structure returned from the Contentful CDN. The following examples
|
673
|
+
show how to use the Store API to retrieve raw data from the store: ```ruby store
|
674
|
+
= WCC::Contentful::Services.instance.store # => #<WCC::Contentful::Store::CDNAdapter:0x00007fb92a221498 store.find(''5FsqsbMECsM62e04U8sY4Y'')
|
649
675
|
# => {"sys"=> # ... # "fields"=> # ...} store.find_by(content_type: ''page'',
|
650
676
|
filter: { slug: ''/some-slug'' }) # => {"sys"=> # ... # "fields"=> # ...} query
|
651
677
|
= store.find_all(content_type: ''page'').eq(''group'', ''some-group'') # => #<WCC::Contentful::Store::CDNAdapter::Query:0x00007fa3d40b84f0
|
@@ -692,7 +718,7 @@ summary: '[![Gem Version](https://badge.fury.io/rb/wcc-contentful.svg)](https://
|
|
692
718
|
< ApplicationJob include WCC::Contentful::ServiceAccessors def perform Page.find(...) store.find(...) client.entries(...)
|
693
719
|
end end ``` ## Architecture ![wcc-contentful diagram](./doc-static/wcc-contentful.png) From
|
694
720
|
the bottom up: ### Client Layer The {WCC::Contentful::SimpleClient} provides methods
|
695
|
-
to access the [Contentful
|
721
|
+
to access the [Contentful Content Delivery API](https://www.contentful.com/developers/docs/references/content-delivery-api/)
|
696
722
|
through your favorite HTTP client gem. The SimpleClient expects an Adapter that
|
697
723
|
conforms to the Faraday interface. Creating a SimpleClient to connect using different
|
698
724
|
credentials, or to connect without setting up all the rest of WCC::Contentful, is
|
@@ -711,7 +737,7 @@ summary: '[![Gem Version](https://badge.fury.io/rb/wcc-contentful.svg)](https://
|
|
711
737
|
your own store. In your RSpec suite: ```ruby # frozen_string_literal: true require
|
712
738
|
''my_store'' require ''wcc/contentful/store/rspec_examples'' RSpec.describe MyStore
|
713
739
|
do it_behaves_like ''contentful store'', { # Set which store features your store
|
714
|
-
implements.
|
740
|
+
implements. nested_queries: true, # Does your store implement JOINs? include_param:
|
715
741
|
true # Does your store resolve links when given the :include option? } ``` The
|
716
742
|
store is kept up-to-date by the {WCC::Contentful::SyncEngine}. The `SyncEngine#next`
|
717
743
|
methodcalls the `#index` method on the configured store in order to update it with
|
@@ -827,7 +853,7 @@ summary: '[![Gem Version](https://badge.fury.io/rb/wcc-contentful.svg)](https://
|
|
827
853
|
in a class defined inside the `app` directory, this will have the effect of deleting
|
828
854
|
all configuration that was set in the initializer as well as the constants generated
|
829
855
|
from your schema. This will result in one of two errors: * `NameError (uninitialized
|
830
|
-
constant MySecondSpace::MyContentType)`
|
856
|
+
constant MySecondSpace::MyContentType)` if you try to reference a subclass such
|
831
857
|
as `MyContentType < MySecondSpace::MyContentType` * `ArgumentError (Not yet configured!)`
|
832
858
|
if you try to `MySecondSpace.find(''xxxx'')` to load an Entry or Asset The solution
|
833
859
|
is to have your secondary namespace in a folder which is not in the `autoload_paths`.
|