wcc-contentful 1.0.7 → 1.1.2
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.
- checksums.yaml +4 -4
- data/README.md +174 -9
- data/app/jobs/wcc/contentful/webhook_enable_job.rb +7 -7
- data/lib/wcc/contentful/instrumentation.rb +20 -2
- data/lib/wcc/contentful/link_visitor.rb +26 -30
- data/lib/wcc/contentful/middleware/store.rb +2 -1
- data/lib/wcc/contentful/model.rb +5 -128
- data/lib/wcc/contentful/model_api.rb +189 -0
- data/lib/wcc/contentful/model_builder.rb +10 -4
- data/lib/wcc/contentful/model_methods.rb +8 -10
- data/lib/wcc/contentful/model_singleton_methods.rb +8 -8
- data/lib/wcc/contentful/rspec.rb +7 -5
- data/lib/wcc/contentful/services.rb +59 -61
- data/lib/wcc/contentful/store/cdn_adapter.rb +7 -1
- data/lib/wcc/contentful/store/factory.rb +2 -6
- data/lib/wcc/contentful/store/memory_store.rb +47 -13
- data/lib/wcc/contentful/store/query.rb +22 -2
- data/lib/wcc/contentful/store/rspec_examples/basic_store.rb +0 -103
- data/lib/wcc/contentful/store/rspec_examples/operators/eq.rb +92 -0
- data/lib/wcc/contentful/store/rspec_examples/operators/in.rb +131 -0
- data/lib/wcc/contentful/store/rspec_examples/operators/ne.rb +77 -0
- data/lib/wcc/contentful/store/rspec_examples/operators/nin.rb +80 -0
- data/lib/wcc/contentful/store/rspec_examples/operators.rb +50 -0
- data/lib/wcc/contentful/store/rspec_examples.rb +2 -0
- data/lib/wcc/contentful/sync_engine.rb +0 -1
- data/lib/wcc/contentful/test/attributes.rb +0 -4
- data/lib/wcc/contentful/test/double.rb +4 -2
- data/lib/wcc/contentful/test/factory.rb +4 -2
- data/lib/wcc/contentful/version.rb +1 -1
- data/lib/wcc/contentful.rb +14 -8
- metadata +117 -37
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3facd27f1d4401d01c3e42dc9ab11d75652cc6859a74c5c74ffb58647b13c28d
|
4
|
+
data.tar.gz: 48072021ae935b5c7d9a684f488dafb3ffa53b94ea153bfeeba7f1b5f5a46806
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b884394e95a3c722c8a3a1327780700f153f9e724c920d9d57e81409d836e5111777f3493b504bfe7b6321a1d83ed95f6fe2613b9f77677393d2caab631caa4e
|
7
|
+
data.tar.gz: 6891f2e8a291d4a72c725ce09a7270681ed8e2f7485d2f68d195f6fc0f55005e07408855f194d1ac130fe3c874c59c3bdc4e5aa87b512b776c37be792dd5e605
|
data/README.md
CHANGED
@@ -14,16 +14,20 @@ Table of Contents:
|
|
14
14
|
2. [Installation](#installation)
|
15
15
|
3. [Configuration](#configure)
|
16
16
|
4. [Usage](#usage)
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
17
|
+
1. [Model API](#wcccontentfulmodel-api)
|
18
|
+
2. [Store API](#store-api)
|
19
|
+
3. [Direct CDN client](#direct-cdn-api-simpleclient)
|
20
|
+
4. [Accessing the APIs](#accessing-the-apis-within-application-code)
|
21
21
|
5. [Architecture](#architecture)
|
22
|
+
1. [Client Layer](#client-layer)
|
23
|
+
2. [Store Layer](#store-layer)
|
24
|
+
3. [Model Layer](#model-layer)
|
22
25
|
6. [Test Helpers](#test-helpers)
|
23
26
|
7. [Advanced Configuration Example](#advanced-configuration-example)
|
24
|
-
8. [
|
25
|
-
9. [
|
26
|
-
10. [
|
27
|
+
8. [Connecting to Multiple Spaces](#connecting-to-multiple-spaces-or-environments)
|
28
|
+
9. [Development](#development)
|
29
|
+
10. [Contributing](#contributing)
|
30
|
+
11. [License](#license)
|
27
31
|
|
28
32
|
|
29
33
|
## Why did you rewrite the Contentful ruby stack?
|
@@ -45,7 +49,7 @@ The wcc-contentful gem enables caching at two levels: the HTTP response using [F
|
|
45
49
|
By default, the contentful.rb gem requires the [HTTP library](https://rubygems.org/gems/http). While simple and straightforward to use, it is not as powerful for caching. We decided to make our client conform to the [Faraday gem's API](https://github.com/lostisland/faraday). If you prefer not to use Faraday, you can choose to supply your own HTTP adapter that "quacks like" Faraday (see the [TyphoeusAdapter](https://github.com/watermarkchurch/wcc-contentful/blob/master/wcc-contentful/lib/wcc/contentful/simple_client/typhoeus_adapter.rb) for one implementation).
|
46
50
|
|
47
51
|
Using Faraday makes it easy to add Middleware. As an example, our flagship Rails app that powers watermark.org uses the following configuration in Production, which provides us with instrumentation through statsd, logging, and caching:
|
48
|
-
```
|
52
|
+
```ruby
|
49
53
|
config.connection = Faraday.new do |builder|
|
50
54
|
builder.use :http_cache,
|
51
55
|
shared_cache: false,
|
@@ -77,7 +81,7 @@ We also took advantage of Rails' naming conventions to automatically infer the c
|
|
77
81
|
|
78
82
|
All our models are automatically generated at startup which improves response times at the expense of initialization time. In addition, our content model registry allows easy definition of custom models in your `app/models` directory to override fields. This plays nice with other gems like algoliasearch-rails, which allows you to declaratively manage your Algolia indexes. Another example from our flagship watermark.org:
|
79
83
|
|
80
|
-
```
|
84
|
+
```ruby
|
81
85
|
class Page < WCC::Contentful::Model::Page
|
82
86
|
include AlgoliaSearch
|
83
87
|
|
@@ -327,6 +331,98 @@ end
|
|
327
331
|
|
328
332
|

|
329
333
|
|
334
|
+
From the bottom up:
|
335
|
+
|
336
|
+
### Client Layer
|
337
|
+
|
338
|
+
The {WCC::Contentful::SimpleClient} provides methods to access the [Contentful
|
339
|
+
Content Delivery API](https://www.contentful.com/developers/docs/references/content-delivery-api/)
|
340
|
+
through your favorite HTTP client gem. The SimpleClient expects
|
341
|
+
an Adapter that conforms to the Faraday interface.
|
342
|
+
|
343
|
+
Creating a SimpleClient to connect using different credentials, or to connect
|
344
|
+
without setting up all the rest of WCC::Contentful, is easy:
|
345
|
+
|
346
|
+
```ruby
|
347
|
+
WCC::Contentful::SimpleClient::Cdn.new(
|
348
|
+
# required
|
349
|
+
access_token: 'xxxx',
|
350
|
+
space: '1234',
|
351
|
+
# optional
|
352
|
+
environment: 'staging', # omit to use master
|
353
|
+
default_locale: '*',
|
354
|
+
rate_limit_wait_timeout: 10,
|
355
|
+
instrumentation: ActiveSupport::Notifications,
|
356
|
+
connection: Faraday.new { |builder| ... },
|
357
|
+
)
|
358
|
+
```
|
359
|
+
|
360
|
+
You can also create a {WCC::Contentful::SimpleClient::Preview} to talk to the
|
361
|
+
Preview API, or a {WCC::Contentful::SimpleClient::Management} to talk to the
|
362
|
+
Management API.
|
363
|
+
|
364
|
+
### Store Layer
|
365
|
+
|
366
|
+
The Store Layer represents the data store where Contentful entries are kept for
|
367
|
+
querying. By default, `WCC::Contentful.init!` creates a {WCC::Contentful::Store::CDNAdapter}
|
368
|
+
which uses a {WCC::Contentful::SimpleClient::Cdn} instance to query entries from
|
369
|
+
the [Contentful Content Delivery API](https://www.contentful.com/developers/docs/references/content-delivery-api/). You can also query entries from another
|
370
|
+
source like Postgres or an in-memory hash if your data is small enough.
|
371
|
+
|
372
|
+
You can also implement your own store if you want! The gem contains a suite of
|
373
|
+
RSpec shared examples that give you a baseline for implementing your own store.
|
374
|
+
In your RSpec suite:
|
375
|
+
```ruby
|
376
|
+
# frozen_string_literal: true
|
377
|
+
|
378
|
+
require 'my_store'
|
379
|
+
require 'wcc/contentful/store/rspec_examples'
|
380
|
+
|
381
|
+
RSpec.describe MyStore do
|
382
|
+
it_behaves_like 'contentful store', {
|
383
|
+
# Set which store features your store implements.
|
384
|
+
nested_queries: true, # Does your store implement JOINs?
|
385
|
+
include_param: true # Does your store resolve links when given the :include option?
|
386
|
+
}
|
387
|
+
```
|
388
|
+
|
389
|
+
The store is kept up-to-date by the {WCC::Contentful::SyncEngine}. The `SyncEngine#next` methodcalls the `#index` method on the configured store in order to update
|
390
|
+
it with the latest data via the [Contentful Sync API](https://www.contentful.com/developers/docs/references/content-delivery-api/#/reference/synchronization). For example,
|
391
|
+
the {WCC::Contentful::Store::MemoryStore} uses this to update the hash with the
|
392
|
+
newest version of an entry, or delete an entry out of the hash.
|
393
|
+
|
394
|
+
#### Store Middleware
|
395
|
+
|
396
|
+
The store layer is made up of a base store (which implements {WCC::Contentful::Store::Interface}),
|
397
|
+
and optional middleware. The middleware
|
398
|
+
allows custom transformation of received entries via the `#select` and `#transform`
|
399
|
+
methods. To create your own middleware simply include {WCC::Contentful::Middleware::Store}
|
400
|
+
and implement those methods, then call `use` when configuring the store:
|
401
|
+
|
402
|
+
```ruby
|
403
|
+
config.store :direct do
|
404
|
+
use MyMiddleware, param1: 'xxx'
|
405
|
+
end
|
406
|
+
```
|
407
|
+
|
408
|
+
The most useful middleware is the {WCC::Contentful::Middleware::Store::CachingMiddleware},
|
409
|
+
which enables `:lazy_sync` mode (see {WCC::Contentful::Configuration#store})
|
410
|
+
|
411
|
+
### Model Layer
|
412
|
+
|
413
|
+
This is the global top layer where your Rails app looks up content similarly to
|
414
|
+
ActiveModel. The models are namespaced under the root class {WCC::Contentful::Model}.
|
415
|
+
Each model's implementation of `.find`, `.find_by`, and `.find_all` simply call
|
416
|
+
into the configured Store.
|
417
|
+
|
418
|
+
The main benefit of the Model layer is lazy link resolution. When a model's
|
419
|
+
property is accessed, if that property is a link that has not been resolved
|
420
|
+
yet (for example using the `include: n` parameter on `.find_by`), the model
|
421
|
+
will automatically call `#find` on the store to resolve that linked entry.
|
422
|
+
|
423
|
+
Note that this can easily result in lots of CDN calls to Contentful! To optimize
|
424
|
+
this you should use the `include` parameter and/or use a different store.
|
425
|
+
|
330
426
|
## Test Helpers
|
331
427
|
|
332
428
|
To use the test helpers, include the following in your rails_helper.rb:
|
@@ -460,6 +556,75 @@ rake wcc_contentful:download_schema
|
|
460
556
|
All configuration options can be found [in the rubydoc](https://www.rubydoc.info/gems/wcc-contentful/WCC/Contentful/Configuration) under
|
461
557
|
{WCC::Contentful::Configuration}
|
462
558
|
|
559
|
+
## Connecting to multiple spaces or environments
|
560
|
+
|
561
|
+
When initializing the WCC::Contentful gem using `WCC::Contentful.init!`, the gem will by default connect to the single
|
562
|
+
Contentful space that you specify in the `WCC::Contentful.configure` step. However the gem is also capable of connecting
|
563
|
+
to multiple spaces within the same ruby process! You just have to create and initialize a namespace.
|
564
|
+
|
565
|
+
The {WCC::Contentful::ModelAPI} concern makes this straightforward. Start by creating your Namespace
|
566
|
+
and including the concern:
|
567
|
+
```ruby
|
568
|
+
# app/models/my_second_space.rb
|
569
|
+
class MySecondSpace
|
570
|
+
include WCC::Contentful::ModelAPI
|
571
|
+
end
|
572
|
+
|
573
|
+
# app/models/other_page.rb
|
574
|
+
class OtherPage < MySecondSpace::Page
|
575
|
+
end
|
576
|
+
```
|
577
|
+
|
578
|
+
Then configure it in an initializer:
|
579
|
+
```ruby
|
580
|
+
# config/initializers/my_second_space.rb
|
581
|
+
MySecondSpace.configure do |config|
|
582
|
+
# Make sure to point to a different schema file from your first space!
|
583
|
+
config.schema_file = Rails.root.join('db/second-contentful-schema.json')
|
584
|
+
|
585
|
+
config.access_token = ENV['SECOND_CONTENTFUL_ACCESS_TOKEN']
|
586
|
+
config.preview_token = ENV['SECOND_CONTENTFUL_PREVIEW_ACCESS_TOKEN']
|
587
|
+
config.space = ENV['SECOND_CONTENTFUL_SPACE_ID']
|
588
|
+
config.environment = ENV['CONTENTFUL_ENVIRONMENT']
|
589
|
+
end
|
590
|
+
```
|
591
|
+
|
592
|
+
Finally, use it:
|
593
|
+
```ruby
|
594
|
+
OtherPage.find('1234')
|
595
|
+
# GET https://cdn.contentful.com/spaces/other-space/environments/other-env/entries/1234
|
596
|
+
# => #<OtherPage:0x0000000005c71a78 @created_at=2018-04-16 18:41:17 UTC...>
|
597
|
+
|
598
|
+
Page.find('1234')
|
599
|
+
# GET https://cdn.contentful.com/spaces/first-space/environments/first-env/entries/1234
|
600
|
+
# => #<Page:0x0000000001271b70 @created_at=2018-04-15 12:02:14 UTC...>
|
601
|
+
```
|
602
|
+
|
603
|
+
The ModelAPI defines a second stack of services that you can access for lower level connections:
|
604
|
+
```ruby
|
605
|
+
store = MySecondSpace.services.store
|
606
|
+
# => #<WCC::Contentful::Store::CDNAdapter:0x00007f888edac118
|
607
|
+
client = MySecondSpace.services.client
|
608
|
+
# => #<WCC::Contentful::SimpleClient::Cdn:0x00007f88942a8888
|
609
|
+
preview_client = MySecondSpace.services.preview_client
|
610
|
+
# => #<WCC::Contentful::SimpleClient::Preview:0x00007f888ccafa00
|
611
|
+
sync_engine = MySecondSpace.services.sync_engine
|
612
|
+
# => #<WCC::Contentful::SyncEngine:0x00007f88960b6b40
|
613
|
+
```
|
614
|
+
Note that the above services are not accessible on {WCC::Contentful::Services.instance}
|
615
|
+
or via the {WCC::Contentful::ServiceAccessors}.
|
616
|
+
|
617
|
+
### Using a sync store with a second space
|
618
|
+
|
619
|
+
If you use something other than the CDNAdapter with your second space, you will
|
620
|
+
need to find a way to trigger `MySecondSpace.services.sync_engine.next` to keep
|
621
|
+
it up-to-date. The {WCC::Contentful::Engine} will only manage the global SyncEngine
|
622
|
+
configured by the global {WCC::Contentful.configure}.
|
623
|
+
|
624
|
+
The easiest way to do this is to set up your own Rails route to respond to Contentful
|
625
|
+
webhooks and then configure the second Contentful space to send webhooks to this route.
|
626
|
+
You could also do this by triggering it periodically in a background job using
|
627
|
+
Sidekiq and [sidekiq-scheduler](https://github.com/Moove-it/sidekiq-scheduler).
|
463
628
|
|
464
629
|
## Development
|
465
630
|
|
@@ -13,18 +13,17 @@ module WCC::Contentful
|
|
13
13
|
client = WCC::Contentful::SimpleClient::Management.new(
|
14
14
|
args
|
15
15
|
)
|
16
|
-
enable_webhook(client, args.slice(:
|
16
|
+
enable_webhook(client, args.slice(:receive_url, :webhook_username, :webhook_password))
|
17
17
|
end
|
18
18
|
|
19
|
-
def enable_webhook(client,
|
20
|
-
|
21
|
-
webhook = client.webhook_definitions.items.find { |w| w['url'] == expected_url }
|
19
|
+
def enable_webhook(client, receive_url:, webhook_username: nil, webhook_password: nil)
|
20
|
+
webhook = client.webhook_definitions.items.find { |w| w['url'] == receive_url }
|
22
21
|
logger.debug "existing webhook: #{webhook.inspect}" if webhook
|
23
22
|
return if webhook
|
24
23
|
|
25
24
|
body = {
|
26
25
|
'name' => 'WCC::Contentful webhook',
|
27
|
-
'url' =>
|
26
|
+
'url' => receive_url,
|
28
27
|
'topics' => [
|
29
28
|
'*.publish',
|
30
29
|
'*.unpublish'
|
@@ -50,13 +49,14 @@ module WCC::Contentful
|
|
50
49
|
|
51
50
|
{
|
52
51
|
management_token: config.management_token,
|
53
|
-
app_url: config.app_url,
|
54
52
|
space: config.space,
|
55
53
|
environment: config.environment,
|
56
54
|
default_locale: config.default_locale,
|
57
55
|
connection: config.connection,
|
58
56
|
webhook_username: config.webhook_username,
|
59
|
-
webhook_password: config.webhook_password
|
57
|
+
webhook_password: config.webhook_password,
|
58
|
+
|
59
|
+
receive_url: URI.join(config.app_url, 'webhook/receive').to_s
|
60
60
|
}
|
61
61
|
end
|
62
62
|
|
@@ -11,13 +11,31 @@ module WCC::Contentful
|
|
11
11
|
.name.parameterize.split('-').reverse.join('.')
|
12
12
|
end
|
13
13
|
|
14
|
+
attr_writer :_instrumentation
|
15
|
+
def _instrumentation
|
16
|
+
# look for per-instance instrumentation then try class level
|
17
|
+
@_instrumentation || self.class._instrumentation
|
18
|
+
end
|
19
|
+
|
14
20
|
included do
|
15
21
|
protected
|
16
22
|
|
17
23
|
def _instrument(name, payload = {}, &block)
|
18
24
|
name += _instrumentation_event_prefix
|
19
|
-
(
|
20
|
-
|
25
|
+
_instrumentation&.instrument(name, payload, &block)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class_methods do
|
30
|
+
attr_writer :_instrumentation
|
31
|
+
|
32
|
+
def _instrumentation
|
33
|
+
@_instrumentation ||
|
34
|
+
# try looking up the class heierarchy
|
35
|
+
superclass.try(:_instrumentation) ||
|
36
|
+
# default to global
|
37
|
+
WCC::Contentful::Services.instance&.instrumentation ||
|
38
|
+
ActiveSupport::Notifications
|
21
39
|
end
|
22
40
|
end
|
23
41
|
|
@@ -5,32 +5,21 @@
|
|
5
5
|
# But you can use it too!
|
6
6
|
class WCC::Contentful::LinkVisitor
|
7
7
|
attr_reader :entry
|
8
|
-
attr_reader :type
|
9
8
|
attr_reader :fields
|
10
9
|
attr_reader :depth
|
11
10
|
|
12
11
|
# @param [Hash] entry The entry hash (resolved or unresolved) to walk
|
13
|
-
# @param [Array<
|
14
|
-
#
|
12
|
+
# @param [Array<Symbol>] The type of fields to select from the entry tree.
|
13
|
+
# Must be one of `:Link`, `:Entry`, `:Asset`.
|
15
14
|
# @param [Fixnum] depth (optional) How far to walk down the tree of links. Be careful of
|
16
15
|
# recursive trees!
|
17
|
-
# @example
|
18
|
-
# entry = store.find_by(id: id, include: 3)
|
19
|
-
# WCC::Contentful::LinkVisitor.new(entry, 'slug', depth: 3)
|
20
|
-
# .map { |slug| 'https://mirror-site' + slug }
|
21
16
|
def initialize(entry, *fields, depth: 0)
|
22
|
-
unless entry.is_a?(Hash) && entry.dig('sys', '
|
17
|
+
unless entry.is_a?(Hash) && entry.dig('sys', 'type') == 'Entry'
|
23
18
|
raise ArgumentError, "Please provide an entry as a hash value (got #{entry})"
|
24
19
|
end
|
25
|
-
unless ct = entry.dig('sys', 'contentType', 'sys', 'id')
|
26
|
-
raise ArgumentError, 'Entry has no content type!'
|
27
|
-
end
|
28
|
-
|
29
|
-
@type = WCC::Contentful.types[ct]
|
30
|
-
raise ArgumentError, "Unknown content type '#{ct}'" unless @type
|
31
20
|
|
32
21
|
@entry = entry
|
33
|
-
@fields = fields
|
22
|
+
@fields = fields.map(&:to_s)
|
34
23
|
@depth = depth
|
35
24
|
end
|
36
25
|
|
@@ -42,7 +31,7 @@ class WCC::Contentful::LinkVisitor
|
|
42
31
|
# @returns nil
|
43
32
|
def each(&block)
|
44
33
|
_each do |val, field, locale, index|
|
45
|
-
yield(val, field, locale, index) if should_yield_field?(field)
|
34
|
+
yield(val, field, locale, index) if should_yield_field?(field, val)
|
46
35
|
|
47
36
|
next unless should_walk_link?(field, val)
|
48
37
|
|
@@ -54,7 +43,7 @@ class WCC::Contentful::LinkVisitor
|
|
54
43
|
|
55
44
|
def map!(&block)
|
56
45
|
_each do |val, field, locale, index|
|
57
|
-
if should_yield_field?(field)
|
46
|
+
if should_yield_field?(field, val)
|
58
47
|
val = yield(val, field, locale, index)
|
59
48
|
set_field(field, locale, index, val)
|
60
49
|
end
|
@@ -70,15 +59,15 @@ class WCC::Contentful::LinkVisitor
|
|
70
59
|
private
|
71
60
|
|
72
61
|
def _each(&block)
|
73
|
-
|
74
|
-
each_field(
|
62
|
+
(entry['fields'] || {}).each do |(k, _v)|
|
63
|
+
each_field(k, &block)
|
75
64
|
end
|
76
65
|
end
|
77
66
|
|
78
67
|
def each_field(field)
|
79
68
|
each_locale(field) do |val, locale|
|
80
|
-
if
|
81
|
-
val
|
69
|
+
if val&.is_a?(Array)
|
70
|
+
val.each_with_index do |v, index|
|
82
71
|
yield(v, field, locale, index) unless v.nil?
|
83
72
|
end
|
84
73
|
else
|
@@ -88,7 +77,7 @@ class WCC::Contentful::LinkVisitor
|
|
88
77
|
end
|
89
78
|
|
90
79
|
def each_locale(field)
|
91
|
-
raw_value = entry.dig('fields', field
|
80
|
+
raw_value = entry.dig('fields', field)
|
92
81
|
if locale = entry.dig('sys', 'locale')
|
93
82
|
if raw_value.is_a?(Hash) && raw_value[locale]
|
94
83
|
# it's a locale=* entry, but they've added sys.locale to those now
|
@@ -102,21 +91,28 @@ class WCC::Contentful::LinkVisitor
|
|
102
91
|
end
|
103
92
|
end
|
104
93
|
|
105
|
-
def should_yield_field?(
|
106
|
-
|
94
|
+
def should_yield_field?(_field, value)
|
95
|
+
return true if fields.empty?
|
96
|
+
|
97
|
+
case value
|
98
|
+
when Hash
|
99
|
+
fields.include?(value.dig('sys', 'type'))
|
100
|
+
when Array
|
101
|
+
value.any? { |v| v.is_a?(Hash) && fields.include?(v.dig('sys', 'type')) }
|
102
|
+
end
|
107
103
|
end
|
108
104
|
|
109
|
-
def should_walk_link?(
|
110
|
-
dep > 0 &&
|
105
|
+
def should_walk_link?(_field, val, dep = depth)
|
106
|
+
dep > 0 && val.is_a?(Hash) && val.dig('sys', 'type') == 'Entry'
|
111
107
|
end
|
112
108
|
|
113
109
|
def set_field(field, locale, index, val)
|
114
|
-
current_field = (entry['fields'][field
|
110
|
+
current_field = (entry['fields'][field] ||= {})
|
115
111
|
|
116
|
-
if
|
117
|
-
(current_field[locale] ||= [])[index] = val
|
118
|
-
else
|
112
|
+
if index.nil?
|
119
113
|
current_field[locale] = val
|
114
|
+
else
|
115
|
+
(current_field[locale] ||= [])[index] = val
|
120
116
|
end
|
121
117
|
end
|
122
118
|
end
|
@@ -55,7 +55,8 @@ module WCC::Contentful::Middleware::Store
|
|
55
55
|
def resolve_includes(entry, depth)
|
56
56
|
return entry unless entry && depth && depth > 0
|
57
57
|
|
58
|
-
|
58
|
+
# We only care about entries (see #resolved_link?)
|
59
|
+
WCC::Contentful::LinkVisitor.new(entry, :Entry, depth: depth).map! do |val|
|
59
60
|
resolve_link(val)
|
60
61
|
end
|
61
62
|
end
|
data/lib/wcc/contentful/model.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative './model_api'
|
4
|
+
|
3
5
|
# This is the top layer of the WCC::Contentful gem. It exposes an API by which
|
4
6
|
# you can query for data from Contentful. The API is only accessible after calling
|
5
7
|
# WCC::Contentful.init!
|
@@ -37,138 +39,13 @@
|
|
37
39
|
#
|
38
40
|
# @api Model
|
39
41
|
class WCC::Contentful::Model
|
40
|
-
|
41
|
-
|
42
|
-
# The Model base class maintains a registry which is best expressed as a
|
43
|
-
# class var.
|
44
|
-
# rubocop:disable Style/ClassVars
|
42
|
+
include WCC::Contentful::ModelAPI
|
45
43
|
|
46
44
|
class << self
|
47
|
-
include WCC::Contentful::ServiceAccessors
|
48
|
-
|
49
45
|
def const_missing(name)
|
46
|
+
type = WCC::Contentful::Helpers.content_type_from_constant(name)
|
50
47
|
raise WCC::Contentful::ContentTypeNotFoundError,
|
51
|
-
"Content type '#{
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
@@registry = {}
|
56
|
-
|
57
|
-
def self.store(preview = false)
|
58
|
-
if preview
|
59
|
-
if preview_store.nil?
|
60
|
-
raise ArgumentError,
|
61
|
-
'You must include a contentful preview token in your WCC::Contentful.configure block'
|
62
|
-
end
|
63
|
-
preview_store
|
64
|
-
else
|
65
|
-
super()
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
# Finds an Entry or Asset by ID in the configured contentful space
|
70
|
-
# and returns an initialized instance of the appropriate model type.
|
71
|
-
#
|
72
|
-
# Makes use of the {WCC::Contentful::Services#store configured store}
|
73
|
-
# to access the Contentful CDN.
|
74
|
-
def self.find(id, options: nil)
|
75
|
-
options ||= {}
|
76
|
-
raw = store(options[:preview])
|
77
|
-
.find(id, options.except(*WCC::Contentful::ModelMethods::MODEL_LAYER_CONTEXT_KEYS))
|
78
|
-
|
79
|
-
new_from_raw(raw, options) if raw.present?
|
80
|
-
end
|
81
|
-
|
82
|
-
# Creates a new initialized instance of the appropriate model type for the
|
83
|
-
# given raw value. The raw value must be the same format as returned from one
|
84
|
-
# of the stores for a given object.
|
85
|
-
def self.new_from_raw(raw, context = nil)
|
86
|
-
content_type = content_type_from_raw(raw)
|
87
|
-
const = resolve_constant(content_type)
|
88
|
-
const.new(raw, context)
|
89
|
-
end
|
90
|
-
|
91
|
-
# Accepts a content type ID as a string and returns the Ruby constant
|
92
|
-
# stored in the registry that represents this content type.
|
93
|
-
def self.resolve_constant(content_type)
|
94
|
-
raise ArgumentError, 'content_type cannot be nil' unless content_type
|
95
|
-
|
96
|
-
const = @@registry[content_type]
|
97
|
-
return const if const
|
98
|
-
|
99
|
-
const_name = constant_from_content_type(content_type).to_s
|
100
|
-
begin
|
101
|
-
# The app may have defined a model and we haven't loaded it yet
|
102
|
-
const = Object.const_missing(const_name)
|
103
|
-
return const if const && const < WCC::Contentful::Model
|
104
|
-
rescue NameError => e
|
105
|
-
raise e unless e.message =~ /uninitialized constant #{const_name}/
|
106
|
-
|
107
|
-
nil
|
108
|
-
end
|
109
|
-
|
110
|
-
# Autoloading couldn't find their model - we'll register our own.
|
111
|
-
const = WCC::Contentful::Model.const_get(constant_from_content_type(content_type))
|
112
|
-
register_for_content_type(content_type, klass: const)
|
113
|
-
end
|
114
|
-
|
115
|
-
# Registers a class constant to be instantiated when resolving an instance
|
116
|
-
# of the given content type. This automatically happens for the first subclass
|
117
|
-
# of a generated model type, example:
|
118
|
-
#
|
119
|
-
# class MyMenu < WCC::Contentful::Model::Menu
|
120
|
-
# end
|
121
|
-
#
|
122
|
-
# In the above case, instances of MyMenu will be instantiated whenever a 'menu'
|
123
|
-
# content type is resolved.
|
124
|
-
# The mapping can be made explicit with the optional parameters. Example:
|
125
|
-
#
|
126
|
-
# class MyFoo < WCC::Contentful::Model::Foo
|
127
|
-
# register_for_content_type 'bar' # MyFoo is assumed
|
128
|
-
# end
|
129
|
-
#
|
130
|
-
# # in initializers/wcc_contentful.rb
|
131
|
-
# WCC::Contentful::Model.register_for_content_type('bar', klass: MyFoo)
|
132
|
-
def self.register_for_content_type(content_type = nil, klass: nil)
|
133
|
-
klass ||= self
|
134
|
-
raise ArgumentError, "#{klass} must be a class constant!" unless klass.respond_to?(:new)
|
135
|
-
|
136
|
-
content_type ||= content_type_from_constant(klass)
|
137
|
-
|
138
|
-
@@registry[content_type] = klass
|
139
|
-
end
|
140
|
-
|
141
|
-
# Returns the current registry of content type names to constants.
|
142
|
-
def self.registry
|
143
|
-
return {} unless @@registry
|
144
|
-
|
145
|
-
@@registry.dup.freeze
|
146
|
-
end
|
147
|
-
|
148
|
-
def self.reload!
|
149
|
-
registry = self.registry
|
150
|
-
registry.each do |(content_type, klass)|
|
151
|
-
const_name = klass.name
|
152
|
-
begin
|
153
|
-
const = Object.const_missing(const_name)
|
154
|
-
register_for_content_type(content_type, klass: const) if const
|
155
|
-
rescue NameError => e
|
156
|
-
msg = "Error when reloading constant #{const_name} - #{e}"
|
157
|
-
if defined?(Rails) && Rails.logger
|
158
|
-
Rails.logger.error msg
|
159
|
-
else
|
160
|
-
puts msg
|
161
|
-
end
|
162
|
-
end
|
48
|
+
"Content type '#{type}' does not exist in the space"
|
163
49
|
end
|
164
50
|
end
|
165
|
-
|
166
|
-
# Checks if a content type has already been registered to a class and returns
|
167
|
-
# that class. If nil, the generated WCC::Contentful::Model::{content_type} class
|
168
|
-
# will be resolved for this content type.
|
169
|
-
def self.registered?(content_type)
|
170
|
-
@@registry[content_type]
|
171
|
-
end
|
172
51
|
end
|
173
|
-
|
174
|
-
# rubocop:enable Style/ClassVars
|