rooftop 0.0.1 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +621 -22
  3. data/README.md +168 -25
  4. data/lib/rooftop/base.rb +74 -0
  5. data/lib/{rooftop_client → rooftop}/client.rb +0 -0
  6. data/lib/rooftop/coercions/author_coercion.rb +10 -0
  7. data/lib/rooftop/coercions/parent_coercion.rb +47 -0
  8. data/lib/{rooftop_client → rooftop}/coercions.rb +10 -3
  9. data/lib/rooftop/content_fields/collection.rb +23 -0
  10. data/lib/rooftop/content_fields/content_fields.rb +39 -0
  11. data/lib/rooftop/content_fields/field.rb +12 -0
  12. data/lib/rooftop/errors/record_not_found.rb +3 -0
  13. data/lib/rooftop/field_aliases.rb +41 -0
  14. data/lib/{rooftop_client → rooftop}/headers.rb +1 -1
  15. data/lib/rooftop/hook_calls.rb +40 -0
  16. data/lib/{rooftop_client → rooftop}/models/author.rb +0 -0
  17. data/lib/{rooftop_client → rooftop}/models/media_item.rb +1 -1
  18. data/lib/rooftop/models/menu.rb +9 -0
  19. data/lib/{rooftop_client → rooftop}/models/menu_item.rb +0 -0
  20. data/lib/{rooftop_client → rooftop}/models/taxonomy.rb +0 -0
  21. data/lib/{rooftop_client → rooftop}/models/taxonomy_term.rb +1 -1
  22. data/lib/rooftop/nested.rb +26 -0
  23. data/lib/rooftop/page.rb +17 -0
  24. data/lib/{rooftop_client → rooftop}/post.rb +2 -2
  25. data/lib/{rooftop_client → rooftop}/queries/queries.rb +11 -0
  26. data/lib/rooftop/resource_links/collection.rb +22 -0
  27. data/lib/rooftop/resource_links/link.rb +15 -0
  28. data/lib/rooftop/resource_links/resource_links.rb +15 -0
  29. data/lib/rooftop/version.rb +3 -0
  30. data/lib/rooftop.rb +54 -7
  31. data/rooftop_ruby_client.gemspec +3 -2
  32. metadata +43 -19
  33. data/lib/rooftop_client/base.rb +0 -38
  34. data/lib/rooftop_client/coercions/author_coercion.rb +0 -8
  35. data/lib/rooftop_client/coercions/parent_coercion.rb +0 -45
  36. data/lib/rooftop_client/models/menu.rb +0 -8
  37. data/lib/rooftop_client/page.rb +0 -19
  38. data/lib/rooftop_client/version.rb +0 -3
data/README.md CHANGED
@@ -1,68 +1,211 @@
1
1
  # Rooftop
2
- A mixin for Ruby classes to access the Wordpress REST API (http://wp-api.org)
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 Rooftop with a block, like this
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://your.rooftop-cms.site/wp-json"
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 use one of the mixins to get the data.
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 API differentiates between types.
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 Wordpress
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 Rooftop::Page mixin identifies this class as a page.
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. For example, it would be useful to parse date strings to DateTime.
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 MyCustomPostType
42
- include Rooftop::Post
85
+ class mycustomposttype
86
+ include rooftop::post
43
87
  self.post_type = "my_custom_post_type"
44
- coerce_field date: ->(date) { DateTime.parse(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
- ### Author
50
- When an object is returned from the API, the Author information is automatically parsed into a Rooftop::Author object.
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
- ### Date
53
- Created date is coerced to a DateTime. It's also aliased to created_at
195
+ Copyright 2015 Error Ltd.
54
196
 
55
- ### Modified
56
- The modification date is coerced to a DateTime. It's also aliased to updated_at
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
- # To do
59
- Lots! Here's a flavour:
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
- * Taxonomies need to be supported (http://wp-api.org/#taxonomies_retrieve-all-taxonomies)
62
- * Authentication: there are a couple of ways of doing this, but the simplest would be a quick WP plugin to generate a per-user API key which we pass in the header.
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
 
@@ -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
- # `after_find` is a method provided by Her; we iterate over our coercions and call each lambda
6
- base.send(:after_find, ->(r){
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,12 @@
1
+ module Rooftop
2
+ module Content
3
+ class Field < ::OpenStruct
4
+
5
+
6
+ def to_s
7
+ value if respond_to?(:value)
8
+ end
9
+
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,3 @@
1
+ module Rooftop
2
+ class RecordNotFound < StandardError; end
3
+ 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]["API-TOKEN"] = Rooftop.configuration.api_token
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
@@ -1,6 +1,6 @@
1
1
  module Rooftop
2
2
  class MediaItem
3
3
  include Rooftop::Base
4
- collection_path "media"
4
+ self.api_endpoint = "media"
5
5
  end
6
6
  end
@@ -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
@@ -1,6 +1,6 @@
1
1
  module Rooftop
2
2
  class TaxonomyTerm
3
3
  include Rooftop::Base
4
- primary_key "ID"
4
+
5
5
  end
6
6
  end
@@ -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
@@ -0,0 +1,17 @@
1
+ module Rooftop
2
+ module Page
3
+ def self.included(base)
4
+ base.include Rooftop::Base
5
+ base.include Rooftop::Nested
6
+ base.extend ClassMethods
7
+ end
8
+
9
+ module ClassMethods
10
+
11
+
12
+ end
13
+
14
+
15
+
16
+ end
17
+ end