wcc-contentful 1.1.2 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3facd27f1d4401d01c3e42dc9ab11d75652cc6859a74c5c74ffb58647b13c28d
4
- data.tar.gz: 48072021ae935b5c7d9a684f488dafb3ffa53b94ea153bfeeba7f1b5f5a46806
3
+ metadata.gz: f05ea585d54f7ca83c96e018021f88776ebb148cc40b7d719d6199b3a3cd9d0c
4
+ data.tar.gz: 0da490d12beb9938ec51c0ac40e140420966667b72afebc5c9895cf197308ea8
5
5
  SHA512:
6
- metadata.gz: b884394e95a3c722c8a3a1327780700f153f9e724c920d9d57e81409d836e5111777f3493b504bfe7b6321a1d83ed95f6fe2613b9f77677393d2caab631caa4e
7
- data.tar.gz: 6891f2e8a291d4a72c725ce09a7270681ed8e2f7485d2f68d195f6fc0f55005e07408855f194d1ac130fe3c874c59c3bdc4e5aa87b512b776c37be792dd5e605
6
+ metadata.gz: 979360a35356b041dbe0f8bbd4f0e5bfcaf91909ccc567322ceef8493de25300ea09a081e755142fe23bf9cfd5ea86ca7f919d3218d5dec44a98bcab5921af7d
7
+ data.tar.gz: 9e5f29e70fdcbccffd72aeb8b896e3518d2236cd699704289e8a3fdf1392201dc49671bab0eb83948e79ee079366de90b46add4ce2027e065db2da28b2be4e85
@@ -11,7 +11,7 @@ module WCC::Contentful
11
11
  args = default_configuration.merge!(args)
12
12
 
13
13
  client = WCC::Contentful::SimpleClient::Management.new(
14
- args
14
+ **args
15
15
  )
16
16
  enable_webhook(client, args.slice(:receive_url, :webhook_username, :webhook_password))
17
17
  end
@@ -64,13 +64,13 @@ module WCC::Contentful::ActiveRecordShim
64
64
  }
65
65
  }
66
66
 
67
- find_all(filter).each_slice(batch_size, &block)
67
+ find_all(**filter).each_slice(batch_size, &block)
68
68
  end
69
69
 
70
70
  def where(**conditions)
71
71
  # TODO: return a Query object that implements more of the ActiveRecord query interface
72
72
  # https://guides.rubyonrails.org/active_record_querying.html#conditions
73
- find_all(conditions)
73
+ find_all(**conditions)
74
74
  end
75
75
  end
76
76
  end
@@ -137,6 +137,8 @@ module WCC::Contentful
137
137
  :Json
138
138
  when 'Location'
139
139
  :Coordinates
140
+ when 'RichText'
141
+ :RichText
140
142
  when 'Array'
141
143
  find_field_type(field.try(:items) || field['items'])
142
144
  when 'Link'
@@ -7,10 +7,10 @@ module WCC::Contentful::Event
7
7
 
8
8
  # Creates an Event out of a raw value received by a webhook or given from
9
9
  # the Contentful Sync API.
10
- def self.from_raw(raw, context = nil)
10
+ def self.from_raw(raw, context = nil, source: nil)
11
11
  const = Registry.instance.get(raw.dig('sys', 'type'))
12
12
 
13
- const.new(raw, context)
13
+ const.new(raw, context, source: source)
14
14
  end
15
15
 
16
16
  class Registry
@@ -134,12 +134,6 @@ class WCC::Contentful::Event::SyncComplete
134
134
  include WCC::Contentful::Event
135
135
 
136
136
  def initialize(items, context = nil, source: nil)
137
- items =
138
- items.map do |item|
139
- next item if item.is_a? WCC::Contentful::Event
140
-
141
- WCC::Contentful::Event.from_raw(item, context, source: source)
142
- end
143
137
  @items = items.freeze
144
138
  @source = source
145
139
  @sys = WCC::Contentful::Sys.new(
@@ -103,6 +103,7 @@ module WCC::Contentful
103
103
  Boolean
104
104
  Json
105
105
  Coordinates
106
+ RichText
106
107
  Link
107
108
  Asset
108
109
  ].freeze
@@ -20,7 +20,7 @@ module WCC::Contentful::Middleware::Store
20
20
  event = 'miss'
21
21
  # if it's not a contentful ID don't hit the API.
22
22
  # Store a nil object if we can't find the object on the CDN.
23
- (store.find(key, options) || nil_obj(key)) if key =~ /^\w+$/
23
+ (store.find(key, **options) || nil_obj(key)) if key =~ /^\w+$/
24
24
  end
25
25
  _instrument(event, key: key, options: options)
26
26
 
@@ -68,7 +68,7 @@ module WCC::Contentful::ModelAPI
68
68
  store = options[:preview] ? services.preview_store : services.store
69
69
  raw =
70
70
  _instrumentation.instrument 'find.model.contentful.wcc', id: id, options: options do
71
- store.find(id, options.except(*WCC::Contentful::ModelMethods::MODEL_LAYER_CONTEXT_KEYS))
71
+ store.find(id, **options.except(*WCC::Contentful::ModelMethods::MODEL_LAYER_CONTEXT_KEYS))
72
72
  end
73
73
 
74
74
  new_from_raw(raw, options) if raw.present?
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative './link'
4
4
  require_relative './sys'
5
+ require_relative './rich_text'
5
6
 
6
7
  module WCC::Contentful
7
8
  class ModelBuilder
@@ -95,6 +96,8 @@ module WCC::Contentful
95
96
  #
96
97
  # when :DateTime
97
98
  # raw_value = Time.parse(raw_value).localtime
99
+ when :RichText
100
+ raw_value = WCC::Contentful::RichText.tokenize(raw_value)
98
101
  when :Int
99
102
  raw_value = Integer(raw_value)
100
103
  when :Float
@@ -16,7 +16,7 @@ module WCC::Contentful::ModelSingletonMethods
16
16
  raw =
17
17
  _instrumentation.instrument 'find.model.contentful.wcc',
18
18
  content_type: content_type, id: id, options: options do
19
- store.find(id, { hint: type }.merge!(options.except(:preview)))
19
+ store.find(id, **{ hint: type }.merge!(options.except(:preview)))
20
20
  end
21
21
  new(raw, options) if raw.present?
22
22
  end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WCC::Contentful::RichText
4
+ module Node
5
+ extend ActiveSupport::Concern
6
+
7
+ def keys
8
+ members.map(&:to_s)
9
+ end
10
+
11
+ included do
12
+ include Enumerable
13
+
14
+ alias_method :node_type, :nodeType
15
+
16
+ # Make the nodes read-only
17
+ undef_method :[]=
18
+ members.each do |member|
19
+ undef_method("#{member}=")
20
+ end
21
+
22
+ # Override each so it has a Hash-like interface rather than Struct-like.
23
+ # The goal being to mimic a JSON-parsed hash representation of the raw
24
+ def each
25
+ members.map do |key|
26
+ tuple = [key.to_s, self.[](key)]
27
+ yield tuple if block_given?
28
+ tuple
29
+ end
30
+ end
31
+ end
32
+
33
+ class_methods do
34
+ # Default value for node_type covers most cases
35
+ def node_type
36
+ name.demodulize.underscore.dasherize
37
+ end
38
+
39
+ def tokenize(raw, context = nil)
40
+ unless raw['nodeType'] == node_type
41
+ raise ArgumentError, "Expected '#{node_type}', got '#{raw['nodeType']}'"
42
+ end
43
+
44
+ values =
45
+ members.map do |symbol|
46
+ val = raw[symbol.to_s]
47
+
48
+ case symbol
49
+ when :content
50
+ WCC::Contentful::RichText.tokenize(val, context)
51
+ # when :data
52
+ # TODO: resolve links...
53
+ else
54
+ val
55
+ end
56
+ end
57
+
58
+ new(*values)
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './rich_text/node'
4
+
5
+ ##
6
+ # This module contains a number of structs representing nodes in a Contentful
7
+ # rich text field. When the Model layer parses a Rich Text field from
8
+ # Contentful, it is turned into a WCC::Contentful::RichText::Document node.
9
+ # The {WCC::Contentful::RichText::Document#content content} method of this
10
+ # node is an Array containing paragraph, blockquote, entry, and other nodes.
11
+ #
12
+ # The various structs in the RichText object model are designed to mimic the
13
+ # Hash interface, so that the indexing operator `#[]` and the `#dig` method
14
+ # can be used to traverse the data. The data can also be accessed by the
15
+ # attribute reader methods defined on the structs. Both of these are considered
16
+ # part of the public API of the model and will not change.
17
+ #
18
+ # In a future release we plan to implement automatic link resolution. When that
19
+ # happens, the `.data` attribute of embedded entries and assets will return a
20
+ # new class that is able to resolve the `.target` automatically into a full
21
+ # entry or asset. This future class will still respect the hash accessor methods
22
+ # `#[]`, `#dig`, `#keys`, and `#each`, so it is safe to use those.
23
+ module WCC::Contentful::RichText
24
+ ##
25
+ # Recursively converts a raw JSON-parsed hash into the RichText object model.
26
+ def self.tokenize(raw, context = nil)
27
+ return unless raw
28
+ return raw.map { |c| tokenize(c, context) } if raw.is_a?(Array)
29
+
30
+ klass =
31
+ case raw['nodeType']
32
+ when 'document'
33
+ Document
34
+ when 'paragraph'
35
+ Paragraph
36
+ when 'blockquote'
37
+ Blockquote
38
+ when 'text'
39
+ Text
40
+ when 'embedded-entry-inline'
41
+ EmbeddedEntryInline
42
+ when 'embedded-entry-block'
43
+ EmbeddedEntryBlock
44
+ when 'embedded-asset-block'
45
+ EmbeddedAssetBlock
46
+ when /heading\-(\d+)/
47
+ size = Regexp.last_match(1)
48
+ const_get("Heading#{size}")
49
+ else
50
+ Unknown
51
+ end
52
+
53
+ klass.tokenize(raw, context)
54
+ end
55
+
56
+ Document =
57
+ Struct.new(:nodeType, :data, :content) do
58
+ include WCC::Contentful::RichText::Node
59
+ end
60
+
61
+ Paragraph =
62
+ Struct.new(:nodeType, :data, :content) do
63
+ include WCC::Contentful::RichText::Node
64
+ end
65
+
66
+ Blockquote =
67
+ Struct.new(:nodeType, :data, :content) do
68
+ include WCC::Contentful::RichText::Node
69
+ end
70
+
71
+ Text =
72
+ Struct.new(:nodeType, :value, :marks, :data) do
73
+ include WCC::Contentful::RichText::Node
74
+ end
75
+
76
+ EmbeddedEntryInline =
77
+ Struct.new(:nodeType, :data, :content) do
78
+ include WCC::Contentful::RichText::Node
79
+ end
80
+
81
+ EmbeddedEntryBlock =
82
+ Struct.new(:nodeType, :data, :content) do
83
+ include WCC::Contentful::RichText::Node
84
+ end
85
+
86
+ EmbeddedAssetBlock =
87
+ Struct.new(:nodeType, :data, :content) do
88
+ include WCC::Contentful::RichText::Node
89
+ end
90
+
91
+ (1..5).each do |i|
92
+ struct =
93
+ Struct.new(:nodeType, :data, :content) do
94
+ include WCC::Contentful::RichText::Node
95
+ end
96
+ sz = i
97
+ struct.define_singleton_method(:node_type) { "heading-#{sz}" }
98
+ const_set("Heading#{sz}", struct)
99
+ end
100
+
101
+ Unknown =
102
+ Struct.new(:nodeType, :data, :content) do
103
+ include WCC::Contentful::RichText::Node
104
+ end
105
+ end
@@ -49,7 +49,6 @@ class WCC::Contentful::SimpleClient
49
49
 
50
50
  def next_page
51
51
  return unless next_page?
52
- return @next_page if @next_page
53
52
 
54
53
  query = (@request[:query] || {}).merge({
55
54
  skip: page_items.length + skip
@@ -58,7 +57,7 @@ class WCC::Contentful::SimpleClient
58
57
  _instrument 'page', url: @request[:url], query: query do
59
58
  @client.get(@request[:url], query)
60
59
  end
61
- @next_page = np.assert_ok!
60
+ np.assert_ok!
62
61
  end
63
62
 
64
63
  def initialize(client, request, raw_response)
@@ -77,16 +76,7 @@ class WCC::Contentful::SimpleClient
77
76
  def each_page(&block)
78
77
  raise ArgumentError, 'Not a collection response' unless page_items
79
78
 
80
- ret =
81
- Enumerator.new do |y|
82
- y << self
83
-
84
- if next_page?
85
- next_page.each_page.each do |page|
86
- y << page
87
- end
88
- end
89
- end
79
+ ret = PaginatingEnumerable.new(self)
90
80
 
91
81
  if block_given?
92
82
  ret.map(&block)
@@ -118,11 +108,6 @@ class WCC::Contentful::SimpleClient
118
108
  raw.dig('includes')&.each_with_object({}) do |(_t, entries), h|
119
109
  entries.each { |e| h[e.dig('sys', 'id')] = e }
120
110
  end || {}
121
-
122
- return @includes unless @next_page
123
-
124
- # This could be more efficient - maybe not worth worrying about
125
- @includes.merge(@next_page.includes)
126
111
  end
127
112
  end
128
113
 
@@ -144,8 +129,8 @@ class WCC::Contentful::SimpleClient
144
129
  @client.get(url)
145
130
  end
146
131
 
147
- @next_page ||= SyncResponse.new(next_page)
148
- @next_page.assert_ok!
132
+ next_page = SyncResponse.new(next_page)
133
+ next_page.assert_ok!
149
134
  end
150
135
 
151
136
  def next_sync_token
@@ -160,16 +145,7 @@ class WCC::Contentful::SimpleClient
160
145
  def each_page
161
146
  raise ArgumentError, 'Not a collection response' unless page_items
162
147
 
163
- ret =
164
- Enumerator.new do |y|
165
- y << self
166
-
167
- if next_page?
168
- next_page.each_page.each do |page|
169
- y << page
170
- end
171
- end
172
- end
148
+ ret = PaginatingEnumerable.new(self)
173
149
 
174
150
  if block_given?
175
151
  ret.map(&block)
@@ -190,6 +166,26 @@ class WCC::Contentful::SimpleClient
190
166
  end
191
167
  end
192
168
 
169
+ class PaginatingEnumerable
170
+ include Enumerable
171
+
172
+ def initialize(initial_page)
173
+ raise ArgumentError, 'Must provide initial page' unless initial_page.present?
174
+
175
+ @initial_page = initial_page
176
+ end
177
+
178
+ def each
179
+ page = @initial_page
180
+ yield page
181
+
182
+ while page.next_page?
183
+ page = page.next_page
184
+ yield page
185
+ end
186
+ end
187
+ end
188
+
193
189
  class ApiError < StandardError
194
190
  attr_reader :response
195
191
 
@@ -72,9 +72,13 @@ module WCC::Contentful::Store
72
72
  delegate :count, to: :response
73
73
 
74
74
  def to_enum
75
- return response.items unless @options[:include]
75
+ return response.each_page.flat_map(&:page_items) unless @options[:include]
76
76
 
77
- response.items.map { |e| resolve_includes(e, @options[:include]) }
77
+ response.each_page
78
+ .flat_map { |page| page.page_items.each_with_object(page).to_a }
79
+ .map do |e, page|
80
+ resolve_includes(e, page.includes, depth: @options[:include])
81
+ end
78
82
  end
79
83
 
80
84
  def initialize(store, client:, relation:, options: nil, **extra)
@@ -160,18 +164,18 @@ module WCC::Contentful::Store
160
164
  end
161
165
  end
162
166
 
163
- def resolve_includes(entry, depth)
167
+ def resolve_includes(entry, includes, depth:)
164
168
  return entry unless entry && depth && depth > 0
165
169
 
166
170
  # Dig links out of response.includes and insert them into the entry
167
171
  WCC::Contentful::LinkVisitor.new(entry, :Link, depth: depth - 1).map! do |val|
168
- resolve_link(val)
172
+ resolve_link(val, includes)
169
173
  end
170
174
  end
171
175
 
172
- def resolve_link(val)
176
+ def resolve_link(val, includes)
173
177
  return val unless val.is_a?(Hash) && val.dig('sys', 'type') == 'Link'
174
- return val unless included = response.includes[val.dig('sys', 'id')]
178
+ return val unless included = includes[val.dig('sys', 'id')]
175
179
 
176
180
  included
177
181
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module WCC
4
4
  module Contentful
5
- VERSION = '1.1.2'
5
+ VERSION = '1.2.0'
6
6
  end
7
7
  end
@@ -3,7 +3,7 @@
3
3
  require 'wcc/contentful/version'
4
4
 
5
5
  require 'active_support'
6
- require 'active_support/core_ext/object'
6
+ require 'active_support/all'
7
7
 
8
8
  require 'wcc/contentful/active_record_shim'
9
9
  require 'wcc/contentful/configuration'
@@ -54,7 +54,7 @@ Gem::Specification.new do |spec|
54
54
  spec.add_development_dependency 'generator_spec', '~> 0.9.4'
55
55
  # spec.add_development_dependency 'rails', '~> 5.0'
56
56
  # spec.add_development_dependency 'rspec-rails', '~> 3.7'
57
- spec.add_development_dependency 'sqlite3', '~> 1.3.6'
57
+ spec.add_development_dependency 'sqlite3', '~> 1.4'
58
58
  spec.add_development_dependency 'timecop', '~> 0.9.1'
59
59
 
60
60
  # optional dependencies
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.1.2
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Watermark Dev
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-02-16 00:00:00.000000000 Z
11
+ date: 2022-06-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: byebug
@@ -282,14 +282,14 @@ dependencies:
282
282
  requirements:
283
283
  - - "~>"
284
284
  - !ruby/object:Gem::Version
285
- version: 1.3.6
285
+ version: '1.4'
286
286
  type: :development
287
287
  prerelease: false
288
288
  version_requirements: !ruby/object:Gem::Requirement
289
289
  requirements:
290
290
  - - "~>"
291
291
  - !ruby/object:Gem::Version
292
- version: 1.3.6
292
+ version: '1.4'
293
293
  - !ruby/object:Gem::Dependency
294
294
  name: timecop
295
295
  requirement: !ruby/object:Gem::Requirement
@@ -461,6 +461,8 @@ files:
461
461
  - lib/wcc/contentful/model_singleton_methods.rb
462
462
  - lib/wcc/contentful/rails.rb
463
463
  - lib/wcc/contentful/rake.rb
464
+ - lib/wcc/contentful/rich_text.rb
465
+ - lib/wcc/contentful/rich_text/node.rb
464
466
  - lib/wcc/contentful/rspec.rb
465
467
  - lib/wcc/contentful/services.rb
466
468
  - lib/wcc/contentful/simple_client.rb
@@ -501,7 +503,7 @@ homepage: https://github.com/watermarkchurch/wcc-contentful/wcc-contentful
501
503
  licenses:
502
504
  - MIT
503
505
  metadata:
504
- documentation_uri: https://watermarkchurch.github.io/wcc-contentful/1.1/wcc-contentful
506
+ documentation_uri: https://watermarkchurch.github.io/wcc-contentful/1.2/wcc-contentful
505
507
  post_install_message:
506
508
  rdoc_options: []
507
509
  require_paths:
@@ -517,8 +519,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
517
519
  - !ruby/object:Gem::Version
518
520
  version: '0'
519
521
  requirements: []
520
- rubyforge_project:
521
- rubygems_version: 2.7.6.2
522
+ rubygems_version: 3.3.7
522
523
  signing_key:
523
524
  specification_version: 4
524
525
  summary: '[![Gem Version](https://badge.fury.io/rb/wcc-contentful.svg)](https://rubygems.org/gems/wcc-contentful)