wcc-contentful 1.3.2 → 1.4.0.rc2
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 +80 -14
- data/app/jobs/wcc/contentful/webhook_enable_job.rb +0 -1
- data/lib/wcc/contentful/configuration.rb +12 -5
- data/lib/wcc/contentful/downloads_schema.rb +14 -2
- data/lib/wcc/contentful/entry_locale_transformer.rb +107 -0
- data/lib/wcc/contentful/exceptions.rb +5 -0
- data/lib/wcc/contentful/link_visitor.rb +12 -1
- data/lib/wcc/contentful/middleware/store/caching_middleware.rb +34 -10
- data/lib/wcc/contentful/middleware/store/locale_middleware.rb +30 -0
- data/lib/wcc/contentful/middleware/store.rb +20 -16
- data/lib/wcc/contentful/model_builder.rb +10 -3
- data/lib/wcc/contentful/model_methods.rb +4 -6
- data/lib/wcc/contentful/simple_client/cdn.rb +5 -2
- data/lib/wcc/contentful/simple_client/management.rb +16 -0
- data/lib/wcc/contentful/simple_client.rb +1 -2
- data/lib/wcc/contentful/store/base.rb +6 -1
- data/lib/wcc/contentful/store/cdn_adapter.rb +13 -4
- data/lib/wcc/contentful/store/factory.rb +8 -1
- data/lib/wcc/contentful/store/memory_store.rb +27 -8
- data/lib/wcc/contentful/store/postgres_store.rb +4 -3
- data/lib/wcc/contentful/store/query/condition.rb +89 -0
- data/lib/wcc/contentful/store/query.rb +9 -35
- data/lib/wcc/contentful/store/rspec_examples/basic_store.rb +84 -12
- data/lib/wcc/contentful/store/rspec_examples/locale_queries.rb +220 -0
- data/lib/wcc/contentful/store/rspec_examples/operators/eq.rb +1 -1
- data/lib/wcc/contentful/store/rspec_examples.rb +13 -1
- data/lib/wcc/contentful/sync_engine.rb +1 -1
- data/lib/wcc/contentful/test/double.rb +1 -1
- data/lib/wcc/contentful/test/factory.rb +2 -4
- data/lib/wcc/contentful/version.rb +1 -1
- data/lib/wcc/contentful.rb +17 -6
- metadata +80 -48
| @@ -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' => {  | 
| 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  | 
| 141 | 
            +
                    self.queue_adapter ||= :async
         | 
| 142 142 | 
             
                    queue_as :default
         | 
| 143 143 |  | 
| 144 144 | 
             
                    def configuration
         | 
| @@ -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] | 
| 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) | 
| 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)
         | 
    
        data/lib/wcc/contentful.rb
    CHANGED
    
    | @@ -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 | 
            -
                   | 
| 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 | 
            -
                 | 
| 96 | 
            +
                schema =
         | 
| 97 97 | 
             
                  begin
         | 
| 98 | 
            -
                    JSON.parse(File.read(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 | 
            -
                 | 
| 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 | 
            -
                #  | 
| 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
         | 
    
        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.4.0.rc2
         | 
| 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-04- | 
| 11 | 
            +
            date: 2023-04-27 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: byebug
         | 
| @@ -429,6 +429,7 @@ files: | |
| 429 429 | 
             
            - lib/wcc/contentful/content_type_indexer.rb
         | 
| 430 430 | 
             
            - lib/wcc/contentful/downloads_schema.rb
         | 
| 431 431 | 
             
            - lib/wcc/contentful/engine.rb
         | 
| 432 | 
            +
            - lib/wcc/contentful/entry_locale_transformer.rb
         | 
| 432 433 | 
             
            - lib/wcc/contentful/event.rb
         | 
| 433 434 | 
             
            - lib/wcc/contentful/events.rb
         | 
| 434 435 | 
             
            - lib/wcc/contentful/exceptions.rb
         | 
| @@ -440,6 +441,7 @@ files: | |
| 440 441 | 
             
            - lib/wcc/contentful/middleware.rb
         | 
| 441 442 | 
             
            - lib/wcc/contentful/middleware/store.rb
         | 
| 442 443 | 
             
            - lib/wcc/contentful/middleware/store/caching_middleware.rb
         | 
| 444 | 
            +
            - lib/wcc/contentful/middleware/store/locale_middleware.rb
         | 
| 443 445 | 
             
            - lib/wcc/contentful/model.rb
         | 
| 444 446 | 
             
            - lib/wcc/contentful/model_api.rb
         | 
| 445 447 | 
             
            - lib/wcc/contentful/model_builder.rb
         | 
| @@ -469,10 +471,12 @@ files: | |
| 469 471 | 
             
            - lib/wcc/contentful/store/postgres_store/schema_1.sql
         | 
| 470 472 | 
             
            - lib/wcc/contentful/store/postgres_store/schema_2.sql
         | 
| 471 473 | 
             
            - lib/wcc/contentful/store/query.rb
         | 
| 474 | 
            +
            - lib/wcc/contentful/store/query/condition.rb
         | 
| 472 475 | 
             
            - lib/wcc/contentful/store/query/interface.rb
         | 
| 473 476 | 
             
            - lib/wcc/contentful/store/rspec_examples.rb
         | 
| 474 477 | 
             
            - lib/wcc/contentful/store/rspec_examples/basic_store.rb
         | 
| 475 478 | 
             
            - lib/wcc/contentful/store/rspec_examples/include_param.rb
         | 
| 479 | 
            +
            - lib/wcc/contentful/store/rspec_examples/locale_queries.rb
         | 
| 476 480 | 
             
            - lib/wcc/contentful/store/rspec_examples/nested_queries.rb
         | 
| 477 481 | 
             
            - lib/wcc/contentful/store/rspec_examples/operators.rb
         | 
| 478 482 | 
             
            - lib/wcc/contentful/store/rspec_examples/operators/eq.rb
         | 
| @@ -491,7 +495,7 @@ homepage: https://github.com/watermarkchurch/wcc-contentful/wcc-contentful | |
| 491 495 | 
             
            licenses:
         | 
| 492 496 | 
             
            - MIT
         | 
| 493 497 | 
             
            metadata:
         | 
| 494 | 
            -
              documentation_uri: https://watermarkchurch.github.io/wcc-contentful/1. | 
| 498 | 
            +
              documentation_uri: https://watermarkchurch.github.io/wcc-contentful/1.4/wcc-contentful
         | 
| 495 499 | 
             
              rubygems_mfa_required: 'true'
         | 
| 496 500 | 
             
            post_install_message:
         | 
| 497 501 | 
             
            rdoc_options: []
         | 
| @@ -504,9 +508,9 @@ required_ruby_version: !ruby/object:Gem::Requirement | |
| 504 508 | 
             
                  version: '2.7'
         | 
| 505 509 | 
             
            required_rubygems_version: !ruby/object:Gem::Requirement
         | 
| 506 510 | 
             
              requirements:
         | 
| 507 | 
            -
              - - " | 
| 511 | 
            +
              - - ">"
         | 
| 508 512 | 
             
                - !ruby/object:Gem::Version
         | 
| 509 | 
            -
                  version:  | 
| 513 | 
            +
                  version: 1.3.1
         | 
| 510 514 | 
             
            requirements: []
         | 
| 511 515 | 
             
            rubygems_version: 3.3.7
         | 
| 512 516 | 
             
            signing_key:
         | 
| @@ -526,7 +530,7 @@ summary: '[](https:// | |
| 526 530 | 
             
              Example](#advanced-configuration-example) 8. [Connecting to Multiple Spaces](#connecting-to-multiple-spaces-or-environments)
         | 
| 527 531 | 
             
              9. [Development](#development) 10. [Contributing](#contributing) 11. [License](#license)   ##
         | 
| 528 532 | 
             
              Why did you rewrite the Contentful ruby stack?  We started working with Contentful
         | 
| 529 | 
            -
              almost  | 
| 533 | 
            +
              almost 5 years ago.  Since that time, Contentful''s ruby stack has improved, but
         | 
| 530 534 | 
             
              there are still a number of pain points that we feel we have addressed better with
         | 
| 531 535 | 
             
              our gem.  These are:  * [Low-level caching](#low-level-caching) * [Better integration
         | 
| 532 536 | 
             
              with Rails & Rails models](#better-rails-integration) * [Automatic pagination and
         | 
| @@ -620,19 +624,22 @@ summary: '[](https:// | |
| 620 624 | 
             
              data as a set of dynamically generated Ruby objects.  These objects are based on
         | 
| 621 625 | 
             
              the content types in your Contentful space.  All these objects are generated by
         | 
| 622 626 | 
             
              `WCC::Contentful.init!`  The following examples show how to use this API to find
         | 
| 623 | 
            -
              entries of the `page` content type:  ```ruby #  | 
| 624 | 
            -
              #  | 
| 625 | 
            -
              UTC...>  # Find objects | 
| 626 | 
            -
              # => #< | 
| 627 | 
            -
              UTC...>  # Use operators to filter by a field # must use full notation | 
| 628 | 
            -
              (except ID)  | 
| 629 | 
            -
              }) # => [#< | 
| 630 | 
            -
               | 
| 631 | 
            -
               | 
| 632 | 
            -
               | 
| 633 | 
            -
               | 
| 634 | 
            -
               | 
| 635 | 
            -
               | 
| 627 | 
            +
              entries of the `page` content type:  ```ruby # app/models/page.rb class Page < WCC::Contentful::Model::Page  #
         | 
| 628 | 
            +
              You can add additional methods here end  # Find objects by id Page.find(''1E2ucWSdacxxf233sfa3'')
         | 
| 629 | 
            +
              # => #<Page:0x0000000005c71a78 @created_at=2018-04-16 18:41:17 UTC...>  # Find objects
         | 
| 630 | 
            +
              by field Page.find_by(slug: ''/some-slug'') # => #<Page:0x0000000005c71a78 @created_at=2018-04-16
         | 
| 631 | 
            +
              18:41:17 UTC...>  # Use operators to filter by a field # must use full notation
         | 
| 632 | 
            +
              for sys attributes (except ID) Page.find_all(''sys.created_at'' => { lte: Date.today
         | 
| 633 | 
            +
              }) # => [#<Page:0x0000000005c71a78 @created_at=2018-04-16 18:41:17 UTC...>, ...
         | 
| 634 | 
            +
              ]  # Nest queries to mimick joins Page.find_by(subpages: { slug: ''/some-slug''
         | 
| 635 | 
            +
              }) # => #<Page:0x0000000005c71a78 @created_at=2018-04-16 18:41:17 UTC...>  # Fetch
         | 
| 636 | 
            +
              an entry in a different locale spanish_homepage = Page.find_by(slug: ''/'', options:
         | 
| 637 | 
            +
              { locale: ''es-US'' }) # => #<Page:0x0000000005c71a78 @created_at=2018-04-16 18:41:17
         | 
| 638 | 
            +
              UTC...> spanish_homepage.title # => Esta es la página principal  # Pass the preview
         | 
| 639 | 
            +
              flag to use the preview client (must have set preview_token config param) preview_redirect
         | 
| 640 | 
            +
              = WCC::Contentful::Model::Redirect.find_by({ slug: ''draft-redirect'' }, preview:
         | 
| 641 | 
            +
              true) # => #<WCC::Contentful::Model::Redirect:0x0000000005d879ad @created_at=2018-04-16
         | 
| 642 | 
            +
              18:41:17 UTC...> preview_redirect_object.href # => ''http://www.somesite.com/slug-for-redirect''
         | 
| 636 643 | 
             
              ```  See the {WCC::Contentful::Model} documentation for more details.  ### Store
         | 
| 637 644 | 
             
              API  The Store layer is used by the Model API to access Contentful data in a raw
         | 
| 638 645 | 
             
              form. The Store layer returns entries as hashes parsed from JSON, conforming to
         | 
| @@ -643,12 +650,20 @@ summary: '[](https:// | |
| 643 650 | 
             
              filter: { slug: ''/some-slug'' }) # => {"sys"=> #  ... # "fields"=> # ...}  query
         | 
| 644 651 | 
             
              = store.find_all(content_type: ''page'').eq(''group'', ''some-group'') # => #<WCC::Contentful::Store::CDNAdapter::Query:0x00007fa3d40b84f0
         | 
| 645 652 | 
             
              query.first # => {"sys"=> #  ... # "fields"=> # ...} query.result # => #<Enumerator::Lazy:
         | 
| 646 | 
            -
              ...> query.result.force # => [{"sys"=> ...}, {"sys"=> ...}, ...] ```   | 
| 647 | 
            -
               | 
| 648 | 
            -
               | 
| 649 | 
            -
               | 
| 650 | 
            -
               | 
| 651 | 
            -
              to  | 
| 653 | 
            +
              ...> query.result.force # => [{"sys"=> ...}, {"sys"=> ...}, ...] ```  The store
         | 
| 654 | 
            +
              layer, while superficially similar to the Contentful API, tries to present a different
         | 
| 655 | 
            +
              "View" over the data which is more compatible with the Model layer.  It resolves
         | 
| 656 | 
            +
              includes by actually replacing the in-memory `Link` objects with their linked `Entry`
         | 
| 657 | 
            +
              representations.  This lets you traverse the links naturally using `#dig` or `#[]`:  ```ruby
         | 
| 658 | 
            +
              # Include to a depth of 3 to make sure it''s included homepage = store.find_by(slug:
         | 
| 659 | 
            +
              ''/'', include: 3) # Traverse through the top nav menu => menu button 0 => about
         | 
| 660 | 
            +
              page about_page = homepage.dig(''fields'', ''nav_menu'', ''fields'', ''buttons'',
         | 
| 661 | 
            +
              0, ''fields'', ''page'') ```   See the {WCC::Contentful::Store} documentation for
         | 
| 662 | 
            +
              more details.  ### Direct CDN API (SimpleClient)  The SimpleClient is the bottom
         | 
| 663 | 
            +
              layer, and is used to get raw data directly from the Contentful CDN.  It handles
         | 
| 664 | 
            +
              response parsing and paging, but does not resolve links or transform the result
         | 
| 665 | 
            +
              into a Model class.  The following examples show how to use the SimpleClient to
         | 
| 666 | 
            +
              retrieve data directly from the Contentful CDN:  ```ruby client = WCC::Contentful::Services.instance.client
         | 
| 652 667 | 
             
              # => #<WCC::Contentful::SimpleClient::Cdn:0x00007fa3cde89310  response = client.entry(''5FsqsbMECsM62e04U8sY4Y'')
         | 
| 653 668 | 
             
              # => #<WCC::Contentful::SimpleClient::Response:0x00007fa3d103a4e0 response.body
         | 
| 654 669 | 
             
              # => "{\n  \"sys\": {\n ... response.raw # => {"sys"=> #  ... # "fields"=> # ...}  client.asset(''5FsqsbMECsM62e04U8sY4Y'').raw
         | 
| @@ -683,8 +698,8 @@ summary: '[](https:// | |
| 683 698 | 
             
              credentials, or to connect without setting up all the rest of WCC::Contentful, is
         | 
| 684 699 | 
             
              easy:  ```ruby WCC::Contentful::SimpleClient::Cdn.new( # required access_token:
         | 
| 685 700 | 
             
              ''xxxx'', space: ''1234'', # optional environment: ''staging'', # omit to use master
         | 
| 686 | 
            -
               | 
| 687 | 
            -
               | 
| 701 | 
            +
              rate_limit_wait_timeout: 10, instrumentation: ActiveSupport::Notifications, connection:
         | 
| 702 | 
            +
              Faraday.new { |builder| ... }, ) ```  You can also create a {WCC::Contentful::SimpleClient::Preview}
         | 
| 688 703 | 
             
              to talk to the Preview API, or a {WCC::Contentful::SimpleClient::Management} to
         | 
| 689 704 | 
             
              talk to the Management API.  ### Store Layer  The Store Layer represents the data
         | 
| 690 705 | 
             
              store where Contentful entries are kept for querying.  By default, `WCC::Contentful.init!`
         | 
| @@ -704,27 +719,44 @@ summary: '[](https:// | |
| 704 719 | 
             
              example, the {WCC::Contentful::Store::MemoryStore} uses this to update the hash
         | 
| 705 720 | 
             
              with the newest version of an entry, or delete an entry out of the hash.  #### Store
         | 
| 706 721 | 
             
              Middleware  The store layer is made up of a base store (which implements {WCC::Contentful::Store::Interface}),
         | 
| 707 | 
            -
              and  | 
| 708 | 
            -
               | 
| 709 | 
            -
              simply include {WCC::Contentful::Middleware::Store}  | 
| 710 | 
            -
               | 
| 711 | 
            -
               | 
| 712 | 
            -
               | 
| 713 | 
            -
               | 
| 714 | 
            -
               | 
| 715 | 
            -
               | 
| 716 | 
            -
               | 
| 717 | 
            -
               | 
| 718 | 
            -
               | 
| 719 | 
            -
               | 
| 720 | 
            -
               | 
| 721 | 
            -
               | 
| 722 | 
            -
               | 
| 723 | 
            -
               | 
| 724 | 
            -
               | 
| 725 | 
            -
               | 
| 726 | 
            -
               | 
| 727 | 
            -
               | 
| 722 | 
            +
              and some required middleware.  The list of default middleware applied to each store
         | 
| 723 | 
            +
              is found in {WCC::Contentful::Store::Factory.default_middleware}  To create your
         | 
| 724 | 
            +
              own middleware simply include {WCC::Contentful::Middleware::Store}.  Then you can
         | 
| 725 | 
            +
              optionally implement the `#transform` and `#select?` methods:  ```ruby class MyMiddleware
         | 
| 726 | 
            +
              include WCC::Contentful::Middleware::Store  # Called for each entry that is requested
         | 
| 727 | 
            +
              out of the backing store.  You can modify the entry and return it to the # next
         | 
| 728 | 
            +
              layer. def transform(entry, options) # Do something with the entry... # Make sure
         | 
| 729 | 
            +
              you return it at the end! entry end  def select?(entry, options) # Choose whether
         | 
| 730 | 
            +
              this entry should exist or not.  If you return false here, then the entry will act
         | 
| 731 | 
            +
              as though it # were archived in Contentful. entry.dig(''fields'', ''hide_until'')
         | 
| 732 | 
            +
              > Time.zone.now end end ```  You can also override any of the standard Store methods.  To
         | 
| 733 | 
            +
              apply the middleware, call `use` when configuring the store:  ```ruby config.store
         | 
| 734 | 
            +
              :direct do use MyMiddleware, param1: ''xxx'' end ```  The most useful middleware
         | 
| 735 | 
            +
              is the {WCC::Contentful::Middleware::Store::CachingMiddleware}, which enables `:lazy_sync`
         | 
| 736 | 
            +
              mode (see {WCC::Contentful::Configuration#store})  ### Model Layer  This is the
         | 
| 737 | 
            +
              global top layer where your Rails app looks up content similarly to ActiveModel.  The
         | 
| 738 | 
            +
              models are namespaced under the root class {WCC::Contentful::Model}. Each model''s
         | 
| 739 | 
            +
              implementation of `.find`, `.find_by`, and `.find_all` simply call into the configured
         | 
| 740 | 
            +
              Store.  Models can be initialized directly with the `.new` method, by passing in
         | 
| 741 | 
            +
              a hash: ```ruby entry = { ''sys'' => ..., ''fields'' => ... } Page.new(entry) ```  **The
         | 
| 742 | 
            +
              initializer must receive a localized entry**.  An entry found using a `locale=*`
         | 
| 743 | 
            +
              query must be transformed to a localized entry using the {WCC::Contentful::EntryLocaleTransformer}
         | 
| 744 | 
            +
              before passing it to your model:  ```ruby entry = client.entry(''1234'', locale:
         | 
| 745 | 
            +
              ''*'').raw localized_entry = WCC::Contentful::EntryLocaleTransformer.transform_to_locale(entry,
         | 
| 746 | 
            +
              ''en-US'') Page.new(localized_entry) ```  The Store layer ensures that localized
         | 
| 747 | 
            +
              entries are returned using the {WCC::Contentful::Middleware::Store::LocaleMiddleware}.  The
         | 
| 748 | 
            +
              main benefit of the Model layer is lazy link resolution.  When a model''s property
         | 
| 749 | 
            +
              is accessed, if that property is a link that has not been resolved yet (for example
         | 
| 750 | 
            +
              using the `include: n` parameter on `.find_by`), the model will automatically call
         | 
| 751 | 
            +
              `#find` on the store to resolve that linked entry.  Note that this can easily result
         | 
| 752 | 
            +
              in lots of CDN calls to Contentful!  To optimize this you should use the `include`
         | 
| 753 | 
            +
              parameter and/or use a different store.  ## Test Helpers  To use the test helpers,
         | 
| 754 | 
            +
              include the following in your rails_helper.rb:  ```ruby require ''wcc/contentful/rspec''
         | 
| 755 | 
            +
              ```  This adds the following helpers to all your specs:  ```ruby ## # Builds a in-memory
         | 
| 756 | 
            +
              instance of the Contentful model for the given content_type. # All attributes that
         | 
| 757 | 
            +
              are known to be required fields on the content type # will return a default value
         | 
| 758 | 
            +
              based on the field type. instance = contentful_create(''my-content-type'', my_field:
         | 
| 759 | 
            +
              ''some-value'') # => #<WCC::Contentful::Model::MyContentType:0x0000000005c71a78
         | 
| 728 760 | 
             
              @created_at=2018-04-16 18:41:17 UTC...>  instance.my_field # => "some-value"  instance.other_required_field
         | 
| 729 761 | 
             
              # => "default-value"  instance.other_optional_field # => nil  instance.not_a_field
         | 
| 730 762 | 
             
              # NoMethodError: undefined method `not_a_field'' for #<MyContentType:0x00007fbac81ee490>  ##
         |