This library is part of the WP on Rails project, that uses WordPress (WP) in a "headless" fashion —as a means of managing content— while using a Rails application to serve public requests and provide a basis for customizations.
To prepare a WP installation to be used in a WP on Rails architecture the wp-relinquish plugin is provided. It provides a way to configure WP actions to send notifications as webhook calls to the Rails application equipped with wp-connector.
When using WP on Rails the content's master data resides in WP's database, as that's where is it created and modified. The Rails application that is connected to WP stores a structured copy of the data (usually providing a separate table for each datatype), acting as a cache, on the basis of which the public requests are served.
The main reasons for not using WP to serve public web requests are:
- Security — The internet is a dangerous place and WordPress has proven to be a popular target for malicious hackers. By not serving public requests from WP, but only the admin interface, the attack surface is significantly reduced.
- Performance — Performance tuning WP can be difficult, especially when a generic caching-proxy (such as Varnish) is not viable due to dynamic content such as ads or personalization. Application frameworks provide a means for fine-grained caching strategies that are needed to serve high-traffic websites containing dynamic content.
- Cost (TCO) of customizations — Customizing WP, and maintaining those customizations, is costly (laborious) and risky (error prone) compared to building custom functionality on top of an application framework (which is specifically designed for that purpose).
- Upgrade path — Keeping a customized WP installation up-to-date can be a pain, and WP-updates come ever more often. When WP is not used to serve public requests and customizations are not built into WP most of this pain avoided.
- Modern browser interfaces — The new breed of JavaScript UI frameworks (such as Facebook's React and Google's AngularJS) have features like "isomorphism" (allowing JS views to be pre-rendered on the server) and "two-way data-binding." These features expect certain server-side behavior and usually consume JSON data instead of HTML. The Rails community provides many tools to facilitate these new UI libraries from the server-side.
After the Rails application receives the webhook call from WP, simply notifying that some content is created or modified, a delayed job to fetch the content is scheduled using Sidekiq. The content is not fetched immediately, but scheduled for a fraction of a second later, the reason for this is twofold:
- The webhook call is synchronous, responding as soon as possible is needed to keep the admin interface of WP responsive.
- It is not guaranteed that all WP's processing is complete (some actions may still fire) by the time the webhook call is made.
The delayed job fetches the relevant content from WP using the WP-REST-API (this can be one or more requests), then possibly transforms and/or enriches the data, and finally stores it using a regular ActiveRecord model. The logic for the fetch and transform/enrich steps is simply part of the ActiveRecord model definition.
Add this line to your Rails application's Gemfile
:
gem 'wp-connector', :github => 'wponrails/wp-connector'
Then execute bundle install
.
In WordPress install both the wp-relinquish
and json-rest-api
plugins. The wonderful ACF plugin should work out-of-the-box with wp-relinquish
. The wp-relinquish
plugin needs some configuration, as specified in the README of that project.
Create an initializer and specify the "JSON route" of the WP REST API, by setting the wordpress_url
config option:
# config/initializers/wp_connector.rb
WpConnector.configure do |config|
config.wordpress_url = "http://wordpress-site.dev/"
end
Here wordpress-site.dev
is the domain for your Wordpress site.
In order to configure models, ['articles', 'news_articles', 'pages']
, for paginated requests to the WP-REST API, add the following to your initializer:
config.wp_api_paginated_models = %w(articles news_articles pages)
Also specify the wp-connector API key in the wp-connector configuration by adding the wp_connector_api_key
config option to the initializer:
config.wp_connector_api_key="H3O5P6P1I5N8G8E4R"
Create the routes for the webhook endpoint (in config/routes.rb
of your Rails app):
# wp-connector endpoints
post 'wp-connector/:model', to: 'wp_connector#model_save'
delete 'wp-connector/:model/:id', to: 'wp_connector#model_delete'
Create a WpConnectorController
class (in app/controllers/wp_connector_controller.rb
) that specifies a webhook
action. For example for the Post
type:
class WpConnectorController < ApplicationController
include WpWebhookEndpoint
skip_before_action :verify_authenticity_token
def model_save
model = params[:model].classify.constantize
render_json_200_or_404 model.schedule_create_or_update(wp_id_from_params)
end
def model_delete
model = params[:model].constantize
render_json_200_or_404 model.purge(wp_id_from_params)
end
end
Create a model for each of the content types that need to be cached by the Rails application.
This is an example for the Post
model:
class Post < ActiveRecord::Base
include WpCache
include WpPost
def update_wp_cache(json)
update_post(json)
author = Author.find_or_create(json["author"]["ID"])
author.update_wp_cache(json["author"])
self.author = author
self.save
end
end
And an example of an Author
model:
class Author < ActiveRecord::Base
include WpCache
include WpPost
def update_wp_cache(json)
update_post(json)
self.username = json["username"]
self.name = json["name"]
self.first_name = json["first_name"]
self.last_name = json["last_name"]
self.nickname = json["nickname"]
self.url = json["URL"]
self.description = json["description"]
self.registered = json["registered"]
self.save
end
end
And create the migration for these models:
class CreatePostsAndAuthors < ActiveRecord::Migration
def change
create_table :posts do |t|
t.integer :wp_id
t.string :title
t.integer :author_id
t.text :content
t.string :slug
t.text :excerpt
t.timestamps
end
create_table :authors do |t|
t.integer :wp_id
t.string :username
t.string :name
t.string :first_name
t.string :last_name
t.string :nickname
t.string :slug
t.string :url
t.string :description
t.string :registered
t.timestamps
end
end
end
wp-connector assumes the model name is the same as the wp post type name. This means the API call for the author becomes:
http://wordpress-site.dev/?json_route=/authors/{id}
However, in some cases the model names do not correspond.
Therefore the wp_type of a rails model can be overridden with the wp_type
class method.
class Author < ActiveRecord::Base
# ... snip ...
def self.wp_type
'wp_post_type_name'
end
# ... snap ...
end
Resulting in the following API call:
http://wordpress-site.dev/?json_route=/wp_post_type_name/{id}
For menu items the json-rest-api-menu-routes
WordPress plugin is required.
The implementation for menus is rather straightforward.
An example for a basic Menu
model:
class Menu < ActiveRecord::Base
include WpCache
include WpMenu
def update_wp_cache(json)
update_menu(json)
end
end
And the migration would be:
class CreateMenus < ActiveRecord::Migration
def change
create_table :menus do |t|
t.integer :wp_id
t.string :slug
t.text :description
t.string :slug
t.integer :count
t.text :items
t.timestamps
end
end
end
You know the drill :)
- Fork it.
- Create your feature branch (
git checkout -b my-new-feature
). - Commit your changes (
git commit -am 'Add some feature'
). - Push to the branch (
git push origin my-new-feature
). - Submit a "Pull Request".
Copyright (c) 2014-2015, Hoppinger B.V.
All files in this repository are MIT-licensed, as specified in the LICENSE file.