--- !ruby/object:Gem::Specification
name: wcc-contentful
version: !ruby/object:Gem::Version
  version: 1.4.0
platform: ruby
authors:
- Watermark Dev
autorequire:
bindir: bin
cert_chain: []
date: 2023-05-09 00:00:00.000000000 Z
dependencies:
- !ruby/object:Gem::Dependency
  name: byebug
  requirement: !ruby/object:Gem::Requirement
    requirements:
    - - "~>"
      - !ruby/object:Gem::Version
        version: 11.0.1
  type: :development
  prerelease: false
  version_requirements: !ruby/object:Gem::Requirement
    requirements:
    - - "~>"
      - !ruby/object:Gem::Version
        version: 11.0.1
- !ruby/object:Gem::Dependency
  name: coveralls
  requirement: !ruby/object:Gem::Requirement
    requirements:
    - - ">="
      - !ruby/object:Gem::Version
        version: '0'
  type: :development
  prerelease: false
  version_requirements: !ruby/object:Gem::Requirement
    requirements:
    - - ">="
      - !ruby/object:Gem::Version
        version: '0'
- !ruby/object:Gem::Dependency
  name: dotenv
  requirement: !ruby/object:Gem::Requirement
    requirements:
    - - "~>"
      - !ruby/object:Gem::Version
        version: '2.2'
  type: :development
  prerelease: false
  version_requirements: !ruby/object:Gem::Requirement
    requirements:
    - - "~>"
      - !ruby/object:Gem::Version
        version: '2.2'
- !ruby/object:Gem::Dependency
  name: erb_lint
  requirement: !ruby/object:Gem::Requirement
    requirements:
    - - "~>"
      - !ruby/object:Gem::Version
        version: 0.0.26
  type: :development
  prerelease: false
  version_requirements: !ruby/object:Gem::Requirement
    requirements:
    - - "~>"
      - !ruby/object:Gem::Version
        version: 0.0.26
- !ruby/object:Gem::Dependency
  name: httplog
  requirement: !ruby/object:Gem::Requirement
    requirements:
    - - "~>"
      - !ruby/object:Gem::Version
        version: '1.0'
  type: :development
  prerelease: false
  version_requirements: !ruby/object:Gem::Requirement
    requirements:
    - - "~>"
      - !ruby/object:Gem::Version
        version: '1.0'
- !ruby/object:Gem::Dependency
  name: rake
  requirement: !ruby/object:Gem::Requirement
    requirements:
    - - "~>"
      - !ruby/object:Gem::Version
        version: '13.0'
  type: :development
  prerelease: false
  version_requirements: !ruby/object:Gem::Requirement
    requirements:
    - - "~>"
      - !ruby/object:Gem::Version
        version: '13.0'
- !ruby/object:Gem::Dependency
  name: rspec
  requirement: !ruby/object:Gem::Requirement
    requirements:
    - - "~>"
      - !ruby/object:Gem::Version
        version: '3.0'
  type: :development
  prerelease: false
  version_requirements: !ruby/object:Gem::Requirement
    requirements:
    - - "~>"
      - !ruby/object:Gem::Version
        version: '3.0'
- !ruby/object:Gem::Dependency
  name: rspec-instrumentation-matcher
  requirement: !ruby/object:Gem::Requirement
    requirements:
    - - ">="
      - !ruby/object:Gem::Version
        version: '0'
  type: :development
  prerelease: false
  version_requirements: !ruby/object:Gem::Requirement
    requirements:
    - - ">="
      - !ruby/object:Gem::Version
        version: '0'
- !ruby/object:Gem::Dependency
  name: rspec_junit_formatter
  requirement: !ruby/object:Gem::Requirement
    requirements:
    - - "~>"
      - !ruby/object:Gem::Version
        version: 0.4.1
  type: :development
  prerelease: false
  version_requirements: !ruby/object:Gem::Requirement
    requirements:
    - - "~>"
      - !ruby/object:Gem::Version
        version: 0.4.1
- !ruby/object:Gem::Dependency
  name: simplecov
  requirement: !ruby/object:Gem::Requirement
    requirements:
    - - "~>"
      - !ruby/object:Gem::Version
        version: 0.16.1
  type: :development
  prerelease: false
  version_requirements: !ruby/object:Gem::Requirement
    requirements:
    - - "~>"
      - !ruby/object:Gem::Version
        version: 0.16.1
- !ruby/object:Gem::Dependency
  name: vcr
  requirement: !ruby/object:Gem::Requirement
    requirements:
    - - "~>"
      - !ruby/object:Gem::Version
        version: '5.0'
  type: :development
  prerelease: false
  version_requirements: !ruby/object:Gem::Requirement
    requirements:
    - - "~>"
      - !ruby/object:Gem::Version
        version: '5.0'
- !ruby/object:Gem::Dependency
  name: webmock
  requirement: !ruby/object:Gem::Requirement
    requirements:
    - - "~>"
      - !ruby/object:Gem::Version
        version: '3.0'
  type: :development
  prerelease: false
  version_requirements: !ruby/object:Gem::Requirement
    requirements:
    - - "~>"
      - !ruby/object:Gem::Version
        version: '3.0'
- !ruby/object:Gem::Dependency
  name: wisper-rspec
  requirement: !ruby/object:Gem::Requirement
    requirements:
    - - ">="
      - !ruby/object:Gem::Version
        version: '0'
  type: :development
  prerelease: false
  version_requirements: !ruby/object:Gem::Requirement
    requirements:
    - - ">="
      - !ruby/object:Gem::Version
        version: '0'
- !ruby/object:Gem::Dependency
  name: guard
  requirement: !ruby/object:Gem::Requirement
    requirements:
    - - "~>"
      - !ruby/object:Gem::Version
        version: '2.14'
  type: :development
  prerelease: false
  version_requirements: !ruby/object:Gem::Requirement
    requirements:
    - - "~>"
      - !ruby/object:Gem::Version
        version: '2.14'
- !ruby/object:Gem::Dependency
  name: guard-rspec
  requirement: !ruby/object:Gem::Requirement
    requirements:
    - - "~>"
      - !ruby/object:Gem::Version
        version: '4.7'
  type: :development
  prerelease: false
  version_requirements: !ruby/object:Gem::Requirement
    requirements:
    - - "~>"
      - !ruby/object:Gem::Version
        version: '4.7'
- !ruby/object:Gem::Dependency
  name: guard-rubocop
  requirement: !ruby/object:Gem::Requirement
    requirements:
    - - "~>"
      - !ruby/object:Gem::Version
        version: 1.3.0
  type: :development
  prerelease: false
  version_requirements: !ruby/object:Gem::Requirement
    requirements:
    - - "~>"
      - !ruby/object:Gem::Version
        version: 1.3.0
- !ruby/object:Gem::Dependency
  name: guard-shell
  requirement: !ruby/object:Gem::Requirement
    requirements:
    - - "~>"
      - !ruby/object:Gem::Version
        version: 0.7.1
  type: :development
  prerelease: false
  version_requirements: !ruby/object:Gem::Requirement
    requirements:
    - - "~>"
      - !ruby/object:Gem::Version
        version: 0.7.1
- !ruby/object:Gem::Dependency
  name: generator_spec
  requirement: !ruby/object:Gem::Requirement
    requirements:
    - - "~>"
      - !ruby/object:Gem::Version
        version: 0.9.4
  type: :development
  prerelease: false
  version_requirements: !ruby/object:Gem::Requirement
    requirements:
    - - "~>"
      - !ruby/object:Gem::Version
        version: 0.9.4
- !ruby/object:Gem::Dependency
  name: sqlite3
  requirement: !ruby/object:Gem::Requirement
    requirements:
    - - "~>"
      - !ruby/object:Gem::Version
        version: '1.4'
  type: :development
  prerelease: false
  version_requirements: !ruby/object:Gem::Requirement
    requirements:
    - - "~>"
      - !ruby/object:Gem::Version
        version: '1.4'
- !ruby/object:Gem::Dependency
  name: timecop
  requirement: !ruby/object:Gem::Requirement
    requirements:
    - - "~>"
      - !ruby/object:Gem::Version
        version: 0.9.1
  type: :development
  prerelease: false
  version_requirements: !ruby/object:Gem::Requirement
    requirements:
    - - "~>"
      - !ruby/object:Gem::Version
        version: 0.9.1
- !ruby/object:Gem::Dependency
  name: connection_pool
  requirement: !ruby/object:Gem::Requirement
    requirements:
    - - "~>"
      - !ruby/object:Gem::Version
        version: '2.2'
  type: :development
  prerelease: false
  version_requirements: !ruby/object:Gem::Requirement
    requirements:
    - - "~>"
      - !ruby/object:Gem::Version
        version: '2.2'
- !ruby/object:Gem::Dependency
  name: faraday
  requirement: !ruby/object:Gem::Requirement
    requirements:
    - - "~>"
      - !ruby/object:Gem::Version
        version: '0.9'
  type: :development
  prerelease: false
  version_requirements: !ruby/object:Gem::Requirement
    requirements:
    - - "~>"
      - !ruby/object:Gem::Version
        version: '0.9'
- !ruby/object:Gem::Dependency
  name: http
  requirement: !ruby/object:Gem::Requirement
    requirements:
    - - ">"
      - !ruby/object:Gem::Version
        version: '1.0'
    - - "<"
      - !ruby/object:Gem::Version
        version: '3.0'
  type: :development
  prerelease: false
  version_requirements: !ruby/object:Gem::Requirement
    requirements:
    - - ">"
      - !ruby/object:Gem::Version
        version: '1.0'
    - - "<"
      - !ruby/object:Gem::Version
        version: '3.0'
- !ruby/object:Gem::Dependency
  name: pg
  requirement: !ruby/object:Gem::Requirement
    requirements:
    - - "~>"
      - !ruby/object:Gem::Version
        version: '1.0'
  type: :development
  prerelease: false
  version_requirements: !ruby/object:Gem::Requirement
    requirements:
    - - "~>"
      - !ruby/object:Gem::Version
        version: '1.0'
- !ruby/object:Gem::Dependency
  name: typhoeus
  requirement: !ruby/object:Gem::Requirement
    requirements:
    - - "~>"
      - !ruby/object:Gem::Version
        version: '1.3'
  type: :development
  prerelease: false
  version_requirements: !ruby/object:Gem::Requirement
    requirements:
    - - "~>"
      - !ruby/object:Gem::Version
        version: '1.3'
- !ruby/object:Gem::Dependency
  name: activesupport
  requirement: !ruby/object:Gem::Requirement
    requirements:
    - - ">="
      - !ruby/object:Gem::Version
        version: '5'
  type: :runtime
  prerelease: false
  version_requirements: !ruby/object:Gem::Requirement
    requirements:
    - - ">="
      - !ruby/object:Gem::Version
        version: '5'
- !ruby/object:Gem::Dependency
  name: wcc-base
  requirement: !ruby/object:Gem::Requirement
    requirements:
    - - "~>"
      - !ruby/object:Gem::Version
        version: 0.3.1
  type: :runtime
  prerelease: false
  version_requirements: !ruby/object:Gem::Requirement
    requirements:
    - - "~>"
      - !ruby/object:Gem::Version
        version: 0.3.1
- !ruby/object:Gem::Dependency
  name: wisper
  requirement: !ruby/object:Gem::Requirement
    requirements:
    - - "~>"
      - !ruby/object:Gem::Version
        version: 2.0.0
  type: :runtime
  prerelease: false
  version_requirements: !ruby/object:Gem::Requirement
    requirements:
    - - "~>"
      - !ruby/object:Gem::Version
        version: 2.0.0
description: Contentful API wrapper library exposing an ActiveRecord-like interface
email:
- dev@watermark.org
executables: []
extensions: []
extra_rdoc_files: []
files:
- README.md
- Rakefile
- app/controllers/wcc/contentful/application_controller.rb
- app/controllers/wcc/contentful/webhook_controller.rb
- app/jobs/wcc/contentful/webhook_enable_job.rb
- config/initializers/mime_types.rb
- config/routes.rb
- lib/tasks/download_schema.rake
- lib/wcc/contentful.rb
- lib/wcc/contentful/active_record_shim.rb
- lib/wcc/contentful/configuration.rb
- lib/wcc/contentful/content_type_indexer.rb
- lib/wcc/contentful/downloads_schema.rb
- lib/wcc/contentful/engine.rb
- lib/wcc/contentful/entry_locale_transformer.rb
- lib/wcc/contentful/event.rb
- lib/wcc/contentful/events.rb
- lib/wcc/contentful/exceptions.rb
- lib/wcc/contentful/helpers.rb
- lib/wcc/contentful/indexed_representation.rb
- lib/wcc/contentful/instrumentation.rb
- lib/wcc/contentful/link.rb
- lib/wcc/contentful/link_visitor.rb
- lib/wcc/contentful/middleware.rb
- lib/wcc/contentful/middleware/store.rb
- lib/wcc/contentful/middleware/store/caching_middleware.rb
- lib/wcc/contentful/middleware/store/locale_middleware.rb
- lib/wcc/contentful/model.rb
- lib/wcc/contentful/model_api.rb
- lib/wcc/contentful/model_builder.rb
- lib/wcc/contentful/model_methods.rb
- lib/wcc/contentful/model_singleton_methods.rb
- lib/wcc/contentful/rails.rb
- lib/wcc/contentful/rake.rb
- lib/wcc/contentful/rich_text.rb
- lib/wcc/contentful/rich_text/node.rb
- lib/wcc/contentful/rspec.rb
- lib/wcc/contentful/services.rb
- lib/wcc/contentful/simple_client.rb
- lib/wcc/contentful/simple_client/cdn.rb
- lib/wcc/contentful/simple_client/management.rb
- lib/wcc/contentful/simple_client/preview.rb
- lib/wcc/contentful/simple_client/response.rb
- lib/wcc/contentful/simple_client/typhoeus_adapter.rb
- lib/wcc/contentful/store.rb
- lib/wcc/contentful/store/README.md
- lib/wcc/contentful/store/base.rb
- lib/wcc/contentful/store/cdn_adapter.rb
- lib/wcc/contentful/store/factory.rb
- lib/wcc/contentful/store/instrumentation.rb
- lib/wcc/contentful/store/interface.rb
- lib/wcc/contentful/store/memory_store.rb
- lib/wcc/contentful/store/postgres_store.rb
- lib/wcc/contentful/store/postgres_store/schema_1.sql
- lib/wcc/contentful/store/postgres_store/schema_2.sql
- lib/wcc/contentful/store/query.rb
- lib/wcc/contentful/store/query/condition.rb
- lib/wcc/contentful/store/query/interface.rb
- lib/wcc/contentful/store/rspec_examples.rb
- lib/wcc/contentful/store/rspec_examples/basic_store.rb
- lib/wcc/contentful/store/rspec_examples/include_param.rb
- lib/wcc/contentful/store/rspec_examples/locale_queries.rb
- lib/wcc/contentful/store/rspec_examples/nested_queries.rb
- lib/wcc/contentful/store/rspec_examples/operators.rb
- lib/wcc/contentful/store/rspec_examples/operators/eq.rb
- lib/wcc/contentful/store/rspec_examples/operators/in.rb
- lib/wcc/contentful/store/rspec_examples/operators/ne.rb
- lib/wcc/contentful/store/rspec_examples/operators/nin.rb
- lib/wcc/contentful/sync_engine.rb
- lib/wcc/contentful/sys.rb
- lib/wcc/contentful/test.rb
- lib/wcc/contentful/test/attributes.rb
- lib/wcc/contentful/test/double.rb
- lib/wcc/contentful/test/factory.rb
- lib/wcc/contentful/version.rb
- wcc-contentful.gemspec
homepage: https://github.com/watermarkchurch/wcc-contentful/wcc-contentful
licenses:
- MIT
metadata:
  documentation_uri: https://watermarkchurch.github.io/wcc-contentful/1.4/wcc-contentful
  rubygems_mfa_required: 'true'
post_install_message:
rdoc_options: []
require_paths:
- lib
required_ruby_version: !ruby/object:Gem::Requirement
  requirements:
  - - ">="
    - !ruby/object:Gem::Version
      version: '2.7'
required_rubygems_version: !ruby/object:Gem::Requirement
  requirements:
  - - ">="
    - !ruby/object:Gem::Version
      version: '0'
requirements: []
rubygems_version: 3.3.7
signing_key:
specification_version: 4
summary: '[![Gem Version](https://badge.fury.io/rb/wcc-contentful.svg)](https://rubygems.org/gems/wcc-contentful)
  [![Build Status](https://circleci.com/gh/watermarkchurch/wcc-contentful.svg?style=svg)](https://circleci.com/gh/watermarkchurch/wcc-contentful)
  [![Coverage Status](https://coveralls.io/repos/github/watermarkchurch/wcc-contentful/badge.svg?branch=master)](https://coveralls.io/github/watermarkchurch/wcc-contentful?branch=master)  Full
  documentation: https://watermarkchurch.github.io/wcc-contentful/latest/wcc-contentful/  #
  WCC::Contentful  An alternative to Contentful''s [contentful.rb ruby client](https://github.com/contentful/contentful.rb/),
  [contentful_model](https://github.com/contentful/contentful_model), and [contentful_rails](https://github.com/contentful/contentful_rails)
  gems all in one.  Table of Contents:  1. [Why?](#why-did-you-rewrite-the-contentful-ruby-stack)
  2. [Installation](#installation) 3. [Configuration](#configure) 4. [Usage](#usage)
  1. [Model API](#wcccontentfulmodel-api) 2. [Store API](#store-api) 3. [Direct CDN
  client](#direct-cdn-api-simpleclient) 4. [Accessing the APIs](#accessing-the-apis-within-application-code)
  5. [Architecture](#architecture) 1. [Client Layer](#client-layer) 2. [Store Layer](#store-layer)
  3. [Model Layer](#model-layer) 6. [Test Helpers](#test-helpers) 7. [Advanced Configuration
  Example](#advanced-configuration-example) 8. [Connecting to Multiple Spaces](#connecting-to-multiple-spaces-or-environments)
  9. [Development](#development) 10. [Contributing](#contributing) 11. [License](#license)   ##
  Why did you rewrite the Contentful ruby stack?  We started working with Contentful
  almost 5 years ago.  Since that time, Contentful''s ruby stack has improved, but
  there are still a number of pain points that we feel we have addressed better with
  our gem.  These are:  * [Low-level caching](#low-level-caching) * [Better integration
  with Rails & Rails models](#better-rails-integration) * [Automatic pagination and
  Automatic link resolution](#automatic-pagination-and-link-resolution) * [Automatic
  webhook management](#automatic-webhook-management)  Our gem no longer depends on
  any of the Contentful gems and interacts directly with the [Contentful CDA](https://www.contentful.com/developers/docs/references/content-delivery-api/)
  and [Content Management API](https://www.contentful.com/developers/docs/references/content-management-api/)
  over HTTPS.  ### Low-level caching  The wcc-contentful gem enables caching at two
  levels: the HTTP response using [Faraday HTTP cache middleware](https://github.com/sourcelevel/faraday-http-cache),
  and at the Entry level using the Rails cache and the [Sync API](https://www.contentful.com/developers/docs/references/content-delivery-api/#/reference/synchronization)
  to keep it up to date.  We''ve found these two cache layers to be very effective
  at reducing both round trip latency to the Content Delivery API, as well as reducing
  our monthly API request usage. (which reduces our overage charges.  Hooray!)  ####
  At the request/response level 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).  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: ```ruby config.connection = Faraday.new do |builder| builder.use :http_cache,
  shared_cache: false, store: ActiveSupport::Cache::MemoryStore.new(size: 512.megabytes),
  logger: Rails.logger, serializer: Marshal, instrumenter: ActiveSupport::Notifications  builder.use
  :gzip builder.response :logger, Rails.logger, headers: false, bodies: false if Rails.env.development?
  builder.request :instrumentation builder.adapter :typhoeus end ```  #### At the
  Entry level  Our stack has three layers, the middle layer being essentially a cache
  for individual Entry hashes parsed out of responses from the Delivery API.  We were
  able to add a caching layer here which stores entries retrieved over the Sync API,
  and responds to queries with cached versions of local content when possible.  We
  consider this to be our best innovation on the Contentful ruby stack.  We have successfully
  created caching layers using Memcached, Postgres, and an in-memory hash.  The architecture
  allows other caching implementations to be created fairly easily, and we have a
  set of rspec specs that can verify that a cache store behaves appropriately.  For
  more information, [see the documentation on the caching modes here](https://watermarkchurch.github.io/wcc-contentful/latest/wcc-contentful/WCC/Contentful/Store.html).  ###
  Better Rails Integration  When we initially got started with the Contentful ruby
  models, we encountered one problem that was more frustrating than all others: If
  a field exists in the content model, but the particular entry we''re working with
  does not have that field populated, then accessing that field raised a `NoMethodError`.  This
  caused us to litter our code with `if defined?(entry.my_field)` which is bad practice.  (Note:
  this has since been fixed in contentful.rb v2).  We decided it was better to not
  rely on `method_missing?` (what contentful.rb does), and instead to use `define_method`
  in an initializer to generate the methods for our models.  This has the advantage
  that calling `.instance_methods` on a model class includes all the fields present
  in the content model.  We also took advantage of Rails'' naming conventions to automatically
  infer the content type name based on the class name.  Thus in our code, we have
  `app/models/page.rb` which defines `class Page << WCC::Contentful::Model::Page`,
  and is automatically linked to the `page` content type ID.  (Note: this is overridable
  on a per-model basis)  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:  ```ruby class Page < WCC::Contentful::Model::Page
  include AlgoliaSearch  algoliasearch(index_name: ''pages'') do attribute(:title,
  :slug) ... end ```  ### Automatic Pagination and Link Resolution  Using the `contentful_model`
  gem, calling `Page.all.load` does not give you all Page entries if there are more
  than 100.  To get the next page you must call `.paginate` on the response.  By contrast,
  `Page.find_all` in the `wcc-contentful` gem gives you a [Lazy Enumerator](https://ruby-doc.org/core-2.5.0/Enumerator/Lazy.html).  As
  you iterate past the 100th entry, the enumerator will automatically fetch the next
  page.  If you only enumerate 99 entries (say with `.take(99)`), then the second
  page will never be fetched.  Similarly, if your Page references an asset, say `hero_image`,
  that field returns a `Link` object rather than the actual `Asset`.  You must either
  predefine how many links you need using `Page.load_children(3).all.load`, or detect
  that `hero_image` is a `Link` like `if @page.hero_image.is_a? Contentful::Link`
  and then call `.resolve` on the link.  We found all of that to be too cumbersome
  when we are down in a nested partial view template that may be invoked from multiple
  places.  The `wcc-contentful` gem, by contrast, automatically resolves a link when
  accessing the associated attribute.  So in our example above, `wcc-contentful` will
  **always** return a `WCC::Contentful::Asset` when calling `@page.hero_image`, even
  if it has to execute a query to cdn.contentful.com in order to fetch it.  Warning:
  This can easily lead to you exhausting your Contentful API quota if you do not carefully
  tune your cache, which you should be doing anyways!  The default settings will use
  the Rails cache to try to cache these resolutions, but *you are ultimately responsible
  for how many queries you execute!*  ### Automatic webhook management  The `wcc-contentful`
  gem, just like `contentful_rails`, provides an Engine to be mounted in your Rails
  routes file.  Unlike `contentful_rails`, if you also configure `wcc-contentful`
  with a Contentful Management Token and a public `app_url`, then on startup the `wcc-contentful`
  engine will reach out to the Contentful Management API and ensure that a webhook
  is configured to point to your app.  This is one less devops burden on you, and
  plays very nicely in with Heroku review apps.  ## Installation  Add this line to
  your application''s Gemfile:  ```ruby gem ''wcc-contentful'', require: ''wcc/contentful/rails''
  ```  If you''re not using rails, exclude the `require:` parameter.  ```ruby gem
  ''wcc-contentful'' ```  And then execute:  ``` $ bundle ```  Or install it yourself:  ```
  $ gem install wcc-contentful ```  ## Configure  Put this in an initializer:  ```ruby
  # config/initializers/wcc_contentful.rb WCC::Contentful.configure do |config| config.access_token
  = <CONTENTFUL_ACCESS_TOKEN> config.space = <CONTENTFUL_SPACE_ID> end  WCC::Contentful.init!
  ```  All configuration options can be found [in the rubydoc under WCC::Contentful::Configuration](https://watermarkchurch.github.io/wcc-contentful/latest/wcc-contentful/WCC/Contentful/Configuration)   ##
  Usage  ### WCC::Contentful::Model API  The WCC::Contentful::Model API exposes Contentful
  data as a set of dynamically generated Ruby objects.  These objects are based on
  the content types in your Contentful space.  All these objects are generated by
  `WCC::Contentful.init!`  The following examples show how to use this API to find
  entries of the `page` content type:  ```ruby # app/models/page.rb class Page < WCC::Contentful::Model::Page  #
  You can add additional methods here end  # Find objects by id Page.find(''1E2ucWSdacxxf233sfa3'')
  # => #<Page:0x0000000005c71a78 @created_at=2018-04-16 18:41:17 UTC...>  # Find objects
  by field Page.find_by(slug: ''/some-slug'') # => #<Page:0x0000000005c71a78 @created_at=2018-04-16
  18:41:17 UTC...>  # Use operators to filter by a field # must use full notation
  for sys attributes (except ID) Page.find_all(''sys.created_at'' => { lte: Date.today
  }) # => [#<Page:0x0000000005c71a78 @created_at=2018-04-16 18:41:17 UTC...>, ...
  ]  # Nest queries to mimick joins Page.find_by(subpages: { slug: ''/some-slug''
  }) # => #<Page:0x0000000005c71a78 @created_at=2018-04-16 18:41:17 UTC...>  # Fetch
  an entry in a different locale spanish_homepage = Page.find_by(slug: ''/'', options:
  { locale: ''es-US'' }) # => #<Page:0x0000000005c71a78 @created_at=2018-04-16 18:41:17
  UTC...> spanish_homepage.title # => Esta es la página principal  # Pass the preview
  flag to use the preview client (must have set preview_token config param) preview_redirect
  = WCC::Contentful::Model::Redirect.find_by({ slug: ''draft-redirect'' }, preview:
  true) # => #<WCC::Contentful::Model::Redirect:0x0000000005d879ad @created_at=2018-04-16
  18:41:17 UTC...> preview_redirect_object.href # => ''http://www.somesite.com/slug-for-redirect''
  ```  See the {WCC::Contentful::Model} documentation for more details.  ### Store
  API  The Store layer is used by the Model API to access Contentful data in a raw
  form. The Store layer returns entries as hashes parsed from JSON, conforming to
  the object structure returned from the Contentful CDN.  The following examples show
  how to use the Store API to retrieve raw data from the store:  ```ruby store = WCC::Contentful::Services.instance.store
  # => #<WCC::Contentful::Store::CDNAdapter:0x00007fb92a221498  store.find(''5FsqsbMECsM62e04U8sY4Y'')
  # => {"sys"=> #  ... # "fields"=> # ...}  store.find_by(content_type: ''page'',
  filter: { slug: ''/some-slug'' }) # => {"sys"=> #  ... # "fields"=> # ...}  query
  = store.find_all(content_type: ''page'').eq(''group'', ''some-group'') # => #<WCC::Contentful::Store::CDNAdapter::Query:0x00007fa3d40b84f0
  query.first # => {"sys"=> #  ... # "fields"=> # ...} query.result # => #<Enumerator::Lazy:
  ...> query.result.force # => [{"sys"=> ...}, {"sys"=> ...}, ...] ```  The store
  layer, while superficially similar to the Contentful API, tries to present a different
  "View" over the data which is more compatible with the Model layer.  It resolves
  includes by actually replacing the in-memory `Link` objects with their linked `Entry`
  representations.  This lets you traverse the links naturally using `#dig` or `#[]`:  ```ruby
  # Include to a depth of 3 to make sure it''s included homepage = store.find_by(slug:
  ''/'', include: 3) # Traverse through the top nav menu => menu button 0 => about
  page about_page = homepage.dig(''fields'', ''nav_menu'', ''fields'', ''buttons'',
  0, ''fields'', ''page'') ```   See the {WCC::Contentful::Store} documentation for
  more details.  ### Direct CDN API (SimpleClient)  The SimpleClient is the bottom
  layer, and is used to get raw data directly from the Contentful CDN.  It handles
  response parsing and paging, but does not resolve links or transform the result
  into a Model class.  The following examples show how to use the SimpleClient to
  retrieve data directly from the Contentful CDN:  ```ruby client = WCC::Contentful::Services.instance.client
  # => #<WCC::Contentful::SimpleClient::Cdn:0x00007fa3cde89310  response = client.entry(''5FsqsbMECsM62e04U8sY4Y'')
  # => #<WCC::Contentful::SimpleClient::Response:0x00007fa3d103a4e0 response.body
  # => "{\n  \"sys\": {\n ... response.raw # => {"sys"=> #  ... # "fields"=> # ...}  client.asset(''5FsqsbMECsM62e04U8sY4Y'').raw
  # => {"sys"=> #  ... # "fields"=> # ...}  response = client.entries(''fields.group''
  => ''some-group'', ''limit'' => 5) # => #<WCC::Contentful::SimpleClient::Response:0x00007fa3d103a4e0
  response.count # => 99 response.first # => {"sys"=> #  ... # "fields"=> # ...} response.items
  => #<Enumerator::Lazy: ...> response.items.count  # Careful! This evaluates the
  lazy iterator and gets all pages # => 99  response.includes # => {"4xNnFJ77egkSMEogE2yISa"=>
  #   {"sys"=> ...} #  "6Fwukxxkxa6qQCC04WCaqg"=> #   {"sys"=> ...} #   ...} ```  The
  client handles Paging automatically within the lazy iterator returned by #items.
  This lazy iterator does not respect the `limit` param - that param is only passed
  through to the API to set the page size.  If you truly want a limited subset of
  response items, use [`response.items.take(n)`](https://ruby-doc.org/core-2.5.3/Enumerable.html#method-i-take)  Entries
  included via the `include` parameter are made available on the #includes field.  This
  is a hash of `<entry ID> => <raw entry>` and makes it easy to grab links.  This
  hash is added to lazily as you enumerate the pages.  See the {WCC::Contentful::SimpleClient}
  documentation for more details.  ### Accessing the APIs within application code  The
  Model API is best exposed by defining your own model classes in the `app/models`
  directory which inherit from the WCC::Contentful models.  ```ruby # app/models/page.rb
  class Page < WCC::Contentful::Model::Page  # You can add additional methods here
  end  # app/controllers/pages_controller.rb class PagesController < ApplicationController
  def show @page = Page.find_by(slug: params[:slug]) raise Exceptions::PageNotFoundError,
  params[:slug] unless @page end end ```  The {WCC::Contentful::Services} singleton
  gives access to the other configured services. You can also include the {WCC::Contentful::ServiceAccessors}
  concern to define these services as attributes in a class.  ```ruby class MyJob
  < ApplicationJob include WCC::Contentful::ServiceAccessors  def perform Page.find(...)  store.find(...)  client.entries(...)
  end end ```  ## Architecture  ![wcc-contentful diagram](./doc-static/wcc-contentful.png)  From
  the bottom up:  ### Client Layer  The {WCC::Contentful::SimpleClient} provides methods
  to access the [Contentful  Content Delivery API](https://www.contentful.com/developers/docs/references/content-delivery-api/)
  through your favorite HTTP client gem.  The SimpleClient expects an Adapter that
  conforms to the Faraday interface.  Creating a SimpleClient to connect using different
  credentials, or to connect without setting up all the rest of WCC::Contentful, is
  easy:  ```ruby WCC::Contentful::SimpleClient::Cdn.new( # required access_token:
  ''xxxx'', space: ''1234'', # optional environment: ''staging'', # omit to use master
  rate_limit_wait_timeout: 10, instrumentation: ActiveSupport::Notifications, connection:
  Faraday.new { |builder| ... }, ) ```  You can also create a {WCC::Contentful::SimpleClient::Preview}
  to talk to the Preview API, or a {WCC::Contentful::SimpleClient::Management} to
  talk to the Management API.  ### Store Layer  The Store Layer represents the data
  store where Contentful entries are kept for querying.  By default, `WCC::Contentful.init!`
  creates a {WCC::Contentful::Store::CDNAdapter} which uses a {WCC::Contentful::SimpleClient::Cdn}
  instance to query entries from the [Contentful Content Delivery API](https://www.contentful.com/developers/docs/references/content-delivery-api/).  You
  can also query entries from another source like Postgres or an in-memory hash if
  your data is small enough.  You can also implement your own store if you want!  The
  gem contains a suite of RSpec shared examples that give you a baseline for implementing
  your own store. In your RSpec suite: ```ruby # frozen_string_literal: true  require
  ''my_store'' require ''wcc/contentful/store/rspec_examples''  RSpec.describe MyStore
  do it_behaves_like ''contentful store'', { # Set which store features your store
  implements.   nested_queries: true,  # Does your store implement JOINs? include_param:
  true    # Does your store resolve links when given the :include option? } ```  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 it with
  the latest data via the [Contentful Sync API](https://www.contentful.com/developers/docs/references/content-delivery-api/#/reference/synchronization).  For
  example, the {WCC::Contentful::Store::MemoryStore} uses this to update the hash
  with the newest version of an entry, or delete an entry out of the hash.  #### Store
  Middleware  The store layer is made up of a base store (which implements {WCC::Contentful::Store::Interface}),
  and some required middleware.  The list of default middleware applied to each store
  is found in {WCC::Contentful::Store::Factory.default_middleware}  To create your
  own middleware simply include {WCC::Contentful::Middleware::Store}.  Then you can
  optionally implement the `#transform` and `#select?` methods:  ```ruby class MyMiddleware
  include WCC::Contentful::Middleware::Store  # Called for each entry that is requested
  out of the backing store.  You can modify the entry and return it to the # next
  layer. def transform(entry, options) # Do something with the entry... # Make sure
  you return it at the end! entry end  def select?(entry, options) # Choose whether
  this entry should exist or not.  If you return false here, then the entry will act
  as though it # were archived in Contentful. entry.dig(''fields'', ''hide_until'')
  > Time.zone.now end end ```  You can also override any of the standard Store methods.  To
  apply the middleware, call `use` when configuring the store:  ```ruby config.store
  :direct do use MyMiddleware, param1: ''xxx'' end ```  The most useful middleware
  is the {WCC::Contentful::Middleware::Store::CachingMiddleware}, which enables `:lazy_sync`
  mode (see {WCC::Contentful::Configuration#store})  ### Model Layer  This is the
  global top layer where your Rails app looks up content similarly to ActiveModel.  The
  models are namespaced under the root class {WCC::Contentful::Model}. Each model''s
  implementation of `.find`, `.find_by`, and `.find_all` simply call into the configured
  Store.  Models can be initialized directly with the `.new` method, by passing in
  a hash: ```ruby entry = { ''sys'' => ..., ''fields'' => ... } Page.new(entry) ```  **The
  initializer must receive a localized entry**.  An entry found using a `locale=*`
  query must be transformed to a localized entry using the {WCC::Contentful::EntryLocaleTransformer}
  before passing it to your model:  ```ruby entry = client.entry(''1234'', locale:
  ''*'').raw localized_entry = WCC::Contentful::EntryLocaleTransformer.transform_to_locale(entry,
  ''en-US'') Page.new(localized_entry) ```  The Store layer ensures that localized
  entries are returned using the {WCC::Contentful::Middleware::Store::LocaleMiddleware}.  The
  main benefit of the Model layer is lazy link resolution.  When a model''s property
  is accessed, if that property is a link that has not been resolved yet (for example
  using the `include: n` parameter on `.find_by`), the model will automatically call
  `#find` on the store to resolve that linked entry.  Note that this can easily result
  in lots of CDN calls to Contentful!  To optimize this you should use the `include`
  parameter and/or use a different store.  ## Test Helpers  To use the test helpers,
  include the following in your rails_helper.rb:  ```ruby require ''wcc/contentful/rspec''
  ```  This adds the following helpers to all your specs:  ```ruby ## # Builds a in-memory
  instance of the Contentful model for the given content_type. # All attributes that
  are known to be required fields on the content type # will return a default value
  based on the field type. instance = contentful_create(''my-content-type'', my_field:
  ''some-value'') # => #<WCC::Contentful::Model::MyContentType:0x0000000005c71a78
  @created_at=2018-04-16 18:41:17 UTC...>  instance.my_field # => "some-value"  instance.other_required_field
  # => "default-value"  instance.other_optional_field # => nil  instance.not_a_field
  # NoMethodError: undefined method `not_a_field'' for #<MyContentType:0x00007fbac81ee490>  ##
  # Builds a rspec double of the Contentful model for the given content_type. # All
  attributes that are known to be required fields on the content type # will return
  a default value based on the field type. dbl = contentful_double(''my-content-type'',
  my_field: ''other-value'') # => #<Double (anonymous)>  dbl.my_field # => "other-value"  dbl.other_optional_field
  # => nil  dbl.not_a_field # => #<Double (anonymous)> received unexpected message
  :not_a_field with (no args)  ## # Builds out a fake Contentful entry for the given
  content type, and then # stubs the Model API to return that content type for `.find`
  and `.find_by` # query methods. stubbed = contentful_stub(''my-content-type'', id:
  ''1234'', my_field: ''test'')  WCC::Contentful::Model.find(''1234'') == stubbed
  # => true  MyContentType.find(''1234'') == stubbed # => true  MyContentType.find_by(my_field:
  ''test'') == stubbed # => true ```  ## Advanced Configuration Example  Here''s an
  example containing all the configuration options, and a sample setup for automatic
  deployment to Heroku.  This is intended to make you aware of what is possible, and
  not as a general recommendation of what your setup should look like.  ```ruby #
  config/initializers/wcc_contentful.rb WCC::Contentful.configure do |config| config.access_token
  = ENV[''CONTENTFUL_ACCESS_TOKEN''] config.space = ENV[''CONTENTFUL_SPACE_ID''] config.environment
  = ENV[''CONTENTFUL_ENVIRONMENT''] config.preview_token = ENV[''CONTENTFUL_PREVIEW_ACCESS_TOKEN'']  #
  You may or may not want to provide this to your production server... config.management_token
  = ENV[''CONTENTFUL_MANAGEMENT_TOKEN''] unless Rails.env.production?  config.app_url
  = "https://#{ENV[''HOSTNAME'']}" config.webhook_username = ''my-app-webhook'' config.webhook_password
  = Rails.application.secrets.webhook_password config.webhook_jobs << MyOnWebhookJob  config.store
  = :lazy_sync, Rails.cache if Rails.env.production? # config.store = MyCustomStore.new  #
  Use a custom Faraday connection config.connection = Faraday.new do |builder| f.request
  :retry f.request MyFaradayRequestAdapter.new ... end # OR implement some adapter
  like this to use another HTTP client config.connection = MyNetHttpAdapter.new  config.update_schema_file
  = :never end  WCC::Contentful.init! ```  For Heroku:  ```yaml # Procfile web: bundle
  exec rails s worker: bundle exec sidekiq release: bin/release ```  ```sh # bin/release
  #!/bin/sh  set -e  echo "Migrating database..." bin/rake db:migrate  echo "Migrating
  contentful..." migrations_to_be_run=$( ... ) # somehow figure this out node_modules/.bin/contentful-migration
  \ -s $CONTENTFUL_SPACE_ID -a $CONTENTFUL_MANAGEMENT_TOKEN \ -y -p "$migrations_to_be_run"  echo
  "Updating schema file..." rake wcc_contentful:download_schema ```  All configuration
  options can be found [in the rubydoc](https://www.rubydoc.info/gems/wcc-contentful/WCC/Contentful/Configuration)
  under {WCC::Contentful::Configuration}  ## Connecting to multiple spaces or environments  When
  initializing the WCC::Contentful gem using `WCC::Contentful.init!`, the gem will
  by default connect to the single Contentful space that you specify in the `WCC::Contentful.configure`
  step.  However the gem is also capable of connecting to multiple spaces within the
  same ruby process!  You just have to create and initialize a namespace.  The {WCC::Contentful::ModelAPI}
  concern makes this straightforward.  Start by creating your Namespace and including
  the concern: ```ruby # lib/my_second_space.rb  # Note: This class must be in the
  "lib" folder in :zeitwerk mode, otherwise Rails 6+ will unload all your constants
  # that were created in the initializer.  Your models which subclass this namespace
  may reside in the app/models directory. class MySecondSpace include WCC::Contentful::ModelAPI
  end  # app/models/other_page.rb class OtherPage < MySecondSpace::Page end ```  Then
  configure it in an initializer: ```ruby # config/initializers/my_second_space.rb
  MySecondSpace.configure do |config| # Make sure to point to a different schema file
  from your first space! config.schema_file = Rails.root.join(''db/second-contentful-schema.json'')  config.access_token
  = ENV[''SECOND_CONTENTFUL_ACCESS_TOKEN''] config.preview_token = ENV[''SECOND_CONTENTFUL_PREVIEW_ACCESS_TOKEN'']
  config.space = ENV[''SECOND_CONTENTFUL_SPACE_ID''] config.environment = ENV[''CONTENTFUL_ENVIRONMENT'']
  end  # Ensure that models are reloaded in Rails development mode Rails.application.config.to_prepare
  do MySecondSpace.reload! end ```  Finally, use it: ```ruby OtherPage.find(''1234'')
  # GET https://cdn.contentful.com/spaces/other-space/environments/other-env/entries/1234
  # => #<OtherPage:0x0000000005c71a78 @created_at=2018-04-16 18:41:17 UTC...>  Page.find(''1234'')
  # GET https://cdn.contentful.com/spaces/first-space/environments/first-env/entries/1234
  # => #<Page:0x0000000001271b70 @created_at=2018-04-15 12:02:14 UTC...> ```  The
  ModelAPI defines a second stack of services that you can access for lower level
  connections: ```ruby store = MySecondSpace.services.store # => #<WCC::Contentful::Store::CDNAdapter:0x00007f888edac118
  client = MySecondSpace.services.client # => #<WCC::Contentful::SimpleClient::Cdn:0x00007f88942a8888
  preview_client = MySecondSpace.services.preview_client # => #<WCC::Contentful::SimpleClient::Preview:0x00007f888ccafa00
  sync_engine = MySecondSpace.services.sync_engine # => #<WCC::Contentful::SyncEngine:0x00007f88960b6b40
  ``` Note that the above services are not accessible on {WCC::Contentful::Services.instance}
  or via the {WCC::Contentful::ServiceAccessors}.  #### Important Note when using
  Zeitwerk with Rails 6+ When using Rails >= 6 with `config.autoloader = :zeitwerk`,
  Rails will remove any models defined in `app/models` after initialization and then
  load them again when they are referenced.  If you `include WCC::Contentful::ModelAPI`
  in a class defined inside the `app` directory, this will have the effect of deleting
  all configuration that was set in the initializer as well as the constants generated
  from your schema. This will result in one of two errors:  * `NameError (uninitialized
  constant MySecondSpace::MyContentType)`   if you try to reference a subclass such
  as `MyContentType < MySecondSpace::MyContentType` * `ArgumentError (Not yet configured!)`
  if you try to `MySecondSpace.find(''xxxx'')` to load an Entry or Asset  The solution
  is to have your secondary namespace in a folder which is not in the `autoload_paths`.
  We suggest using `lib`, which will work so long as you have not added the `lib`
  folder to the `autoload_paths` as some uninformed StackOverflow answers suggest
  you do.  ### Using a sync store with a second space  If you use something other
  than the CDNAdapter with your second space, you will need to find a way to trigger
  `MySecondSpace.services.sync_engine.next` to keep it up-to-date.  The {WCC::Contentful::Engine}
  will only manage the global SyncEngine configured by the global {WCC::Contentful.configure}.  The
  easiest way to do this is to set up your own Rails route to respond to Contentful
  webhooks and then configure the second Contentful space to send webhooks to this
  route. You could also do this by triggering it periodically in a background job
  using Sidekiq and [sidekiq-scheduler](https://github.com/Moove-it/sidekiq-scheduler).  ##
  Development  After checking out the repo, run `bin/setup` to install dependencies.
  Then, run `bundle exec rspec` to run the tests. You can also run `bin/console` for
  an interactive prompt that will allow you to experiment.  ## Contributing  Bug reports
  and pull requests are welcome on GitHub at https://github.com/watermarkchurch/wcc-contentful.  The
  developers at Watermark Community Church have pledged to govern their interactions
  with each other, with their clients, and with the larger wcc-contentful user community
  in accordance with the "instruments of good works" from chapter 4 of The Rule of
  St. Benedict (hereafter: "The Rule"). This code of ethics has proven its mettle
  in thousands of diverse communities for over 1,500 years, and has served as a baseline
  for many civil law codes since the time of Charlemagne.  [See the full Code of Ethics](https://github.com/watermarkchurch/wcc-contentful/blob/master/CODE_OF_ETHICS.md)  ##
  License  The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).'
test_files: []