A mixin for ruby classes to access the rooftop cms rest api: http://www.rooftopcms.com
You can either install using bundler or just from the command line.
Include this in your gemfile
gem 'rooftop'
That's it! this is in active development so you might prefer:
gem 'rooftop', github: "rooftop/rooftop-ruby"
As simple as:
gem install rooftop
You need to configure rooftop with a block, like this
Rooftop.configure do |config|
config.url = "http://yoursite.rooftopcms.io"
config.api_token = "your token"
config.api_path = "/wp-json/wp/v2/"
config.user_agent = "rooftop cms ruby client #{rooftop::version} (http://github.com/rooftopcms/rooftop-ruby)"
config.extra_headers = {custom_header: "foo", another_custom_header: "bar"}
config.advanced_options = {} #for future use
end
The minimum options you need to include are url
and api_token
.
Create a class in your application, and mix in some (or all) of the rooftop modules to interact with your remote content.
The Rooftop::Post mixin lets you specify a post type, so the api differentiates between types. if you don't set a post type, it defaults to posts.
class MyCustomPostType
include Rooftop::Post
self.post_type = "my_custom_post_type" #this is the singular post type name in WordPress
end
The rooftop::page mixin identifies this class as a page.
class Page
include Rooftop::Page
end
## Custom endpoints and WordPress namespaces
If you write a wordpress api plugin, you can either expose your data in an existing /wp/
namespace, or in a custom namespace.
rooftop includes wp-api-menus
, so this gem supports the ability to specify an arbitrary namespace, version and endpoint name if necessary.
this example would return a collection of mything
objects from the path /wp-json/my-things/v3/things
:
class MyThing
include rooftop::base
self.api_namespace = "my-things"
self.api_version = 3
self.api_endpoint = "things"
end
If you don't specify the api_endpoint
attribute, it assumes the underscored, pluralized version of the class name (my_things
in this case)
Sometimes you want to do something with a field after it's returned. for example, it would be useful to parse date strings to datetime.
To coerce one or more fields, call a class method in your class and pass a lambda which is called on the field.
class MyCustomPostType
include rooftop::post
self.post_type = "my_custom_post_type"
coerce_field date: ->(date) { datetime.parse(date)}
end
The created date field is coerced to a datetime. it's also aliased to created_at
The modification date is also coerced to a datetime. it's also aliased to updated_at
Sometimes you might want to alias field names - for example, date
is aliased to created_at
to make it more rubyish. modified
is also updated_at
.
Creating alises and coercing them is order-sensitive. if you need to both coerce a field and alias it, do the coercion first. otherwise you'll find the aliased field isn't coerced.
(We don't just called alias_method
on the original field because the object methods are dynamically generated from the returned data)
class mycustomposttype
include rooftop::post
self.post_type = "my_custom_post_type"
coerce_field some_date: ->(date) { datetime.parse(date)}
alias_field some_date: :another_name_for_some_date
end
Resource links are a work-in-progress
The Wordpress api uses the concept of hypermedia links (see http://v2.wp-api.org/extending/linking/ for more info). we parse the _links
field in the response and build a Rooftop::ResourceLinks::Collection (which is a subclass of array
). the individual links are instances of Rooftop::ResourceLinks::Link.
The reason for these classes is because we can do useful stuff with them, for example call .resolve()
on a link to get an instance of the class to which it refers.
according to the wordpress api docs (and iana link relation convention) you need a custom name for link relations which don't fall into a small subset of names. for those aren't in this list, we're prefixing the relation names with http://docs.rooftopcms.com/link_relations, which will resolve to some documentation.
rooftop uses the custom link relations to return a list of ancestors and children (not all descendants) in _links
. rooftop::post and rooftop::page include rooftop::nested, which has some utility methods to access them.
class Page
include rooftop::page
end
p = Page.first
p.ancestors #returns a collection of rooftop::resourcelinks::link items where `link_type` is "http://docs.rooftopcms.com/link_relations/ancestors"
p.children #returns a collection of rooftop::resourcelinks::link items where `link_type` is "http://docs.rooftopcms.com/link_relations/children"
p.parent #returns the parent entity
Rooftop can return a variable number of content fields depending on what you've configured in advanced custom fields.
The raw data is available in your object at .content
:
p = Page.first
p.content #returns a hash of content data
But that's not super-helpful, so the gem generates a collection for you.
p = Page.first
p.fields #a Rooftop::Content::Collection, containing Rooftop::Content::Field entries.
You can access a particular piece of content like this:
p = Page.first
p.fields.your_field #your_field would be a custom field you've created in Advanced Custom Fields
p.fields.content #the default 'content' field from the Rooftop admin interface
You can get a list of all the fields on your model like this:
p = Page.first
p.fields.field_names #returns an array of field names you can call
Prior to version 1.0, nested content fields (in an ACF repeater) would be returned as an array of Rooftop::Content::Field objects.
In Version 1.0 onwards, the default is to return a Rooftop::Content::Collection for each nested field collection. This is a breaking change. If you want to return to the pre-1.0 functionality, you can include this in your config:
Rooftop.configure do |config|
config.advanced_options = {
create_nested_content_collections: false
}
end
ACF can relate one content type to another. In the native Rooftop response, you can return either an array of IDs or an array of hashes.
In some circumstances it's reasonable to use these directly, but the nested hash representing a relation won't be updated if the relation itself is updated, which can be a problem.
From version 1.0 onwards, the default is to resolve these relations with another API call. If you would prefer to stick with the existing behaviour, you can do that in the advanced options at configuration time:
Rooftop.configure do |config|
config.advanced_options = {
resolve_relations: false
}
end
Hosted Rooftop from rooftopcms.io exclusively uses ssl/tls. you need to configure the rooftop library to use ssl.
This library uses the excellent her rest client which in turn uses faraday for http requests. the faraday ssl docs are pretty complete, and the ssl options are exposed straight through this library:
Rooftop.configure do |config|
# other config options
config.ssl_options = {
#your ssl options in here.
}
# other config options
end
Leaving config.ssl_options
unset allows you to work without https.
While hosted rooftop is cached at source and delivered through a cdn, caching locally is a smart idea. the rooftop library uses the faraday-http-cache to intelligently cache responses from the api. rooftop updates etags when entities are updated so caches shouldn't be stale.
(the cache headers plugin for rooftop is a work in progress)
If you want to cache responses, set perform_caching
to true in your configuration block. you will need to provide a cache store and logger in the cache_store
and cache_logger
config options. by default, the cache store is set to activesupport::cache.lookup_store(:memory_store)
which is a sensible default, but isn't shared across threads. any activesupport-compatible cache store (memcache, file, redis etc.) will do.
The cache_logger
option determines where cache debug messages (hits, misses etc.) get stored. by default it's nil
, which switches logging off.
## Menus WordPress comes with quite a powerful way to build menus, rather than needing to traverse a tree of objects directly.
At the moment you have to know the ID of the menu you need - not ideal. Keep an eye on the following issues to get an update:
So for now, with that in mind, here's how you do it:
r = Rooftop::Menus::Menu.find(2) #returns a menu with ID 2
r.items # a collection of Rooftop::Menus::Item
If a menu item refers to an object in Rooftop - a post or a page - you can access the original object by calling object()
.
r = Rooftop::Menus::Menu.find(2) #returns a menu with ID 2
r.items # a collection of Rooftop::Menus::Item
item = r.items.first # a Rooftop::Menus::Item
item.object # an object derived from the post type and ID.
If you haven't defined a class for a post type, you'll get a Rooftop::Menus::UnmappedObjectError
Lots! here's a flavour:
- preview mode. rooftop supports passing a preview header to see content in draft. we'll expose that in the rooftop gem as a constant.
- taxonomies will be supported and side-loaded against content
- media: media is exposed by the api, and should be accessible and downloadable.
If your api user in rooftop has permission to write data, the api will allow it, and so should this gem. at the moment all the code is theoretically in place but untested. it would be great to have issues raised about writing back to the api.
Some abstractions will definitely need putting back into a hash to save.
rooftop and its libraries are open-source and we'd love your input.
- fork the repo on github
- make whatever changes / extensions you think would be useful
- if you've got lots of commits, rebase them into sensible squashed chunks
- raise a pr on the project
if you have a real desire to get involved, we're looking for maintainers. [let us know!](mailto: hello@rooftopcms.com).
rooftop-ruby
is a library to allow you to connect to Rooftop CMS, the API-first WordPress CMS for developers and content creators.
Copyright 2015 Error Ltd.
This program is free software: you can redistribute it and/or modify it under the terms of the gnu general public license as published by the free software foundation, either version 3 of the license, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but without any warranty; without even the implied warranty of merchantability or fitness for a particular purpose. see the gnu general public license for more details.
You should have received a copy of the GNU general public license along with this program. if not, see http://www.gnu.org/licenses/.