rooftop 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 0267eebc56ea252a961d42eab13531197d5b877b
4
+ data.tar.gz: a66492c5b3a875fa05319456ad8cc75aa229de80
5
+ SHA512:
6
+ metadata.gz: 42a735d1e99e4289de01a2561f63624aeb05eeb73c9cd6c203eda2789dd20098502f9b20e445163ff9f5db76f3cd1270553dfd23b327d25e6a8bd874b20dadc5
7
+ data.tar.gz: 00eee33e7a992852c9a70fcc356b4eeef69c438ca1480d3605c817960500f635d69bfa895882c052c6634088e46cdd1e7be7cc80d25e218488c17ef59bddc2db
data/.gitignore ADDED
@@ -0,0 +1,23 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
23
+ .idea
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in rooftop_ruby_client.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Ed Jones
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,68 @@
1
+ # Rooftop
2
+ A mixin for Ruby classes to access the Wordpress REST API (http://wp-api.org)
3
+
4
+ # Setup
5
+
6
+ ## Configuration
7
+ You need to configure Rooftop with a block, like this
8
+
9
+ ```
10
+ Rooftop.configure do |config|
11
+ config.url = "http://your.rooftop-cms.site/wp-json"
12
+ end
13
+ ```
14
+
15
+ # Use
16
+ Create a class and use one of the mixins to get the data.
17
+
18
+ ## Rooftop::Post
19
+ The Rooftop::Post mixin lets you specify a post type, so the API differentiates between types.
20
+
21
+ ```
22
+ class MyCustomPostType
23
+ include Rooftop::Post
24
+ self.post_type = "my_custom_post_type" #this is the post type name in Wordpress
25
+ end
26
+ ```
27
+ ## Rooftop::Page
28
+ The Rooftop::Page mixin identifies this class as a page.
29
+ ```
30
+ class Page
31
+ include Rooftop::Page
32
+ end
33
+ ```
34
+
35
+ ## 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.
37
+
38
+ To coerce one or more fields, call a class method in your class and pass a lambda which is called on the field.
39
+
40
+ ```
41
+ class MyCustomPostType
42
+ include Rooftop::Post
43
+ self.post_type = "my_custom_post_type"
44
+ coerce_field date: ->(date) { DateTime.parse(date)}
45
+ ```
46
+
47
+ There are some coercions done manually.
48
+
49
+ ### Author
50
+ When an object is returned from the API, the Author information is automatically parsed into a Rooftop::Author object.
51
+
52
+ ### Date
53
+ Created date is coerced to a DateTime. It's also aliased to created_at
54
+
55
+ ### Modified
56
+ The modification date is coerced to a DateTime. It's also aliased to updated_at
57
+
58
+ # To do
59
+ Lots! Here's a flavour:
60
+
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
66
+
67
+
68
+
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
data/lib/rooftop.rb ADDED
@@ -0,0 +1,59 @@
1
+ require 'her'
2
+ require 'require_all'
3
+ require_rel '.'
4
+
5
+ require "active_support/all"
6
+
7
+ module Rooftop
8
+ class << self
9
+ #accessor to set the preview API for use instead of the production one
10
+ attr_accessor :use_preview_api
11
+
12
+ #access the configuration class as Rooftop.configuration
13
+ attr_accessor :configuration
14
+
15
+ #block for configuration.
16
+ def configure
17
+ self.configuration ||= Configuration.new
18
+ yield(configuration)
19
+ self.configuration.configure_connection
20
+ end
21
+
22
+ end
23
+
24
+ class Configuration
25
+ attr_accessor :api_token, :url, :extra_headers
26
+ attr_reader :connection
27
+
28
+ def initialize
29
+ @extra_headers ||= []
30
+ @connection ||= Her::API.new
31
+ end
32
+
33
+ # Return the Configuration object as a hash, with symbols as keys.
34
+ # @return [Hash]
35
+ def to_hash
36
+ Hash[instance_variables.map { |name| [name.to_s.gsub("@","").to_sym, instance_variable_get(name)] } ]
37
+ end
38
+
39
+ def configure_connection
40
+ if @url.nil?
41
+ raise ArgumentError, "You need to configure Rooftop before instantiating a class with a Rooftop mixin"
42
+ end
43
+
44
+ @connection.setup url: @url do |c|
45
+ #Headers
46
+ c.use Rooftop::Headers
47
+
48
+ # Request
49
+ c.use Faraday::Request::UrlEncoded
50
+
51
+ # Response
52
+ c.use Her::Middleware::DefaultParseJSON
53
+
54
+ # Adapter
55
+ c.use Faraday::Adapter::NetHttp
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,38 @@
1
+ module Rooftop
2
+ module Base
3
+ def self.included(base)
4
+ base.include Her::Model
5
+ # Coercions allow you to pass a block to do something with a returned field
6
+ base.include Rooftop::Coercions
7
+ # Queries mixin includes a fixup for there `where()` method
8
+ base.include Rooftop::Queries
9
+ # Use the API instance we have configured - in a proc because we can't control load order
10
+ base.send(:use_api,->{Rooftop.configuration.connection})
11
+ # WP returns an uppercase attribute for ID. Annoying.
12
+ # base.send(:primary_key, :"ID")
13
+ # Date and Modified fields are pretty universal in responses from WP, so we can automatically
14
+ # coerce these to DateTime.
15
+ base.send(:coerce_field,date: ->(date) {DateTime.parse(date)})
16
+ base.send(:coerce_field,modified: ->(modified) {DateTime.parse(modified)})
17
+ base.extend ClassMethods
18
+ end
19
+
20
+ module ClassMethods
21
+ # Allow calling 'first'
22
+ def first
23
+ all.first
24
+ end
25
+ end
26
+
27
+ # Utility method to make the 'created' field have a more rubyish name
28
+ def created_at
29
+ date
30
+ end
31
+
32
+ # Utility method to make the 'updated' field have a more rubyish name
33
+ def updated_at
34
+ modified
35
+ end
36
+
37
+ end
38
+ end
@@ -0,0 +1,24 @@
1
+ module Rooftop
2
+ module Client
3
+ def self.included(base)
4
+ if Rooftop.configuration.url.nil?
5
+ raise ArgumentError, "You need to configure Rooftop before instantiating a class with a Rooftop mixin"
6
+ end
7
+
8
+ Her::API.setup url: Rooftop.configuration.url do |c|
9
+ #Headers
10
+ c.use Rooftop::Headers
11
+
12
+ # Request
13
+ c.use Faraday::Request::UrlEncoded
14
+
15
+ # Response
16
+ c.use Her::Middleware::DefaultParseJSON
17
+
18
+ # Adapter
19
+ c.use Faraday::Adapter::NetHttp
20
+ end
21
+ base.include Her::Model
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,8 @@
1
+ module Rooftop
2
+ module AuthorCoercion
3
+ def self.included(base)
4
+ base.send(:after_find, ->(r) { r.author.registered = DateTime.parse(r.author.registered)})
5
+ base.send(:coerce_field, {author: ->(author) { Rooftop::Author.new(author) }})
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,45 @@
1
+ # Coerce any field called 'parent' which returns an ID into an actual object
2
+ module Rooftop
3
+ module ParentCoercion
4
+ def self.included(base)
5
+ base.extend ClassMethods
6
+ # base.send(:after_find, ->(r) {
7
+ # if r.has_parent?
8
+ # r.instance_variable_set(:"parent_#{base.to_s.underscore}", resolve_parent_id())
9
+ # r.class.send(:attr_reader, :"parent_#{base.to_s.underscore}")
10
+ # end
11
+ # })
12
+ # base.send(:coerce_field, parent: ->(p) { base.send(:resolve_parent_id,p) })
13
+ end
14
+
15
+ module ClassMethods
16
+ def add_parent_reference
17
+ define_method :"parent_#{self.to_s.underscore}" do
18
+ puts "hello"
19
+ end
20
+ end
21
+ end
22
+
23
+ def has_parent?
24
+ respond_to?(:parent) && parent.is_a?(Fixnum) && parent != 0
25
+ end
26
+
27
+ def resolve_parent_id
28
+ if respond_to?(:parent)
29
+ if parent.is_a?(Fixnum)
30
+ if parent == 0
31
+ #no parent
32
+ return nil
33
+ else
34
+ return self.class.send(:find, id)
35
+ end
36
+ else
37
+ return parent
38
+ end
39
+
40
+ end
41
+
42
+ end
43
+ end
44
+
45
+ end
@@ -0,0 +1,32 @@
1
+ module Rooftop
2
+ module Coercions
3
+ def self.included(base)
4
+ 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
+ r.coercions.each do |field,coercion|
8
+ if r.respond_to?(field)
9
+ r.send("#{field}=",coercion.call(r.send(field)))
10
+ end
11
+ end
12
+ })
13
+ end
14
+
15
+ module ClassMethods
16
+ # Call coerce_field() in a class to do something with the attribute. Useful for parsing dates etc.
17
+ # @param coercion [Hash] the coercion to apply - key is the field, value is a lambda
18
+ def coerce_field(*coercions)
19
+ @coercions ||= {}
20
+ coercions.each do |coercions_hash|
21
+ @coercions.merge!(coercions_hash)
22
+ end
23
+ @coercions
24
+ end
25
+ end
26
+
27
+ # Instance method to get the class's coercions
28
+ def coercions
29
+ self.class.instance_variable_get(:"@coercions")
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,15 @@
1
+ module Rooftop
2
+ class Headers < Faraday::Middleware
3
+ def call(env)
4
+ unless Rooftop.configuration.api_token.nil?
5
+ env[:request_headers]["API-TOKEN"] = Rooftop.configuration.api_token
6
+ end
7
+
8
+ Rooftop.configuration.extra_headers.each do |key,value|
9
+ env[:request_headers][key.to_s] = value
10
+ end
11
+ env[:request_headers]["User-Agent"] = Rooftop.configuration.user_agent
12
+ @app.call(env)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,18 @@
1
+ module Rooftop
2
+ class Author
3
+ def initialize(args)
4
+ args.each do |k,v|
5
+ instance_variable_set("@#{k}", v)
6
+ self.class.send(:attr_accessor, k)
7
+ end
8
+ end
9
+
10
+ def id
11
+ self.instance_variable_get(:"@ID")
12
+ end
13
+
14
+ def ==(other)
15
+ other.respond_to?(:id) && other.id == id
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,6 @@
1
+ module Rooftop
2
+ class MediaItem
3
+ include Rooftop::Base
4
+ collection_path "media"
5
+ end
6
+ end
@@ -0,0 +1,8 @@
1
+ module Rooftop
2
+ class Menu
3
+ include Rooftop::Base
4
+ has_many :menu_items, class: "Rooftop::MenuItem"
5
+ collection_path "menus"
6
+ coerce_field items: ->(items) { items.collect {|i| MenuItem.new(i)} unless items.nil?}
7
+ end
8
+ end
@@ -0,0 +1,18 @@
1
+ module Rooftop
2
+ class MenuItem
3
+ def initialize(args)
4
+ args.each do |k,v|
5
+ instance_variable_set("@#{k}", v)
6
+ self.class.send(:attr_accessor, k)
7
+ end
8
+ end
9
+
10
+ def id
11
+ self.instance_variable_get(:"@ID")
12
+ end
13
+
14
+ def ==(other)
15
+ other.class == self.class && other.respond_to?(:id) && other.id == id
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,8 @@
1
+ module Rooftop
2
+ class Taxonomy
3
+ include Rooftop::Base
4
+ collection_path "taxonomies"
5
+ primary_key "slug"
6
+ has_many :terms, class_name: "Rooftop::TaxonomyTerm"
7
+ end
8
+ end
@@ -0,0 +1,6 @@
1
+ module Rooftop
2
+ class TaxonomyTerm
3
+ include Rooftop::Base
4
+ primary_key "ID"
5
+ end
6
+ end
@@ -0,0 +1,19 @@
1
+ module Rooftop
2
+ module Page
3
+ def self.included(base)
4
+ base.include Rooftop::Base
5
+ #TODO instead of the silly coercion, we should be able to use Her to do this
6
+ # has_one :parent, data_field: :parent, class_name: "Page", path: "/pages"
7
+ base.include Rooftop::ParentCoercion
8
+ base.extend ClassMethods
9
+ base.send(:collection_path,"pages")
10
+ end
11
+
12
+ module ClassMethods
13
+
14
+ end
15
+
16
+
17
+
18
+ end
19
+ end
@@ -0,0 +1,15 @@
1
+ module Rooftop
2
+ module Post
3
+ def self.included(base)
4
+ base.include Rooftop::Base
5
+ base.extend ClassMethods
6
+ base.send(:collection_path,"posts")
7
+ end
8
+
9
+ module ClassMethods
10
+ def post_type=(type)
11
+ self.send(:default_scope, ->{ where(type: type) })
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,24 @@
1
+ module Rooftop
2
+ module Queries
3
+ def self.included(base)
4
+ base.extend ClassMethods
5
+ end
6
+
7
+ module ClassMethods
8
+ # We need to fix up the `where()` filter. WP-API expects a url format for filters like this:
9
+ # /?filter[something]=foo.
10
+ def where(args)
11
+ args = HashWithIndifferentAccess.new(args)
12
+ # the fact that 'slug' is referred to in the db as 'name' is irritating. Let's fix that
13
+ # in queries so we can specify {slug: "foo"}
14
+ if args.keys.collect(&:to_sym).include?(:slug)
15
+ args[:name] = args[:slug]
16
+ args.delete(:slug)
17
+ end
18
+ filters = args.inject({}) {|hash,pair| hash["filter[#{pair.first}]"] = pair.last; hash}
19
+ #Call the Her `where` method with our new filters
20
+ super().where(filters)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,3 @@
1
+ module Rooftop
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'rooftop_client/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "rooftop"
8
+ spec.version = Rooftop::VERSION
9
+ spec.authors = ["Ed Jones"]
10
+ spec.email = ["ed@errorstudio.co.uk"]
11
+ spec.summary = "An ActiveRecord-like interface to the Rooftop CMS JSON API"
12
+ spec.description = "An ActiveRecord-like interface to the Rooftop CMS JSON API"
13
+ spec.homepage = "http://www.rooftopcms.com"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.6"
22
+ spec.add_development_dependency "rake"
23
+
24
+ spec.add_dependency "activesupport"
25
+ spec.add_dependency "require_all"
26
+ spec.add_dependency "her"
27
+
28
+ end
metadata ADDED
@@ -0,0 +1,137 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rooftop
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Ed Jones
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-10-14 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.6'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.6'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: activesupport
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: require_all
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: her
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: An ActiveRecord-like interface to the Rooftop CMS JSON API
84
+ email:
85
+ - ed@errorstudio.co.uk
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - .gitignore
91
+ - Gemfile
92
+ - LICENSE.txt
93
+ - README.md
94
+ - Rakefile
95
+ - lib/rooftop.rb
96
+ - lib/rooftop_client/base.rb
97
+ - lib/rooftop_client/client.rb
98
+ - lib/rooftop_client/coercions.rb
99
+ - lib/rooftop_client/coercions/author_coercion.rb
100
+ - lib/rooftop_client/coercions/parent_coercion.rb
101
+ - lib/rooftop_client/headers.rb
102
+ - lib/rooftop_client/models/author.rb
103
+ - lib/rooftop_client/models/media_item.rb
104
+ - lib/rooftop_client/models/menu.rb
105
+ - lib/rooftop_client/models/menu_item.rb
106
+ - lib/rooftop_client/models/taxonomy.rb
107
+ - lib/rooftop_client/models/taxonomy_term.rb
108
+ - lib/rooftop_client/page.rb
109
+ - lib/rooftop_client/post.rb
110
+ - lib/rooftop_client/queries/queries.rb
111
+ - lib/rooftop_client/version.rb
112
+ - rooftop_ruby_client.gemspec
113
+ homepage: http://www.rooftopcms.com
114
+ licenses:
115
+ - MIT
116
+ metadata: {}
117
+ post_install_message:
118
+ rdoc_options: []
119
+ require_paths:
120
+ - lib
121
+ required_ruby_version: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ required_rubygems_version: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - '>='
129
+ - !ruby/object:Gem::Version
130
+ version: '0'
131
+ requirements: []
132
+ rubyforge_project:
133
+ rubygems_version: 2.2.2
134
+ signing_key:
135
+ specification_version: 4
136
+ summary: An ActiveRecord-like interface to the Rooftop CMS JSON API
137
+ test_files: []