wcc-contentful 1.3.0 → 1.4.0.rc1

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.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +105 -14
  3. data/lib/wcc/contentful/configuration.rb +12 -5
  4. data/lib/wcc/contentful/downloads_schema.rb +14 -2
  5. data/lib/wcc/contentful/entry_locale_transformer.rb +107 -0
  6. data/lib/wcc/contentful/exceptions.rb +5 -0
  7. data/lib/wcc/contentful/link_visitor.rb +12 -1
  8. data/lib/wcc/contentful/middleware/store/caching_middleware.rb +25 -8
  9. data/lib/wcc/contentful/middleware/store/locale_middleware.rb +30 -0
  10. data/lib/wcc/contentful/middleware/store.rb +20 -16
  11. data/lib/wcc/contentful/model_api.rb +2 -2
  12. data/lib/wcc/contentful/model_builder.rb +9 -2
  13. data/lib/wcc/contentful/model_methods.rb +4 -6
  14. data/lib/wcc/contentful/simple_client/cdn.rb +5 -2
  15. data/lib/wcc/contentful/simple_client/management.rb +16 -0
  16. data/lib/wcc/contentful/simple_client.rb +1 -0
  17. data/lib/wcc/contentful/store/base.rb +6 -1
  18. data/lib/wcc/contentful/store/cdn_adapter.rb +13 -4
  19. data/lib/wcc/contentful/store/factory.rb +8 -1
  20. data/lib/wcc/contentful/store/memory_store.rb +27 -8
  21. data/lib/wcc/contentful/store/postgres_store.rb +4 -3
  22. data/lib/wcc/contentful/store/query/condition.rb +89 -0
  23. data/lib/wcc/contentful/store/query.rb +9 -35
  24. data/lib/wcc/contentful/store/rspec_examples/basic_store.rb +84 -12
  25. data/lib/wcc/contentful/store/rspec_examples/locale_queries.rb +220 -0
  26. data/lib/wcc/contentful/store/rspec_examples/operators/eq.rb +1 -1
  27. data/lib/wcc/contentful/store/rspec_examples.rb +13 -1
  28. data/lib/wcc/contentful/sync_engine.rb +1 -1
  29. data/lib/wcc/contentful/test/double.rb +1 -1
  30. data/lib/wcc/contentful/test/factory.rb +2 -4
  31. data/lib/wcc/contentful/version.rb +1 -1
  32. data/lib/wcc/contentful.rb +17 -6
  33. metadata +112 -62
@@ -0,0 +1,220 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.shared_examples 'supports locales in queries' do |feature_set|
4
+ describe 'supports query options: { locale: ... }' do
5
+ before { skip('querying alternate locales not supported') } if feature_set == false
6
+
7
+ generator = proc { "test#{rand(1..10_000)}" }
8
+
9
+ let(:desired_value) {
10
+ generator.call
11
+ }
12
+
13
+ let(:data) {
14
+ 1.upto(3).map do |i|
15
+ {
16
+ 'sys' => { 'id' => "k#{i}", 'contentType' => { 'sys' => { 'id' => 'test1' } } },
17
+ 'fields' => {
18
+ 'slug' => {
19
+ 'en-US' => generator.call,
20
+ 'es-ES' => generator.call
21
+ }
22
+ }
23
+ }
24
+ end
25
+ }
26
+
27
+ let(:desired) {
28
+ {
29
+ 'sys' => { 'id' => "k#{rand}", 'contentType' => { 'sys' => { 'id' => 'test1' } } },
30
+ 'fields' => {
31
+ 'slug' => {
32
+ 'en-US' => generator.call,
33
+ 'es-ES' => desired_value
34
+ }
35
+ }
36
+ }
37
+ }
38
+
39
+ context 'when localized value exists' do
40
+ it 'find_by can apply filter object' do
41
+ [*data, desired].shuffle.each { |d| subject.set(d.dig('sys', 'id'), d) }
42
+
43
+ # act
44
+ found = subject.find_by(content_type: 'test1',
45
+ filter: { 'slug' => desired_value },
46
+ options: { locale: 'es-ES' })
47
+
48
+ # assert
49
+ expect(found).to_not be_nil
50
+ expect(found).to eq(desired)
51
+ end
52
+
53
+ it 'find_by can find value in array' do
54
+ data =
55
+ 1.upto(3).map do |i|
56
+ {
57
+ 'sys' => {
58
+ 'id' => "k#{i}",
59
+ 'contentType' => { 'sys' => { 'id' => 'test1' } }
60
+ },
61
+ 'fields' => {
62
+ 'slug' => {
63
+ 'en-US' => [generator.call, generator.call],
64
+ 'es-ES' => [generator.call, generator.call]
65
+ }
66
+ }
67
+ }
68
+ end
69
+
70
+ desired_value = generator.call
71
+ desired = {
72
+ 'sys' => { 'id' => "k#{rand}", 'contentType' => { 'sys' => { 'id' => 'test1' } } },
73
+ 'fields' => {
74
+ 'slug' => {
75
+ 'en-US' => [generator.call, generator.call],
76
+ 'es-ES' => [generator.call, desired_value]
77
+ }
78
+ }
79
+ }
80
+
81
+ data << desired
82
+ data.shuffle.each { |d| subject.set(d.dig('sys', 'id'), d) }
83
+
84
+ # act
85
+ found = subject.find_by(content_type: 'test1',
86
+ filter: { 'slug' => { eq: desired_value } },
87
+ options: { locale: 'es-ES' })
88
+
89
+ # assert
90
+ expect(found).to_not be_nil
91
+ expect(found).to eq(desired)
92
+ end
93
+
94
+ it 'find_all can apply operator' do
95
+ desired =
96
+ 4.upto(5).map do |i|
97
+ {
98
+ 'sys' => { 'id' => "d#{i}", 'contentType' => { 'sys' => { 'id' => 'test1' } } },
99
+ 'fields' => {
100
+ 'slug' => {
101
+ 'en-US' => generator.call,
102
+ 'es-ES' => desired_value
103
+ }
104
+ }
105
+ }
106
+ end
107
+
108
+ [*data, *desired].shuffle.each { |d| subject.set(d.dig('sys', 'id'), d) }
109
+
110
+ # act
111
+ found = subject.find_all(content_type: 'test1', options: { locale: 'es-ES' })
112
+ .eq('slug', desired_value)
113
+
114
+ # assert
115
+ expect(found.count).to eq(2)
116
+ sorted = found.to_a.sort_by { |item| item.dig('sys', 'id') }
117
+ expect(sorted).to eq(desired)
118
+ end
119
+ end
120
+
121
+ context 'using fallback locales' do
122
+ before { pending('querying alternate locales not yet implemented') } if feature_set&.to_s == 'pending'
123
+
124
+ before do
125
+ allow(configuration).to receive(:locale_fallbacks)
126
+ .and_return({
127
+ 'es-MX' => 'es-ES',
128
+ 'es-ES' => 'en-US'
129
+ })
130
+ end
131
+
132
+ it 'find_by can apply filter object' do
133
+ [*data, desired].shuffle.each { |d| subject.set(d.dig('sys', 'id'), d) }
134
+
135
+ # act
136
+ found = subject.find_by(content_type: 'test1',
137
+ filter: { 'slug' => desired_value },
138
+ options: { locale: 'es-MX' })
139
+
140
+ # assert
141
+ expect(found).to_not be_nil
142
+ expect(found).to eq(desired)
143
+ end
144
+
145
+ it 'find_by can find value in array' do
146
+ data =
147
+ 1.upto(3).map do |i|
148
+ {
149
+ 'sys' => {
150
+ 'id' => "k#{i}",
151
+ 'contentType' => { 'sys' => { 'id' => 'test1' } }
152
+ },
153
+ 'fields' => {
154
+ 'slug' => {
155
+ 'en-US' => [generator.call, generator.call],
156
+ 'es-ES' => [generator.call, generator.call]
157
+ }
158
+ }
159
+ }
160
+ end
161
+
162
+ desired_value = generator.call
163
+ desired = {
164
+ 'sys' => { 'id' => "k#{rand}", 'contentType' => { 'sys' => { 'id' => 'test1' } } },
165
+ 'fields' => {
166
+ 'slug' => {
167
+ 'en-US' => [generator.call, generator.call],
168
+ 'es-ES' => [generator.call, desired_value]
169
+ }
170
+ }
171
+ }
172
+
173
+ data << desired
174
+ data.shuffle.each { |d| subject.set(d.dig('sys', 'id'), d) }
175
+
176
+ # act
177
+ found = subject.find_by(content_type: 'test1',
178
+ filter: { 'slug' => { eq: desired_value } },
179
+ options: { locale: 'es-MX' })
180
+
181
+ # assert
182
+ expect(found).to_not be_nil
183
+ expect(found).to eq(desired)
184
+ end
185
+
186
+ it 'find_all can apply operator' do
187
+ desired = [
188
+ {
189
+ 'sys' => { 'id' => 'd1', 'contentType' => { 'sys' => { 'id' => 'test1' } } },
190
+ 'fields' => {
191
+ 'slug' => {
192
+ 'en-US' => generator.call,
193
+ 'es-ES' => desired_value
194
+ }
195
+ }
196
+ },
197
+ {
198
+ 'sys' => { 'id' => 'd2', 'contentType' => { 'sys' => { 'id' => 'test1' } } },
199
+ 'fields' => {
200
+ 'slug' => {
201
+ 'en-US' => desired_value
202
+ }
203
+ }
204
+ }
205
+ ]
206
+
207
+ [*data, *desired].shuffle.each { |d| subject.set(d.dig('sys', 'id'), d) }
208
+
209
+ # act
210
+ found = subject.find_all(content_type: 'test1', options: { locale: 'es-MX' })
211
+ .eq('slug', desired_value)
212
+
213
+ # assert
214
+ expect(found.count).to eq(2)
215
+ sorted = found.to_a.sort_by { |item| item.dig('sys', 'id') }
216
+ expect(sorted).to eq(desired)
217
+ end
218
+ end
219
+ end
220
+ end
@@ -46,7 +46,7 @@ RSpec.shared_examples 'supports :eq operator' do
46
46
  'id' => "k#{i}",
47
47
  'contentType' => { 'sys' => { 'id' => 'test1' } }
48
48
  },
49
- 'fields' => { 'name' => { 'en-US' => [generator.call, generator.call] } }
49
+ 'fields' => { type.to_s => { 'en-US' => [generator.call, generator.call] } }
50
50
  }
51
51
  end
52
52
 
@@ -4,6 +4,7 @@ require_relative './rspec_examples/basic_store'
4
4
  require_relative './rspec_examples/operators'
5
5
  require_relative './rspec_examples/nested_queries'
6
6
  require_relative './rspec_examples/include_param'
7
+ require_relative './rspec_examples/locale_queries'
7
8
 
8
9
  # rubocop:disable Style/BlockDelimiters
9
10
 
@@ -24,6 +25,11 @@ require_relative './rspec_examples/include_param'
24
25
  # all linked entries of an object in a single query.
25
26
  # If your store does not respect the include parameter, then the Model layer
26
27
  # will be calling #find a lot in order to resolve linked entries.
28
+ # [:locale_queries] - This feature defines how the store respects the `locale: x`
29
+ # key in the Options hash. If this option is set, then the store needs to
30
+ # compare the query to the appropriate localized value.
31
+ # If the store does not support this, then either the application should not
32
+ # use multiple locales OR should always query using the default locale.
27
33
  #
28
34
  # @example
29
35
  # require 'wcc/contentful/store/rspec_examples'
@@ -38,13 +44,19 @@ require_relative './rspec_examples/include_param'
38
44
  RSpec.shared_examples 'contentful store' do |feature_set|
39
45
  feature_set = {
40
46
  nested_queries: 'pending',
41
- include_param: 'pending'
47
+ include_param: 'pending',
48
+ locale_queries: 'pending'
42
49
  }.merge(feature_set&.symbolize_keys || {})
43
50
 
51
+ let(:configuration) {
52
+ WCC::Contentful::Configuration.new
53
+ }
54
+
44
55
  include_examples 'basic store'
45
56
  include_examples 'operators', feature_set[:operators]
46
57
  include_examples 'supports nested queries', feature_set[:nested_queries]
47
58
  include_examples 'supports include param', feature_set[:include_param]
59
+ include_examples 'supports locales in queries', feature_set[:locale_queries]
48
60
  end
49
61
 
50
62
  # rubocop:enable Style/BlockDelimiters
@@ -138,7 +138,7 @@ module WCC::Contentful
138
138
  # This job uses the Contentful Sync API to update the configured store with
139
139
  # the latest data from Contentful.
140
140
  class Job < ActiveJob::Base
141
- self.queue_adapter = :async
141
+ self.queue_adapter ||= :async
142
142
  queue_as :default
143
143
 
144
144
  def configuration
@@ -60,7 +60,7 @@ module WCC::Contentful::Test::Double
60
60
  revision: rand(100),
61
61
  locale: 'en-US'
62
62
  },
63
- fields: attrs.transform_values { |v| { 'en-US' => v } }
63
+ fields: attrs
64
64
  }
65
65
 
66
66
  double(attrs)
@@ -24,7 +24,7 @@ module WCC::Contentful::Test::Factory
24
24
 
25
25
  raw_value = v
26
26
  raw_value = to_raw(v, field.type) if %i[Asset Link].include?(field.type)
27
- raw['fields'][field.name][raw.dig('sys', 'locale')] = raw_value
27
+ raw['fields'][field.name] = raw_value
28
28
  end
29
29
 
30
30
  instance = const.new(raw, context)
@@ -84,9 +84,7 @@ module WCC::Contentful::Test::Factory
84
84
  end
85
85
 
86
86
  def contentful_fields(model)
87
- WCC::Contentful::Test::Attributes.defaults(model).transform_values do |v|
88
- { 'en-US' => v }
89
- end
87
+ WCC::Contentful::Test::Attributes.defaults(model)
90
88
  end
91
89
 
92
90
  def to_raw(val, field_type)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module WCC
4
4
  module Contentful
5
- VERSION = '1.3.0'
5
+ VERSION = '1.4.0.rc1'
6
6
  end
7
7
  end
@@ -10,6 +10,7 @@ require 'wcc/contentful/configuration'
10
10
  require 'wcc/contentful/downloads_schema'
11
11
  require 'wcc/contentful/exceptions'
12
12
  require 'wcc/contentful/helpers'
13
+ require 'wcc/contentful/entry_locale_transformer'
13
14
  require 'wcc/contentful/link_visitor'
14
15
  require 'wcc/contentful/services'
15
16
  require 'wcc/contentful/simple_client'
@@ -40,9 +41,8 @@ module WCC::Contentful
40
41
  end
41
42
 
42
43
  # Gets all queryable locales.
43
- # Reserved for future use.
44
44
  def locales
45
- @locales ||= { 'en-US' => {} }.freeze
45
+ configuration&.locale_fallbacks
46
46
  end
47
47
 
48
48
  def logger
@@ -93,15 +93,18 @@ module WCC::Contentful
93
93
  end
94
94
  end
95
95
 
96
- content_types =
96
+ schema =
97
97
  begin
98
- JSON.parse(File.read(configuration.schema_file))['contentTypes'] if File.exist?(configuration.schema_file)
98
+ JSON.parse(File.read(configuration.schema_file)) if File.exist?(configuration.schema_file)
99
99
  rescue JSON::ParserError
100
100
  Services.instance.warn("Schema file invalid, ignoring it: #{configuration.schema_file}")
101
101
  nil
102
102
  end
103
103
 
104
- if !content_types && %i[if_possible never].include?(configuration.update_schema_file)
104
+ content_types = schema['contentTypes'] if schema
105
+ locales = schema['locales'] if schema
106
+
107
+ if !schema && %i[if_possible never].include?(configuration.update_schema_file)
105
108
  # Final fallback - try to grab content types from CDN. We can't update the file
106
109
  # because the CDN doesn't have all the field validation info, but we can at least
107
110
  # build the WCC::Contentful::Model instances.
@@ -127,7 +130,15 @@ module WCC::Contentful
127
130
  services: WCC::Contentful::Services.instance
128
131
  )
129
132
 
130
- # Drop an initial sync
133
+ # Update the locale fallbacks from the schema file, unless they have already
134
+ # been configured.
135
+ locales&.each do |locale_hash|
136
+ next if @configuration.locale_fallbacks[locale_hash['code']]
137
+
138
+ @configuration.locale_fallbacks[locale_hash['code']] = locale_hash['fallbackCode']
139
+ end
140
+
141
+ # Enqueue an initial sync
131
142
  WCC::Contentful::SyncEngine::Job.perform_later if defined?(WCC::Contentful::SyncEngine::Job)
132
143
 
133
144
  @configuration = @configuration.freeze