rooftop 0.0.1 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/LICENSE.txt +621 -22
- data/README.md +168 -25
- data/lib/rooftop/base.rb +74 -0
- data/lib/{rooftop_client → rooftop}/client.rb +0 -0
- data/lib/rooftop/coercions/author_coercion.rb +10 -0
- data/lib/rooftop/coercions/parent_coercion.rb +47 -0
- data/lib/{rooftop_client → rooftop}/coercions.rb +10 -3
- data/lib/rooftop/content_fields/collection.rb +23 -0
- data/lib/rooftop/content_fields/content_fields.rb +39 -0
- data/lib/rooftop/content_fields/field.rb +12 -0
- data/lib/rooftop/errors/record_not_found.rb +3 -0
- data/lib/rooftop/field_aliases.rb +41 -0
- data/lib/{rooftop_client → rooftop}/headers.rb +1 -1
- data/lib/rooftop/hook_calls.rb +40 -0
- data/lib/{rooftop_client → rooftop}/models/author.rb +0 -0
- data/lib/{rooftop_client → rooftop}/models/media_item.rb +1 -1
- data/lib/rooftop/models/menu.rb +9 -0
- data/lib/{rooftop_client → rooftop}/models/menu_item.rb +0 -0
- data/lib/{rooftop_client → rooftop}/models/taxonomy.rb +0 -0
- data/lib/{rooftop_client → rooftop}/models/taxonomy_term.rb +1 -1
- data/lib/rooftop/nested.rb +26 -0
- data/lib/rooftop/page.rb +17 -0
- data/lib/{rooftop_client → rooftop}/post.rb +2 -2
- data/lib/{rooftop_client → rooftop}/queries/queries.rb +11 -0
- data/lib/rooftop/resource_links/collection.rb +22 -0
- data/lib/rooftop/resource_links/link.rb +15 -0
- data/lib/rooftop/resource_links/resource_links.rb +15 -0
- data/lib/rooftop/version.rb +3 -0
- data/lib/rooftop.rb +54 -7
- data/rooftop_ruby_client.gemspec +3 -2
- metadata +43 -19
- data/lib/rooftop_client/base.rb +0 -38
- data/lib/rooftop_client/coercions/author_coercion.rb +0 -8
- data/lib/rooftop_client/coercions/parent_coercion.rb +0 -45
- data/lib/rooftop_client/models/menu.rb +0 -8
- data/lib/rooftop_client/page.rb +0 -19
- data/lib/rooftop_client/version.rb +0 -3
data/README.md
CHANGED
@@ -1,68 +1,211 @@
|
|
1
1
|
# Rooftop
|
2
|
-
A mixin for
|
2
|
+
A mixin for ruby classes to access the rooftop cms rest api: http://www.rooftopcms.com
|
3
3
|
|
4
4
|
# Setup
|
5
5
|
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
You can either install using [bundler](http://bundler.io) or just from the command line.
|
9
|
+
|
10
|
+
### Bundler
|
11
|
+
Include this in your gemfile
|
12
|
+
|
13
|
+
`gem 'rooftop'`
|
14
|
+
|
15
|
+
That's it! this is in active development so you might prefer:
|
16
|
+
|
17
|
+
`gem 'rooftop', github: "rooftop/rooftop-ruby"`
|
18
|
+
|
19
|
+
### Using Gem
|
20
|
+
As simple as:
|
21
|
+
|
22
|
+
`gem install rooftop`
|
23
|
+
|
6
24
|
## Configuration
|
7
|
-
You need to configure
|
25
|
+
You need to configure rooftop with a block, like this
|
8
26
|
|
9
27
|
```
|
10
28
|
Rooftop.configure do |config|
|
11
|
-
config.url = "http://
|
29
|
+
config.url = "http://yoursite.rooftopcms.io"
|
30
|
+
config.api_token = "your token"
|
31
|
+
config.api_path = "/wp-json/wp/v2/"
|
32
|
+
config.user_agent = "rooftop cms ruby client #{rooftop::version} (http://github.com/rooftopcms/rooftop-ruby)"
|
33
|
+
config.extra_headers = {custom_header: "foo", another_custom_header: "bar"}
|
34
|
+
config.advanced_options = {} #for future use
|
12
35
|
end
|
13
36
|
```
|
14
37
|
|
38
|
+
The minimum options you need to include are `url` and `api_token`.
|
39
|
+
|
15
40
|
# Use
|
16
|
-
Create a class and
|
41
|
+
Create a class in your application, and mix in some (or all) of the rooftop modules to interact with your remote content.
|
17
42
|
|
18
43
|
## Rooftop::Post
|
19
|
-
The Rooftop::Post mixin lets you specify a post type, so the
|
44
|
+
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.
|
20
45
|
|
21
46
|
```
|
22
47
|
class MyCustomPostType
|
23
48
|
include Rooftop::Post
|
24
|
-
self.post_type = "my_custom_post_type" #this is the post type name in
|
49
|
+
self.post_type = "my_custom_post_type" #this is the singular post type name in WordPress
|
25
50
|
end
|
26
51
|
```
|
27
52
|
## Rooftop::Page
|
28
|
-
The
|
53
|
+
The rooftop::page mixin identifies this class as a page.
|
29
54
|
```
|
30
55
|
class Page
|
31
56
|
include Rooftop::Page
|
32
57
|
end
|
33
58
|
```
|
34
59
|
|
60
|
+
## Custom endpoints and WordPress namespaces
|
61
|
+
If you write a wordpress api plugin, you can either expose your data in an existing `/wp/` namespace, or in a custom namespace.
|
62
|
+
|
63
|
+
rooftop includes `wp-api-menus`, so this gem supports the ability to specify an arbitrary namespace, version and endpoint name if necessary.
|
64
|
+
|
65
|
+
this example would return a collection of `mything` objects from the path `/wp-json/my-things/v3/things`:
|
66
|
+
|
67
|
+
```
|
68
|
+
class MyThing
|
69
|
+
include rooftop::base
|
70
|
+
self.api_namespace = "my-things"
|
71
|
+
self.api_version = 3
|
72
|
+
self.api_endpoint = "things"
|
73
|
+
end
|
74
|
+
```
|
75
|
+
|
76
|
+
If you don't specify the `api_endpoint` attribute, it assumes the underscored, pluralized version of the class name (`my_things` in this case)
|
77
|
+
|
78
|
+
|
35
79
|
## Field coercions
|
36
|
-
Sometimes you want to do something with a field after it's returned.
|
80
|
+
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.
|
37
81
|
|
38
82
|
To coerce one or more fields, call a class method in your class and pass a lambda which is called on the field.
|
39
83
|
|
40
84
|
```
|
41
|
-
class
|
42
|
-
include
|
85
|
+
class mycustomposttype
|
86
|
+
include rooftop::post
|
43
87
|
self.post_type = "my_custom_post_type"
|
44
|
-
coerce_field date: ->(date) {
|
88
|
+
coerce_field date: ->(date) { datetime.parse(date)}
|
89
|
+
end
|
90
|
+
|
91
|
+
```
|
92
|
+
|
93
|
+
### Object dates are coerced automatically
|
94
|
+
The created date field is coerced to a datetime. it's also aliased to `created_at`
|
95
|
+
|
96
|
+
The modification date is also coerced to a datetime. it's also aliased to `updated_at`
|
97
|
+
|
98
|
+
## Field aliases
|
99
|
+
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`.
|
100
|
+
|
101
|
+
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.
|
102
|
+
|
103
|
+
(We don't just called `alias_method` on the original field because the object methods are dynamically generated from the returned data)
|
104
|
+
|
45
105
|
```
|
106
|
+
class mycustomposttype
|
107
|
+
include rooftop::post
|
108
|
+
self.post_type = "my_custom_post_type"
|
109
|
+
coerce_field some_date: ->(date) { datetime.parse(date)}
|
110
|
+
alias_field some_date: :another_name_for_some_date
|
111
|
+
end
|
112
|
+
```
|
113
|
+
|
114
|
+
## Resource links
|
115
|
+
*Resource links are a work-in-progress*
|
116
|
+
|
117
|
+
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.
|
118
|
+
|
119
|
+
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.
|
120
|
+
|
121
|
+
### Custom Link Relations
|
122
|
+
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](http://www.iana.org/assignments/link-relations/link-relations.xhtml). 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.
|
123
|
+
|
124
|
+
### Nested Resource Links
|
125
|
+
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.
|
126
|
+
|
127
|
+
```
|
128
|
+
class Page
|
129
|
+
include rooftop::page
|
130
|
+
end
|
131
|
+
|
132
|
+
p = Page.first
|
133
|
+
p.ancestors #returns a collection of rooftop::resourcelinks::link items where `link_type` is "http://docs.rooftopcms.com/link_relations/ancestors"
|
134
|
+
p.children #returns a collection of rooftop::resourcelinks::link items where `link_type` is "http://docs.rooftopcms.com/link_relations/children"
|
135
|
+
p.parent #returns the parent entity
|
136
|
+
```
|
137
|
+
## Handling Content Fields
|
138
|
+
Rooftop can return a variable number of content fields depending on what you've configured in advanced custom fields.
|
139
|
+
|
140
|
+
## SSL / TLS
|
141
|
+
Hosted Rooftop from rooftopcms.io exclusively uses ssl/tls. you need to configure the rooftop library to use ssl.
|
142
|
+
|
143
|
+
This library uses the excellent [her rest client](https://github.com/remiprev/her) which in turn uses [faraday](https://github.com/lostisland/faraday/) for http requests. the [faraday ssl docs](https://github.com/lostisland/faraday/wiki/setting-up-ssl-certificates) are pretty complete, and the ssl options are exposed straight through this library:
|
144
|
+
|
145
|
+
```
|
146
|
+
Rooftop.configure do |config|
|
147
|
+
# other config options
|
148
|
+
config.ssl_options = {
|
149
|
+
#your ssl options in here.
|
150
|
+
}
|
151
|
+
# other config options
|
152
|
+
end
|
153
|
+
```
|
154
|
+
|
155
|
+
Leaving `config.ssl_options` unset allows you to work without https.
|
156
|
+
|
157
|
+
## Caching
|
158
|
+
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](https://github.com/plataformatec/faraday-http-cache) to intelligently cache responses from the api. rooftop updates etags when entities are updated so caches shouldn't be stale.
|
159
|
+
|
160
|
+
_(the cache headers plugin for rooftop is a work in progress)_
|
161
|
+
|
162
|
+
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.
|
163
|
+
|
164
|
+
The `cache_logger` option determines where cache debug messages (hits, misses etc.) get stored. by default it's `nil`, which switches logging off.
|
165
|
+
|
166
|
+
# Roadmap
|
167
|
+
Lots! here's a flavour:
|
168
|
+
|
169
|
+
## Reading data
|
170
|
+
|
171
|
+
* preview mode. rooftop supports passing a preview header to see content in draft. we'll expose that in the rooftop gem as a constant.
|
172
|
+
* taxonomies will be supported and side-loaded against content
|
173
|
+
* menus are exposed by rooftop. we need to create a rooftop::menu mixin
|
174
|
+
* media: media is exposed by the api, and should be accessible and downloadable.
|
175
|
+
|
176
|
+
## Writing
|
177
|
+
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](https://github.com/rooftopcms/rooftop-ruby/issues) about writing back to the api.
|
178
|
+
|
179
|
+
Some abstractions will definitely need putting back into a hash to save.
|
180
|
+
|
181
|
+
# Contributing
|
182
|
+
rooftop and its libraries are open-source and we'd love your input.
|
183
|
+
|
184
|
+
1. fork the repo on github
|
185
|
+
2. make whatever changes / extensions you think would be useful
|
186
|
+
3. if you've got lots of commits, rebase them into sensible squashed chunks
|
187
|
+
4. raise a pr on the project
|
188
|
+
|
189
|
+
if you have a real desire to get involved, we're looking for maintainers. [let us know!](mailto: hello@rooftopcms.com).
|
46
190
|
|
47
|
-
There are some coercions done manually.
|
48
191
|
|
49
|
-
|
50
|
-
|
192
|
+
# Licence
|
193
|
+
`rooftop-ruby` is a library to allow you to connect to Rooftop CMS, the API-first WordPress CMS for developers and content creators.
|
51
194
|
|
52
|
-
|
53
|
-
Created date is coerced to a DateTime. It's also aliased to created_at
|
195
|
+
Copyright 2015 Error Ltd.
|
54
196
|
|
55
|
-
|
56
|
-
|
197
|
+
This program is free software: you can redistribute it and/or modify
|
198
|
+
it under the terms of the gnu general public license as published by
|
199
|
+
the free software foundation, either version 3 of the license, or
|
200
|
+
(at your option) any later version.
|
57
201
|
|
58
|
-
|
59
|
-
|
202
|
+
This program is distributed in the hope that it will be useful,
|
203
|
+
but without any warranty; without even the implied warranty of
|
204
|
+
merchantability or fitness for a particular purpose. see the
|
205
|
+
gnu general public license for more details.
|
60
206
|
|
61
|
-
|
62
|
-
|
63
|
-
* Preview: once authentication is solved, we need to be able to show posts in draft
|
64
|
-
* Media: media is exposed by the API. Don't know if this explicitly needs supporting by the API or just accessible
|
65
|
-
* Allowing other classes to be exposed: mixing in Rooftop::Client *should* allow a custom class to hit the right endpoint, but it's work-in-progress
|
207
|
+
You should have received a copy of the GNU general public license
|
208
|
+
along with this program. if not, see <http://www.gnu.org/licenses/>.
|
66
209
|
|
67
210
|
|
68
211
|
|
data/lib/rooftop/base.rb
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
module Rooftop
|
2
|
+
module Base
|
3
|
+
def self.included(base)
|
4
|
+
base.extend ClassMethods
|
5
|
+
base.include Her::Model
|
6
|
+
|
7
|
+
# Paths to get to the API
|
8
|
+
base.api_namespace = Rooftop::DEFAULT_API_NAMESPACE
|
9
|
+
base.api_version = Rooftop::DEFAULT_API_VERSION
|
10
|
+
base.setup_path!
|
11
|
+
|
12
|
+
# Coercions allow you to pass a block to do something with a returned field
|
13
|
+
base.include Rooftop::Coercions
|
14
|
+
# Aliases allow you to specify a field (or fields) to alias
|
15
|
+
base.include Rooftop::FieldAliases
|
16
|
+
# Queries mixin includes a fixup for there `where()` method
|
17
|
+
base.include Rooftop::Queries
|
18
|
+
# Links mixin handles the _links key in a response
|
19
|
+
base.include Rooftop::ResourceLinks
|
20
|
+
# Use the API instance we have configured - in a proc because we can't control load order
|
21
|
+
base.send(:use_api,->{Rooftop.configuration.connection})
|
22
|
+
|
23
|
+
# Turn calls to `content` into a collection of Rooftop::ContentField objects
|
24
|
+
base.include Rooftop::Content
|
25
|
+
|
26
|
+
# Date and Modified fields are pretty universal in responses from WP, so we can automatically
|
27
|
+
# coerce these to DateTime.
|
28
|
+
base.send(:coerce_field,date: ->(date) {DateTime.parse(date)})
|
29
|
+
base.send(:coerce_field,modified: ->(modified) {DateTime.parse(modified)})
|
30
|
+
|
31
|
+
# Having coerced the fields, we can alias them (order is important - coerce first.)
|
32
|
+
base.send(:alias_field, date: :created_at)
|
33
|
+
base.send(:alias_field, modified: :updated_at)
|
34
|
+
|
35
|
+
# Set up the hooks identified in other mixins. This method is defined in Rooftop::HookCalls
|
36
|
+
base.send(:"setup_hooks!")
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
module ClassMethods
|
41
|
+
attr_reader :api_namespace, :api_version, :api_endpoint
|
42
|
+
|
43
|
+
def api_namespace=(ns)
|
44
|
+
@api_namespace = ns
|
45
|
+
setup_path!
|
46
|
+
end
|
47
|
+
|
48
|
+
def api_version=(v)
|
49
|
+
@api_version = v
|
50
|
+
setup_path!
|
51
|
+
end
|
52
|
+
|
53
|
+
def api_endpoint=(e)
|
54
|
+
@api_endpoint = e
|
55
|
+
setup_path!
|
56
|
+
end
|
57
|
+
|
58
|
+
def setup_path!
|
59
|
+
@api_endpoint ||= collection_path
|
60
|
+
self.collection_path "#{@api_namespace}/v#{@api_version}/#{@api_endpoint}"
|
61
|
+
end
|
62
|
+
|
63
|
+
# Allow calling 'first'
|
64
|
+
def first
|
65
|
+
all.first
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
end
|
File without changes
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module Rooftop
|
2
|
+
module Coercions
|
3
|
+
module AuthorCoercion
|
4
|
+
def self.included(base)
|
5
|
+
base.send(:after_find, ->(r) { r.author.registered = DateTime.parse(r.author.registered)})
|
6
|
+
base.send(:coerce_field, {author: ->(author) { Rooftop::Author.new(author) }})
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# Coerce any field called 'parent' which returns an ID into an actual object
|
2
|
+
module Rooftop
|
3
|
+
module Coercions
|
4
|
+
module ParentCoercion
|
5
|
+
def self.included(base)
|
6
|
+
base.extend ClassMethods
|
7
|
+
# base.send(:after_find, ->(r) {
|
8
|
+
# if r.has_parent?
|
9
|
+
# r.instance_variable_set(:"parent_#{base.to_s.underscore}", resolve_parent_id())
|
10
|
+
# r.class.send(:attr_reader, :"parent_#{base.to_s.underscore}")
|
11
|
+
# end
|
12
|
+
# })
|
13
|
+
# base.send(:coerce_field, parent: ->(p) { base.send(:resolve_parent_id,p) })
|
14
|
+
end
|
15
|
+
|
16
|
+
module ClassMethods
|
17
|
+
def add_parent_reference
|
18
|
+
define_method :"parent_#{self.to_s.underscore}" do
|
19
|
+
puts "hello"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def has_parent?
|
25
|
+
respond_to?(:parent) && parent.is_a?(Fixnum) && parent != 0
|
26
|
+
end
|
27
|
+
|
28
|
+
def resolve_parent_id
|
29
|
+
if respond_to?(:parent)
|
30
|
+
if parent.is_a?(Fixnum)
|
31
|
+
if parent == 0
|
32
|
+
#no parent
|
33
|
+
return nil
|
34
|
+
else
|
35
|
+
return self.class.send(:find, id)
|
36
|
+
end
|
37
|
+
else
|
38
|
+
return parent
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
@@ -1,9 +1,14 @@
|
|
1
1
|
module Rooftop
|
2
2
|
module Coercions
|
3
3
|
def self.included(base)
|
4
|
+
# Include Rooftop::HookCalls to allow us to push things into a list of hooks in the right order
|
5
|
+
base.include Rooftop::HookCalls
|
4
6
|
base.extend ClassMethods
|
5
|
-
|
6
|
-
|
7
|
+
|
8
|
+
# Add the call to the :after_find hook to the list of hook calls, to be processed later.
|
9
|
+
# This is where we iterate over our previously established list of coercions, and call each
|
10
|
+
# in turn
|
11
|
+
base.send(:add_to_hook, :after_find, ->(r){
|
7
12
|
r.coercions.each do |field,coercion|
|
8
13
|
if r.respond_to?(field)
|
9
14
|
r.send("#{field}=",coercion.call(r.send(field)))
|
@@ -14,6 +19,8 @@ module Rooftop
|
|
14
19
|
|
15
20
|
module ClassMethods
|
16
21
|
# Call coerce_field() in a class to do something with the attribute. Useful for parsing dates etc.
|
22
|
+
# For example: coerce_field(date: ->(date_string) { DateTime.parse(date_string)}) to get a DateTime object from a string field. The date field will now be a DateTime.
|
23
|
+
|
17
24
|
# @param coercion [Hash] the coercion to apply - key is the field, value is a lambda
|
18
25
|
def coerce_field(*coercions)
|
19
26
|
@coercions ||= {}
|
@@ -26,7 +33,7 @@ module Rooftop
|
|
26
33
|
|
27
34
|
# Instance method to get the class's coercions
|
28
35
|
def coercions
|
29
|
-
self.class.instance_variable_get(:"@coercions")
|
36
|
+
self.class.instance_variable_get(:"@coercions") || {}
|
30
37
|
end
|
31
38
|
end
|
32
39
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Rooftop
|
2
|
+
module Content
|
3
|
+
class Collection < ::Array
|
4
|
+
def initialize(content_fields)
|
5
|
+
content_fields.each do |field|
|
6
|
+
self << Rooftop::Content::Field.new(field)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
# Find content_fields by attribute. Assume there will only be one attribute in the search
|
11
|
+
def find_by(hash)
|
12
|
+
raise ArgumentError, "you can only find a field by one attribute at a time" unless hash.length == 1
|
13
|
+
attr = hash.first.first
|
14
|
+
val = hash.first.last
|
15
|
+
self.select {|l| l.send(attr) == val.to_s}
|
16
|
+
end
|
17
|
+
|
18
|
+
def named(name)
|
19
|
+
find_by(name: name.to_s).first
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Rooftop
|
2
|
+
# The Rooftop API returns content for basic and advanced custom fields together. This module
|
3
|
+
# cleans up the response, and creates a collection of ContentField objects, with which we can do
|
4
|
+
# things like parse links.
|
5
|
+
module Content
|
6
|
+
def self.included(base)
|
7
|
+
base.include Rooftop::HookCalls
|
8
|
+
base.send(:add_to_hook, :after_find, ->(r) {
|
9
|
+
# basic content is the stuff which comes from WP by default.
|
10
|
+
if r.respond_to?(:content)
|
11
|
+
basic_fields = r.content[:basic].collect {|k,v| {name: k, value: v, fieldset: "Basic"}}
|
12
|
+
# advanced fields from from ACF, and are exposed in the api in this form:
|
13
|
+
# [
|
14
|
+
# {
|
15
|
+
# "title"=>"The fieldset title",
|
16
|
+
# "fields"=>[
|
17
|
+
# {"name"=>"field name", "label"=>"display name", "class"=>"type of field", "value"=>"The value of the field"},
|
18
|
+
# {"name"=>"field name", "label"=>"display name", "class"=>"type of field",
|
19
|
+
# "value"=>"The value of the field"},
|
20
|
+
# etc.
|
21
|
+
# ]
|
22
|
+
# }
|
23
|
+
# ]
|
24
|
+
# Given that's a bit convoluted, we get both the content types into the same output format, like this:
|
25
|
+
# {"field name", "label"=>"display name", "class"=>"type of field", "value"=>"value of the field", "fieldset"=>"fieldset if there is one, or Basic for the builtin ones"}
|
26
|
+
advanced_fields = r.content[:advanced].collect do |fieldset|
|
27
|
+
fieldset[:fields].each do |field|
|
28
|
+
field.merge!(fieldset: fieldset[:title])
|
29
|
+
field[:type] = field[:class]
|
30
|
+
field.delete(:class)
|
31
|
+
end
|
32
|
+
fieldset[:fields]
|
33
|
+
end.flatten
|
34
|
+
r.fields = Rooftop::Content::Collection.new((basic_fields + advanced_fields))
|
35
|
+
end
|
36
|
+
})
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# This module allows you to alias one field as another. There's a bit of a circuitous route to
|
2
|
+
# getting it done, because you need to push the after_find hook call onto the end of the hash of
|
3
|
+
# existing hook calls. See Rooftop::HookCalls for more details.
|
4
|
+
|
5
|
+
module Rooftop
|
6
|
+
module FieldAliases
|
7
|
+
def self.included(base)
|
8
|
+
# Include Rooftop::HookCalls to allow us to push things into a list of hooks in the right order
|
9
|
+
base.include Rooftop::HookCalls
|
10
|
+
base.extend ClassMethods
|
11
|
+
|
12
|
+
# Add the call to the :after_find hook to the list of hook calls, to be processed later.
|
13
|
+
# This is where we iterate over our previously established list of field aliases.
|
14
|
+
base.send(:add_to_hook, :after_find, ->(r){
|
15
|
+
r.field_aliases.each do |old, new|
|
16
|
+
if r.respond_to?(old)
|
17
|
+
r.send("#{new}=",r.send(old))
|
18
|
+
end
|
19
|
+
end
|
20
|
+
})
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
module ClassMethods
|
25
|
+
# Call alias_field(foo: :bar) in a class to alias the foo as bar.
|
26
|
+
# @param aliases [Sym] a hash of old and new field names
|
27
|
+
def alias_field(*aliases)
|
28
|
+
@field_aliases ||= {}
|
29
|
+
aliases.each do |alias_hash|
|
30
|
+
@field_aliases.merge!(alias_hash)
|
31
|
+
end
|
32
|
+
@field_aliases
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Class method to get the class's field aliases
|
37
|
+
def field_aliases
|
38
|
+
self.class.instance_variable_get(:"@field_aliases") || {}
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -2,7 +2,7 @@ module Rooftop
|
|
2
2
|
class Headers < Faraday::Middleware
|
3
3
|
def call(env)
|
4
4
|
unless Rooftop.configuration.api_token.nil?
|
5
|
-
env[:request_headers]["
|
5
|
+
env[:request_headers]["Api-Token"] = Rooftop.configuration.api_token
|
6
6
|
end
|
7
7
|
|
8
8
|
Rooftop.configuration.extra_headers.each do |key,value|
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# This module exists because call order on the hooks provided by Her is important in some cases.
|
2
|
+
# For example in Rooftop::FieldAliases we are aliasing the content of a field, which might need to
|
3
|
+
# have been coerced first. So we control the order by writing (in a known order) to @hook_calls, and
|
4
|
+
# then iterating over them.
|
5
|
+
module Rooftop
|
6
|
+
module HookCalls
|
7
|
+
def self.included(base)
|
8
|
+
base.extend ClassMethods
|
9
|
+
# Iterate over an instance var which is a hash of types of hook, and blocks to call
|
10
|
+
# like this {after_find: [->{something}, ->{something}]}
|
11
|
+
base.instance_variable_set(:"@hook_calls", base.instance_variable_get(:"@hook_calls") || {})
|
12
|
+
end
|
13
|
+
|
14
|
+
module ClassMethods
|
15
|
+
# Add something to the list of hook calls, for a particular hook. This is called in other mixins where something is being added to a hook (probably :after_find). For example Rooftop::FieldAliases and Rooftop::FieldCoercions
|
16
|
+
def add_to_hook(hook, block)
|
17
|
+
# get existing hook calls
|
18
|
+
hook_calls = instance_variable_get(:"@hook_calls")
|
19
|
+
# add new one for the appropriate hook
|
20
|
+
if hook_calls[hook].is_a?(Array)
|
21
|
+
hook_calls[hook] << block
|
22
|
+
else
|
23
|
+
hook_calls[hook] = [block]
|
24
|
+
end
|
25
|
+
instance_variable_set(:"@hook_calls",hook_calls)
|
26
|
+
end
|
27
|
+
|
28
|
+
# A method to call the hooks. This iterates over each of the types of hook (:after_find,
|
29
|
+
# :before_save etc, identified here: https://github.com/remiprev/her#callbacks) and sets up
|
30
|
+
# the actual hook. All this is worth it to control order.
|
31
|
+
def setup_hooks!
|
32
|
+
instance_variable_get(:"@hook_calls").each do |type, calls|
|
33
|
+
calls.each do |call|
|
34
|
+
self.send(type, call)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
File without changes
|
@@ -0,0 +1,9 @@
|
|
1
|
+
module Rooftop
|
2
|
+
class Menu
|
3
|
+
include Rooftop::Base
|
4
|
+
has_many :menu_items, class: "Rooftop::MenuItem"
|
5
|
+
self.api_namespace = "wp-api-menus"
|
6
|
+
self.api_version = 2
|
7
|
+
# coerce_field items: ->(items) { items.collect {|i| MenuItem.new(i)} unless items.nil?}
|
8
|
+
end
|
9
|
+
end
|
File without changes
|
File without changes
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Rooftop
|
2
|
+
module Nested
|
3
|
+
|
4
|
+
def ancestors
|
5
|
+
if respond_to?(:resource_links)
|
6
|
+
resource_links.find_by(link_type: "#{Rooftop::ResourceLinks::CUSTOM_LINK_RELATION_BASE}/ancestors")
|
7
|
+
else
|
8
|
+
[]
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def children
|
13
|
+
if respond_to?(:resource_links)
|
14
|
+
resource_links.find_by(link_type: "#{Rooftop::ResourceLinks::CUSTOM_LINK_RELATION_BASE}/children")
|
15
|
+
else
|
16
|
+
[]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def parent
|
21
|
+
if respond_to?(:resource_links)
|
22
|
+
resource_links.find_by(link_type: "up").first
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|