Code Monkey home page Code Monkey logo

contentful_rails's Introduction

Contentful Rails

A collection of useful things to help make it easier to integrate Contentful into your Rails app. It includes view helpers, a Webhook handler, caching, and a Rails Engine to hook it all together.

This is a work in progress. It relies on the contentful_model gem (http://github.com/contentful/contentful_model)

What is Contentful?

Contentful provides a content infrastructure for digital teams to power content in websites, apps, and devices. Unlike a CMS, Contentful was built to integrate with the modern software stack. It offers a central hub for structured content, powerful management and delivery APIs, and a customizable web app that enable developers and content creators to ship digital products faster.

Configuration

ContentfulRails accepts a block for configuration. Best done in a Rails initializer.

ContentfulRails.configure do |config|
  config.authenticate_webhooks = true # false here would allow the webhooks to process without basic auth
  config.webhooks_username = "a basic auth username"
  config.webhooks_password = "a basic auth password"
  config.access_token = "your access token"
  config.preview_access_token = "your preview access token"
  config.management_token = "your management access token"
  config.space = "your space ID"
  config.environment = "your environment ID"
  config.contentful_options = "hash of options"
end

Note that you don't have to separately configure ContentfulModel - adding the access tokens / space ID / options here will pass to ContentfulModel in an initializer in the Rails engine.

The default is to authenticate the webhooks; probably a smart move to host on an HTTPS endpoint too.

Entry Mapping

By default, ContentfulRails will try to define your entry_mapping configuration for you. It does this by iterating through the descendents of the base class ContentfulModel::Base during initialization. In order to ensure these classes are loaded by this time, it will call eager_load! for the entire application. If this is not desired, you can set the eager_load_entry_mapping config to false set your entry mapping manually by setting the entry_mapping config as described here.

ContentfulRails.configure do |config|
  ...
  config.eager_load_entry_mapping = false
  config.contentful_options = {
    entry_mapping: {
      'article' => Article,
      ...
    }
  }
end

Note: If you do not define the entry mapping in your configuration, the webhook cache expiration will likely not work as expected

Allowing 'Russian Doll' style caching on Entries

The issue with 'Russian Doll' caching in Rails is that it requires a hit on the database to check the updated_at timestamp of an object.

This is obviously expensive when the object is called over an API. So this gem wraps caches updated_at locally, and checks that first on subsequent calls.

Foo.updated_at #returns a timestamp from cache, or from the API if no cache exists

Webhooks Endpoint

If there's a new version of an entry we need to expire the timestamp from the cache.

This gem includes a controller endpoint for Contentful to POST back to.

To make use of this in your app:

routes.rb

Mount the ContentfulRails engine at your preferred url:

mount ContentfulRails::Engine => '/contentful' #feel free to choose a different endpoint name

This will give you 2 routes:

/contentful/webhooks - the URL for contentful to post back to. /contentful/webhooks/debug - a development-only URL to check you have mounted the engine properly :-)

What the webhook handler does

At the moment all this does is delete the timestamp cache entry, which means that a subsequent call to updated_at calls the API.

View Helpers

Contentful has a really nice url-based image manipulation API.

To take advantage of this, there's a custom Redcarpet renderer which allows you to pass the image parameters you want into the call to a parse_markdown() method.

In your application_controller.rb:

helper ContentfulRails::MarkdownHelper

This allows you to call parse_markdown(@your_markdown) and get HTML. Note that out of the box, the parse_markdown() is really permissive and allows you to put HTML in the Contentful markdown fields. This might not be what you want.

Manipulating images

To manipulate images which are referenced in your markdown, you can pass the following into the parse_markdown() call.

parse_markdown(@your_markdown, image_options: { width: 1024, height: 1024 })

The image_options parameter takes the following options (some are mutually exclusive. Read the instructions here):

  • :width
  • :height
  • :fit
  • :focus
  • :corner_radius
  • :quality

Subclassing the MarkdownRenderer class

Sometimes you might want to apply some specific class, markup or similar to an html entity when it's being processed. With RedCarpet that's dead easy.

Just subclass the ContentfulRails::MarkdownRenderer class, and call any methods you need.

class MyRenderer < ContentfulRails::MarkdownRenderer
  # If you want to pass options into your renderer, you need to overload initialize()
  def initialize(opts)
    @options = opts
    super
  end

  # If you want to do something special with links:
  def link(link,title,content)
    # Add a class name to all links, for example
    class_name = "my-link-class-name"
    content_tag(:a, content, href: link, title: title, class: class_name)
  end
end

You can overload any methods exposed in RedCarpet.

Preview API

ContenfulRails can be set up to use the Contenful Preview API, and has the option of protecting that content behind basic authentication. To enable the preview api, add the below settings to your configuration.

ContentfulRails.configure do |config|
  config.enable_preview_domain = true # use the preview domain
  config.preview_access_token = "your preview access token"
  config.preview_username = "a basic auth username"
  config.preview_password = "a basic auth password"
end

If you site is already protected with another form of authentication, then leave preview_username and preview_password unset. This will skip the authentication step when displaying preview content.

To Do

Some things would be nice to do:

  • Tests :-)
  • Make caching the timestamp optional in the configuration
  • Implement a method on ContentfulModel to simulate a parent-child relationship, so we can invalidate caches for parent items

Licence

Licence is MIT. Please see MIT-LICENCE in this repo.

Contributing

Please feel free to contribute!

  • Fork this repo
  • Make your changes
  • Commit
  • Create a PR

contentful_rails's People

Contributors

andyhansen avatar bazzel avatar clowder avatar cprodhomme avatar damienh avatar dlitvakb avatar quasi-presence avatar reidcooper avatar sebbean avatar shaug avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

contentful_rails's Issues

Webhook Engine does not provide the /debug route as expected

Upon mounting the engine, I am unable to access the /webhooks/debug route.

My suggested fix in contentful_rails/development_constraint.rb:

module ContentfulRails
  # Constraint for ensuring development origin
  class DevelopmentConstraint
    # Returns if current request is coming from development environment or localhost
    def self.matches?(request)
      Rails.env =~ %r{development} && (request.remote_ip =~ %r{127.0.0} || request.remote_ip =~ %r{^::1$})
    end
  end
end

Due to https://stackoverflow.com/a/12384762/3674071 stating that the %r{} syntax allows you to use / without having to escape them. We don't want to check whether or not the Rails.env = "/development/", but if its "development". Applies concept to the request.remote_ip assertions as well.

Engine fails to mount

I created a new Rails 4.1.8 project, added contentful_rails to the gemfile, and was unable to start the Rails server. The stack trace is below:

=> Booting WEBrick
=> Rails 4.1.8 application starting in development on http://0.0.0.0:3000
=> Run `rails server -h` for more startup options
=> Notice: server is listening on all interfaces (0.0.0.0). Consider using 127.0.0.1 (--binding option)
=> Ctrl-C to shutdown server
Exiting
/Users/r626356/.rvm/gems/ruby-2.2.1@swh-store/gems/contentful_rails-0.2.2.1/lib/contentful_rails/engine.rb:7:in `block in <class:Engine>': undefined method `configuration' for ContentfulRails:Module (NoMethodError)
    from /Users/r626356/.rvm/gems/ruby-2.2.1@swh-store/gems/activesupport-4.1.8/lib/active_support/lazy_load_hooks.rb:36:in `call'
    from /Users/r626356/.rvm/gems/ruby-2.2.1@swh-store/gems/activesupport-4.1.8/lib/active_support/lazy_load_hooks.rb:36:in `execute_hook'
    from /Users/r626356/.rvm/gems/ruby-2.2.1@swh-store/gems/activesupport-4.1.8/lib/active_support/lazy_load_hooks.rb:45:in `block in run_load_hooks'
    from /Users/r626356/.rvm/gems/ruby-2.2.1@swh-store/gems/activesupport-4.1.8/lib/active_support/lazy_load_hooks.rb:44:in `each'
    from /Users/r626356/.rvm/gems/ruby-2.2.1@swh-store/gems/activesupport-4.1.8/lib/active_support/lazy_load_hooks.rb:44:in `run_load_hooks'
    from /Users/r626356/.rvm/gems/ruby-2.2.1@swh-store/gems/railties-4.1.8/lib/rails/application/bootstrap.rb:76:in `block in <module:Bootstrap>'
    from /Users/r626356/.rvm/gems/ruby-2.2.1@swh-store/gems/railties-4.1.8/lib/rails/initializable.rb:30:in `instance_exec'
    from /Users/r626356/.rvm/gems/ruby-2.2.1@swh-store/gems/railties-4.1.8/lib/rails/initializable.rb:30:in `run'
    from /Users/r626356/.rvm/gems/ruby-2.2.1@swh-store/gems/railties-4.1.8/lib/rails/initializable.rb:55:in `block in run_initializers'
    from /Users/r626356/.rvm/rubies/ruby-2.2.1/lib/ruby/2.2.0/tsort.rb:226:in `block in tsort_each'
    from /Users/r626356/.rvm/rubies/ruby-2.2.1/lib/ruby/2.2.0/tsort.rb:348:in `block (2 levels) in each_strongly_connected_component'
    from /Users/r626356/.rvm/rubies/ruby-2.2.1/lib/ruby/2.2.0/tsort.rb:429:in `each_strongly_connected_component_from'
    from /Users/r626356/.rvm/rubies/ruby-2.2.1/lib/ruby/2.2.0/tsort.rb:347:in `block in each_strongly_connected_component'
    from /Users/r626356/.rvm/rubies/ruby-2.2.1/lib/ruby/2.2.0/tsort.rb:345:in `each'
    from /Users/r626356/.rvm/rubies/ruby-2.2.1/lib/ruby/2.2.0/tsort.rb:345:in `call'
    from /Users/r626356/.rvm/rubies/ruby-2.2.1/lib/ruby/2.2.0/tsort.rb:345:in `each_strongly_connected_component'
    from /Users/r626356/.rvm/rubies/ruby-2.2.1/lib/ruby/2.2.0/tsort.rb:224:in `tsort_each'
    from /Users/r626356/.rvm/rubies/ruby-2.2.1/lib/ruby/2.2.0/tsort.rb:203:in `tsort_each'
    from /Users/r626356/.rvm/gems/ruby-2.2.1@swh-store/gems/railties-4.1.8/lib/rails/initializable.rb:54:in `run_initializers'
    from /Users/r626356/.rvm/gems/ruby-2.2.1@swh-store/gems/railties-4.1.8/lib/rails/application.rb:300:in `initialize!'
    from /Users/r626356/workspace/contentful-test/config/environment.rb:5:in `<top (required)>'
    from /Users/r626356/workspace/contentful-test/config.ru:3:in `require'
    from /Users/r626356/workspace/contentful-test/config.ru:3:in `block in <main>'
    from /Users/r626356/.rvm/gems/ruby-2.2.1@swh-store/gems/rack-1.5.5/lib/rack/builder.rb:55:in `instance_eval'
    from /Users/r626356/.rvm/gems/ruby-2.2.1@swh-store/gems/rack-1.5.5/lib/rack/builder.rb:55:in `initialize'
    from /Users/r626356/workspace/contentful-test/config.ru:in `new'
    from /Users/r626356/workspace/contentful-test/config.ru:in `<main>'
    from /Users/r626356/.rvm/gems/ruby-2.2.1@swh-store/gems/rack-1.5.5/lib/rack/builder.rb:49:in `eval'
    from /Users/r626356/.rvm/gems/ruby-2.2.1@swh-store/gems/rack-1.5.5/lib/rack/builder.rb:49:in `new_from_string'
    from /Users/r626356/.rvm/gems/ruby-2.2.1@swh-store/gems/rack-1.5.5/lib/rack/builder.rb:40:in `parse_file'
    from /Users/r626356/.rvm/gems/ruby-2.2.1@swh-store/gems/rack-1.5.5/lib/rack/server.rb:277:in `build_app_and_options_from_config'
    from /Users/r626356/.rvm/gems/ruby-2.2.1@swh-store/gems/rack-1.5.5/lib/rack/server.rb:199:in `app'
    from /Users/r626356/.rvm/gems/ruby-2.2.1@swh-store/gems/railties-4.1.8/lib/rails/commands/server.rb:50:in `app'
    from /Users/r626356/.rvm/gems/ruby-2.2.1@swh-store/gems/rack-1.5.5/lib/rack/server.rb:314:in `wrapped_app'
    from /Users/r626356/.rvm/gems/ruby-2.2.1@swh-store/gems/railties-4.1.8/lib/rails/commands/server.rb:130:in `log_to_stdout'
    from /Users/r626356/.rvm/gems/ruby-2.2.1@swh-store/gems/railties-4.1.8/lib/rails/commands/server.rb:67:in `start'
    from /Users/r626356/.rvm/gems/ruby-2.2.1@swh-store/gems/railties-4.1.8/lib/rails/commands/commands_tasks.rb:81:in `block in server'
    from /Users/r626356/.rvm/gems/ruby-2.2.1@swh-store/gems/railties-4.1.8/lib/rails/commands/commands_tasks.rb:76:in `tap'
    from /Users/r626356/.rvm/gems/ruby-2.2.1@swh-store/gems/railties-4.1.8/lib/rails/commands/commands_tasks.rb:76:in `server'
    from /Users/r626356/.rvm/gems/ruby-2.2.1@swh-store/gems/railties-4.1.8/lib/rails/commands/commands_tasks.rb:40:in `run_command!'
    from /Users/r626356/.rvm/gems/ruby-2.2.1@swh-store/gems/railties-4.1.8/lib/rails/commands.rb:17:in `<top (required)>'
    from /Users/r626356/workspace/contentful-test/bin/rails:8:in `require'
    from /Users/r626356/workspace/contentful-test/bin/rails:8:in `<top (required)>'
    from /Users/r626356/.rvm/gems/ruby-2.2.1@swh-store/gems/spring-1.3.6/lib/spring/client/rails.rb:28:in `load'
    from /Users/r626356/.rvm/gems/ruby-2.2.1@swh-store/gems/spring-1.3.6/lib/spring/client/rails.rb:28:in `call'
    from /Users/r626356/.rvm/gems/ruby-2.2.1@swh-store/gems/spring-1.3.6/lib/spring/client/command.rb:7:in `call'
    from /Users/r626356/.rvm/gems/ruby-2.2.1@swh-store/gems/spring-1.3.6/lib/spring/client.rb:26:in `run'
    from /Users/r626356/.rvm/gems/ruby-2.2.1@swh-store/gems/spring-1.3.6/bin/spring:48:in `<top (required)>'
    from /Users/r626356/.rvm/gems/ruby-2.2.1@swh-store/gems/spring-1.3.6/lib/spring/binstub.rb:11:in `load'
    from /Users/r626356/.rvm/gems/ruby-2.2.1@swh-store/gems/spring-1.3.6/lib/spring/binstub.rb:11:in `<top (required)>'
    from /Users/r626356/workspace/contentful-test/bin/spring:13:in `require'
    from /Users/r626356/workspace/contentful-test/bin/spring:13:in `<top (required)>'
    from bin/rails:3:in `load'
    from bin/rails:3:in `<main>'

Is this an official gem?

Is this gem part of Contentful's official support for for Ruby?

If so, are there any policies on how often issues are responded to or how often PRs are merged? I get the feeling this isn't a priority for Contentful to maintain based on the frequency of activity and lack of tests.

Webhooks Rails 5

Hi,

I get errors with the webhooks engine in Rails 5 (on master)

Processing by ContentfulRails::WebhooksController#create as JSON
NoMethodError (undefined method `[]' for nil:NilClass): 
 vendor/bundle/ruby/2.3.0/bundler/gems/contentful_rails-d1d3c62141da/lib/contentful_rails/engine.rb:39:in `block (2 levels) in <class:Engine>' 

"Only Redis glob strings are supported: " on unpublish when using redis as cache store

Pretty sure this is the issue:

ActionController::Base.new.expire_fragment(%r{/.*#{payload[:sys][:id]}.*/})

Redis doesn't support regex and instead supports "glob" strings for matching expiration (and according to the Rails docs for expire_fragment, neither does memcache): https://apidock.com/rails/v6.1.3.1/AbstractController/Caching/Fragments/expire_fragment

I'm not sure the best method to fix this; if there is just one key and it is deterministic, passing a string instead of a regex would be better supported.

contentful_rails requires contentful_model, but doesn't.

Unless the contentful_model gem is added to the Gemfile or a require statement is inserted at the top of the contentful_rails.rb initializer, Rails bombs.

If it's a non-optional dependency, please require contentful_model from the contentful_rails gem.

$ rails s
=> Booting WEBrick
=> Rails 4.2.3 application starting in development on http://localhost:3000
=> Run `rails server -h` for more startup options
=> Ctrl-C to shutdown server
Exiting
/home/trevor/.rvm/gems/ruby-2.2.2@www-contentful-rails/gems/contentful_rails-0.2.2.1/lib/contentful_rails/engine.rb:13:in `block in <class:Engine>': uninitialized constant ContentfulRails::En
gine::ContentfulModel (NameError)                                                                                                                                                                      from /home/trevor/.rvm/gems/ruby-2.2.2@www-contentful-rails/gems/railties-4.2.3/lib/rails/initializable.rb:30:in `instance_exec'
        from /home/trevor/.rvm/gems/ruby-2.2.2@www-contentful-rails/gems/railties-4.2.3/lib/rails/initializable.rb:30:in `run'
        from /home/trevor/.rvm/gems/ruby-2.2.2@www-contentful-rails/gems/railties-4.2.3/lib/rails/initializable.rb:55:in `block in run_initializers'
        from /home/trevor/.rvm/rubies/ruby-2.2.2/lib/ruby/2.2.0/tsort.rb:226:in `block in tsort_each'
        from /home/trevor/.rvm/rubies/ruby-2.2.2/lib/ruby/2.2.0/tsort.rb:348:in `block (2 levels) in each_strongly_connected_component'
        from /home/trevor/.rvm/rubies/ruby-2.2.2/lib/ruby/2.2.0/tsort.rb:429:in `each_strongly_connected_component_from'
        from /home/trevor/.rvm/rubies/ruby-2.2.2/lib/ruby/2.2.0/tsort.rb:347:in `block in each_strongly_connected_component'
        from /home/trevor/.rvm/rubies/ruby-2.2.2/lib/ruby/2.2.0/tsort.rb:345:in `each'
        from /home/trevor/.rvm/rubies/ruby-2.2.2/lib/ruby/2.2.0/tsort.rb:345:in `call'
        from /home/trevor/.rvm/rubies/ruby-2.2.2/lib/ruby/2.2.0/tsort.rb:345:in `each_strongly_connected_component'
        from /home/trevor/.rvm/rubies/ruby-2.2.2/lib/ruby/2.2.0/tsort.rb:224:in `tsort_each'
        from /home/trevor/.rvm/rubies/ruby-2.2.2/lib/ruby/2.2.0/tsort.rb:203:in `tsort_each'
        from /home/trevor/.rvm/gems/ruby-2.2.2@www-contentful-rails/gems/railties-4.2.3/lib/rails/initializable.rb:54:in `run_initializers'
        from /home/trevor/.rvm/gems/ruby-2.2.2@www-contentful-rails/gems/railties-4.2.3/lib/rails/application.rb:352:in `initialize!'
        from /home/trevor/code/chef/www-contentful-rails/config/environment.rb:5:in `<top (required)>'
        from /home/trevor/code/chef/www-contentful-rails/config.ru:3:in `require'
        from /home/trevor/code/chef/www-contentful-rails/config.ru:3:in `block in <main>'
        from /home/trevor/.rvm/gems/ruby-2.2.2@www-contentful-rails/gems/rack-1.6.4/lib/rack/builder.rb:55:in `instance_eval'
        from /home/trevor/.rvm/gems/ruby-2.2.2@www-contentful-rails/gems/rack-1.6.4/lib/rack/builder.rb:55:in `initialize'
        from /home/trevor/code/chef/www-contentful-rails/config.ru:in `new'
        from /home/trevor/code/chef/www-contentful-rails/config.ru:in `<main>'
        from /home/trevor/.rvm/gems/ruby-2.2.2@www-contentful-rails/gems/rack-1.6.4/lib/rack/builder.rb:49:in `eval'
        from /home/trevor/.rvm/gems/ruby-2.2.2@www-contentful-rails/gems/rack-1.6.4/lib/rack/builder.rb:49:in `new_from_string'
        from /home/trevor/.rvm/gems/ruby-2.2.2@www-contentful-rails/gems/rack-1.6.4/lib/rack/builder.rb:40:in `parse_file'
        from /home/trevor/.rvm/gems/ruby-2.2.2@www-contentful-rails/gems/rack-1.6.4/lib/rack/server.rb:299:in `build_app_and_options_from_config'
        from /home/trevor/.rvm/gems/ruby-2.2.2@www-contentful-rails/gems/rack-1.6.4/lib/rack/server.rb:208:in `app'
        from /home/trevor/.rvm/gems/ruby-2.2.2@www-contentful-rails/gems/railties-4.2.3/lib/rails/commands/server.rb:61:in `app'
        from /home/trevor/.rvm/gems/ruby-2.2.2@www-contentful-rails/gems/rack-1.6.4/lib/rack/server.rb:336:in `wrapped_app'
        from /home/trevor/.rvm/gems/ruby-2.2.2@www-contentful-rails/gems/railties-4.2.3/lib/rails/commands/server.rb:139:in `log_to_stdout'
        from /home/trevor/.rvm/gems/ruby-2.2.2@www-contentful-rails/gems/railties-4.2.3/lib/rails/commands/server.rb:78:in `start'
        from /home/trevor/.rvm/gems/ruby-2.2.2@www-contentful-rails/gems/railties-4.2.3/lib/rails/commands/commands_tasks.rb:80:in `block in server'
        from /home/trevor/.rvm/gems/ruby-2.2.2@www-contentful-rails/gems/railties-4.2.3/lib/rails/commands/commands_tasks.rb:75:in `tap'
        from /home/trevor/.rvm/gems/ruby-2.2.2@www-contentful-rails/gems/railties-4.2.3/lib/rails/commands/commands_tasks.rb:75:in `server'
        from /home/trevor/.rvm/gems/ruby-2.2.2@www-contentful-rails/gems/railties-4.2.3/lib/rails/commands/commands_tasks.rb:39:in `run_command!'
        from /home/trevor/.rvm/gems/ruby-2.2.2@www-contentful-rails/gems/railties-4.2.3/lib/rails/commands.rb:17:in `<top (required)>'
        from /home/trevor/code/chef/www-contentful-rails/bin/rails:8:in `require'
        from /home/trevor/code/chef/www-contentful-rails/bin/rails:8:in `<top (required)>'
        from /home/trevor/.rvm/gems/ruby-2.2.2@www-contentful-rails/gems/spring-1.3.6/lib/spring/client/rails.rb:28:in `load'
        from /home/trevor/.rvm/gems/ruby-2.2.2@www-contentful-rails/gems/spring-1.3.6/lib/spring/client/rails.rb:28:in `call'
        from /home/trevor/.rvm/gems/ruby-2.2.2@www-contentful-rails/gems/spring-1.3.6/lib/spring/client/command.rb:7:in `call'
        from /home/trevor/.rvm/gems/ruby-2.2.2@www-contentful-rails/gems/spring-1.3.6/lib/spring/client.rb:26:in `run'
        from /home/trevor/.rvm/gems/ruby-2.2.2@www-contentful-rails/gems/spring-1.3.6/bin/spring:48:in `<top (required)>'
        from /home/trevor/.rvm/gems/ruby-2.2.2@www-contentful-rails/gems/spring-1.3.6/lib/spring/binstub.rb:11:in `load'
        from /home/trevor/.rvm/gems/ruby-2.2.2@www-contentful-rails/gems/spring-1.3.6/lib/spring/binstub.rb:11:in `<top (required)>'
        from /home/trevor/code/chef/www-contentful-rails/bin/spring:13:in `require'
        from /home/trevor/code/chef/www-contentful-rails/bin/spring:13:in `<top (required)>'
        from bin/rails:3:in `load'
        from bin/rails:3:in `<main>'

Webhooks fail in Rails 5.1

:nothing option for render has been removed in Rails 5.1. This causes it throw a template error. Proposed fix in the PR below.

#38

Rails 5 support?

I have a PR to handle all of what we could find for Rails 5 support, while still supporting Rails 4.

#31

Eager load in a to_prepare block

There is an engine initializer doing this:

Rails.application.eager_load!
ContentfulModel::Base.descendents.each do |klass|
  klass.send(:add_entry_mapping)
end

Rails applications cannot load application code so early. If you change a content model and reload, nobody is invoking #add_entry_mapping on it again. In Rails 7 this is not even possible.

That should be done in a to_prepare block, so that it runs when things are ready on boot, and also on every reload.

(This logic is not related to Zeitwerk, though Rails and Zeitwerk are being polished to uncover latent issues like this one.)

Using caching and the preview API simultaneously is impossible

Due to the following code, it is impossible to test caching while also testing the preview API - would it be possible to separate these concepts so that both could be used at the same time?

      def updated_at_with_caching
        if ContentfulRails.configuration.perform_caching && !ContentfulModel.use_preview_api
          Rails.cache.fetch(self.timestamp_cache_key) do
            updated_at_without_caching
          end
        else
          updated_at_without_caching
        end
      end  

Rails.catch.fetch doesn't properly serialize ContentfulModels

Let's say we have a ContentfulModel named Company with several fields and associations. If we do:

  def company
    Rails.cache.fetch("some_cache_key") do
      Company.first
    end
  end

The first time we call company we'll receive the a Company. The second time, we'll receive an Array with some of its associations (but none of its field values). The second time we're loading the result from the cache, so this leads me to believe the ContentfulModel is not being marshalled correctly.

If instead we do:

  def company
    marshalled = Rails.cache.fetch("some_other_cache_key") do
      Marshal.dump(Company.first)
    end

    Marshal.load(marshalled)
  end

We get the Company back every time, which I believe confirms my suspicion that this is a marshalling problem with ContentfulModel.

I'm pretty unfamiliar with the inner workings of Rails.cache, but it seems like this is hopefully an easy problem to solve. Likely ContentfulModel is not quite behaving as Rails expects it to.

Subclassing MarkdownRenderer

so, I was following your README on subclassing the markdown renderer, and I realized that in your markdown_helper.rb parse_markdown method you always instantiate the
renderer = ContentfulRails::MarkdownRenderer.new(renderer_opts)
So, how do you typically force your engine to use your new and improved subclassed markdown renderer?

Missing attr_writer for attributes with nil values

We've hit an an issue were we're unable to programatically write attributes if they are blank.

Steps to reproduce:

  1. Create a Post content type with a title attribute
  2. Create create a record with the title "Testing 123"
  3. Add a slug attribute to the type
  4. From the console programmatically try and add the slug to each record:
    Post.each { |p| p.slug = p.name.parameterize }.each(&:save)
  5. ContentfulModel::AttributeNotFoundError: no attribute slug= found

It feels like the correct behaviour would be that ContentfulModel::Base subclasses should know all their possible attributes and allow you to edit them.


Apologies if this isn't the right place for this issue, I was unsure if it should live here or in on of the downstream gems.

undefined method `management_token=' for #<ContentfulRails::Configuration:0x007fc86564b510> (NoMethodError)

Rails version: 5.0.0
Gem version: contentful_rails (0.2.3)

Steps to duplicate:

Add simple initializer. Attempt to launch server.

# config/initializers/contentful.rb
ContentfulRails.configure do |config|
  config.authenticate_webhooks = true # false here would allow the webhooks to process without basic auth
  config.webhooks_username = "a basic auth username"
  config.webhooks_password = "a basic auth password"
  config.access_token = "your access token"
  config.preview_access_token = "your preview access token"
  config.management_token = "your management access token"
  config.space = "your space ID"
  config.options = "hash of options"
end

rails c =>  /app/user/config/initializers/contentful.rb:7:in `block in <top (required)>': undefined method `management_token=' for #<ContentfulRails::Configuration:0x007fc86564b510> (NoMethodError)
        from /bundle/gems/contentful_rails-0.2.3/lib/contentful_rails.rb:19:in `configure'

Concern with `eager_load!`

While adding the contentful_rails gem to my application I noticed it instantly bombed initialization and it was happening on load of a class which I dynamically load after initialization.

It was bombing because my Rails.application.routes.url_helpers haven't yet been defined and i traced it back to line 28 of engine.rb where Rails.application.eager_load! is being called. This is a concern for me because adjusting the existing code may prove to be more time costly than forking and making these adjustments.

I followed the commit history and looked at the commit messages and comments within the code but they haven't been the most helpful.

How imperative is the eager_loading? I as soon as I remove this line my app is in tip top shape. I'd appreciate a little insight thanks!

Off-topic additional question: Line 52 of engine.rb appears to be replacing the .json mimetype completely for the entire application. Am I completely wrong?

EDIT
Just saw #2 which answers this question. Perhaps I can assist.

Thanks in advance.

Load error

Hi. As of this morning I started seeing this:

/Users/nnn/.rvm/gems/ruby-2.4.2/gems/require_all-2.0.0/lib/require_all.rb:102:in `rescue in block in require_all': Could not require /Users/nnn/.rvm/gems/ruby-2.4.2/gems/contentful_model-0.2.0/lib/contentful_model/base.rb (uninitialized constant ContentfulModel::ChainableQueries). Please require the necessary files (RequireAll::LoadError)

Any idea what's causing this? Never seen this kind of error in Rails before.

Requires remote web requests even with full cache usage

I'm a little confused as to how to best make use of the existing caching support.

I see in lib/contentful_rails/caching/timestamps.rb that this maintains a timestamp_cache_key for ContentfulModels that gets invalidated automatically if you tell Contentful to post back to your webhook. This also sets updated_at to cache based on this key. So, from my understanding, this allows us to do things like:

  <% Product.all.each do |p| %>
    <% cache(p) do %>
      <%= link_to p.name, product_url(p) %>
    <% end %>
  <% end %>

where a ContentfulModel named Product can be provided to Rails's cache method to provide the key. Very cool.

But getting the full list of Products is expensive too - it will take at least several hundred milliseconds and at worst many seconds.

With a database, the Rails examples deal with this by using simpler, faster queries to determine a cache key, and then checking against that to see if they need to query the whole set:

def cache_key_for_products
  count          = Product.count
  max_updated_at = Product.maximum(:updated_at).try(:utc).try(:to_s, :number)
  "products/all-#{count}-#{max_updated_at}"
end

...

<% cache(cache_key_for_products) do %>
  All available products:
  <% Product.all.each do |p| %>
    <% cache(p) do %>
      <%= link_to p.name, product_url(p) %>
    <% end %>
  <% end %>
<% end %>

But we can't do that, because no matter how simple the call to Contentful, it's still a remote web request and it's going to take forever in relation to other initial page load blockers.

So it seems like we need to maintain cache keys for each ContentfulModel class, which it then checks before refreshing an internally cached result set. These keys should be invalidated by a webhook POST for that Content Type.

These seems like it might but probably doesn't fit under this item listed on the Readme's todos:

Implement a method on ContentfulModel to simulate a parent-child relationship, so we can invalidate caches for parent items

Can you confirm my thinking here? Is a solution like that planned? How have other people solved this problem?

alias_method_chain call in timestamps.rb causes deprecation warning

I get these warning messages for every request. Could this be fixed?

DEPRECATION WARNING: alias_method_chain is deprecated. Please, use Module#prepend instead. From module, you can access the original method using super. (called from block in included at /Users/<my_name>/.rvm/gems/ruby-2.3.1/bundler/gems/contentful_rails-f41079955eda/lib/contentful_rails/caching/timestamps.rb:10)
DEPRECATION WARNING: alias_method_chain is deprecated. Please, use Module#prepend instead. From module, you can access the original method using super. (called from block in included at /Users/<my_name>/.rvm/gems/ruby-2.3.1/bundler/gems/contentful_rails-f41079955eda/lib/contentful_rails/caching/timestamps.rb:11)
DEPRECATION WARNING: alias_method_chain is deprecated. Please, use Module#prepend instead. From module, you can access the original method using super. (called from block in included at /Users/<my_name>/.rvm/gems/ruby-2.3.1/bundler/gems/contentful_rails-f41079955eda/lib/contentful_rails/caching/timestamps.rb:10)
DEPRECATION WARNING: alias_method_chain is deprecated. Please, use Module#prepend instead. From module, you can access the original method using super. (called from block in included at /Users/<my_name>/.rvm/gems/ruby-2.3.1/bundler/gems/contentful_rails-f41079955eda/lib/contentful_rails/caching/timestamps.rb:11)

Can't load gem without ActiveRecord

This gem assumes your project is using ActiveRecord and fails in the boot process. Does it make sense to assume the ORM? Should ContentfulRails be extending ActiveRecord::Migration with something from ContentfulModel? This coupling is troublesome when you have a thin client and are using contentful as your database...

Help understanding how to use caching

Thanks for a great gem, eases use of contentful in rails apps allot!

But I'm having a hard time understand how I would go about adding caching to my application. Can fragment caching be used?

<% @car = Car.first %>
<% cache(@car) %>
    <%= @car.name %>

Is it possible to add a section in readme or similar with some small code examples on how to use cache (updated_at) for views and in controllers?

Unable to reset cache from webhooks

Webhooks for clearing cache is now throwing an error how can I solve this?

NoMethodError (undefined method `clear_cache_for' for nil:NilClass):
vendor/bundle/ruby/2.2.0/gems/contentful_rails-0.2.1/lib/contentful_rails/engine.rb:37:in `block (2 levels) in <class:Engine>'
vendor/bundle/ruby/2.2.0/gems/activesupport-4.2.1/lib/active_support/notifications/fanout.rb:127:in `call'
vendor/bundle/ruby/2.2.0/gems/activesupport-4.2.1/lib/active_support/notifications/fanout.rb:127:in `finish'
vendor/bundle/ruby/2.2.0/gems/activesupport-4.2.1/lib/active_support/notifications/fanout.rb:46:in `block in finish'
vendor/bundle/ruby/2.2.0/gems/activesupport-4.2.1/lib/active_support/notifications/fanout.rb:46:in `each'
vendor/bundle/ruby/2.2.0/gems/activesupport-4.2.1/lib/active_support/notifications/fanout.rb:46:in `finish'
vendor/bundle/ruby/2.2.0/gems/activesupport-4.2.1/lib/active_support/notifications/instrumenter.rb:36:in `finish'
vendor/bundle/ruby/2.2.0/gems/activesupport-4.2.1/lib/active_support/notifications/instrumenter.rb:25:in `instrument'
vendor/bundle/ruby/2.2.0/gems/activesupport-4.2.1/lib/active_support/notifications.rb:164:in `instrument'
vendor/bundle/ruby/2.2.0/gems/contentful_rails-0.2.1/app/controllers/contentful_rails/webhooks_controller.rb:23:in `create'
vendor/bundle/ruby/2.2.0/gems/actionpack-4.2.1/lib/action_controller/metal/implicit_render.rb:4:in `send_action'

Content types caching blowing up VCR on CI

I'm pulling what little hair I have out trying to figure this out.

Running Rails app tests locally and everything is fine.

When it goes to CI I get requests that aren't handled ending in content_types?limit=1000. I'm guessing this is to do with dynamic loading.

How do I get the same behaviour locally so I can update VCR?

Thanks

Release webhooks fix?

Actually the latest contentful_rails gem version is 0.4.1 and it doesn't contain the webhooks fix, can you publish a minor release to fix that issue?

How do you perform the russian nesting doll cache?

I am looking to verify that the cache is saving the right api response, however, I am not see that(?) My redis keeps filling up with new objects.

What is the best way to implement this?

I have the engine mounted to '/'

When I ask the model for its updated_at timestamp, I don't seem to enter the line of code in lib/contentful_rails/caching/timestamps.rb and I don't see a value entered into Redis.

@content_type.method(:updated_at).source_location returns ["lib/contentful_model/base.rb", 39] which comes from the coercing.

Update:

Seems like the Rails engine,

initializer 'prepend_timestamps_module', after: :subscribe_to_webhook_events do
      if defined?(::ContentfulModel)
        ContentfulModel::Base.send(:prepend, ContentfulRails::Caching::Timestamps)
      end
    end

Works, however, the updated_at timestamp is overwritten but setting the instance methods in both the contentful_model and contentful gems.

OOM errors in Docker

We're seeing fairly heavy RAM consumption when performing basic queries:

@tests = TestProduct.all.order('name').load

We suspect this is due to eager loading, as TestProducts have a number of associations (only a handful of actual TestProducts exist). We've tried suppressing eager loading as described here, but no dice.

Any ideas?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.