pco_api 1.0.0

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: 6389ae53bf68d7cd03849bc44f51776dd0c8705a
4
+ data.tar.gz: d0a6ef6c002c42787f00e8e3525f407959eebbf0
5
+ SHA512:
6
+ metadata.gz: edad5401340bd1a6c87f7cc360c8fc09af04d2eebc61de5fed0eba57dbc8d92ce6db43253d810d786b25c475a5e8b17367e42af5d475d4276573034ca90bc374
7
+ data.tar.gz: ff3c7b758c84e4331b16356689111ff717c1bf322fd2fa2888a4af3838c59360c56263e1e020c18b0a9884fcf26f8af3726d973b35d94b4928651bfc9782f0e4
data/README.md ADDED
@@ -0,0 +1,208 @@
1
+ # pco_api
2
+
3
+ `pco_api` is a Rubygem that provides a simple wrapper around our RESTful JSON api at https://api.planningcenteronline.com.
4
+
5
+ ## Installation
6
+
7
+ ```
8
+ gem install pco_api
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ 1. Create a new API object, passing in your credentials (either HTTP Basic or OAuth2 access token):
14
+
15
+ ```ruby
16
+ # authenticate with HTTP Basic:
17
+ api = PCO::API.new(basic_auth_token: 'token', basic_auth_secret: 'secret')
18
+ # ...or authenticate with an OAuth2 access token (use the 'oauth2' gem to obtain the token)
19
+ api = PCO::API.new(oauth_access_token: 'token')
20
+ ```
21
+
22
+ 2. Chain path elements together as method calls.
23
+
24
+ ```ruby
25
+ api.people.v1.households
26
+ # /people/v1/households
27
+ ```
28
+
29
+ 3. For IDs, treat the object like a hash (use square brackets).
30
+
31
+ ```ruby
32
+ api.people.v1.households[1]
33
+ # /people/v1/households/1
34
+ ```
35
+
36
+ 4. To execute the request, use `get`, `post`, `patch`, or `delete`, optionally passing arguments.
37
+
38
+ ```ruby
39
+ api.people.v1.households.get(order: 'name')
40
+ # GET /people/v1/households?order=name
41
+ ```
42
+
43
+ ## Example
44
+
45
+ ```ruby
46
+ require 'pco_api'
47
+
48
+ api = PCO::API.new(auth_token: 'token', auth_secret: 'secret')
49
+ api.people.v1.people.get(order: 'last_name')
50
+ ```
51
+
52
+ ...which returns something like:
53
+
54
+ ```ruby
55
+ {
56
+ "links" => {
57
+ "self" => "https://api.planningcenteronline.com/people/v1/people?order=last_name",
58
+ "next" => "https://api.planningcenteronline.com/people/v1/people?offset=25&order=last_name"
59
+ },
60
+ "data"=> [
61
+ {
62
+ "type" => "Person",
63
+ "id" => "271",
64
+ "first_name" => "Jean",
65
+ "middle_name" => nil,
66
+ "last_name" => "Abernathy",
67
+ "birthdate" => "1885-01-01",
68
+ "anniversary" => nil,
69
+ "gender" => "F",
70
+ "grade" => -1,
71
+ "child" => false,
72
+ "status" => "active",
73
+ "school_type" => nil,
74
+ "graduation_year" => nil,
75
+ "site_administrator" => false,
76
+ "people_permissions" => nil,
77
+ "created_at" => "2015-04-01T20:18:22Z",
78
+ "updated_at" => "2015-04-10T18:59:51Z",
79
+ "avatar" => nil,
80
+ "links" => {
81
+ "self" => "https://api.planningcenteronline.com/people/v1/people/271"
82
+ }
83
+ },
84
+ # ...
85
+ ],
86
+ "meta" => {
87
+ "total_count" => 409,
88
+ "count" => 25,
89
+ "next" => {
90
+ "offset" => 25
91
+ },
92
+ "orderable_by" => [
93
+ "first_name",
94
+ "middle_name",
95
+ "last_name",
96
+ "birthdate",
97
+ "anniversary",
98
+ "gender",
99
+ "grade",
100
+ "child",
101
+ "status",
102
+ "school_type",
103
+ "graduation_year",
104
+ "site_administrator",
105
+ "people_permissions",
106
+ "created_at",
107
+ "updated_at"
108
+ ],
109
+ "filterable_by" => [
110
+ "first_name",
111
+ "middle_name",
112
+ "last_name",
113
+ "birthdate",
114
+ "anniversary",
115
+ "gender",
116
+ "grade",
117
+ "child",
118
+ "status",
119
+ "school_type",
120
+ "graduation_year",
121
+ "site_administrator",
122
+ "people_permissions",
123
+ "created_at",
124
+ "updated_at"
125
+ ],
126
+ "can_include" => [
127
+ "emails",
128
+ "addresses",
129
+ "phone_numbers",
130
+ "households",
131
+ "school",
132
+ "inactive_reason",
133
+ "marital_status",
134
+ "name_prefix",
135
+ "name_suffix",
136
+ "field_data",
137
+ "apps"
138
+ ]
139
+ }
140
+ }
141
+ ```
142
+
143
+ ## get()
144
+
145
+ `get()` works for a collection (index) and a single resource (show).
146
+
147
+ ```ruby
148
+ # collection
149
+ api.people.v1.people.get(order: 'last_name')
150
+ # => { data: array_of_resources }
151
+
152
+ # single resource
153
+ api.people.v1.people[1].get
154
+ # => { data: resource_hash }
155
+ ```
156
+
157
+ ## post()
158
+
159
+ `post()` sends a POST request to create a new resource. You *must* wrap your resource with
160
+ a `{ data: { ... } }` hash.
161
+
162
+ ```ruby
163
+ api.people.v1.people.post(data: { first_name: 'Tim', last_name: 'Morgan' })
164
+ # => { data: resource_hash }
165
+ ```
166
+
167
+ ## patch()
168
+
169
+ `patch()` sends a PATCH request to update an existing resource. You *must* wrap your resource with
170
+ a `{ data: { ... } }` hash.
171
+
172
+ ```ruby
173
+ api.people.v1.people[1].patch(data: { first_name: 'Tim', last_name: 'Morgan' })
174
+ # => { data: resource_hash }
175
+ ```
176
+
177
+ ## delete()
178
+
179
+ `delete()` sends a DELETE request to delete an existing resource. This method returns `true` if the delete was successful.
180
+
181
+ ```ruby
182
+ api.people.v1.people[1].delete
183
+ # => true
184
+ ```
185
+
186
+ ## Errors
187
+
188
+ The following errors may be raised, which you should rescue in most circumstances.
189
+
190
+ | HTTP Status Codes | Error Class |
191
+ | ------------------- | ------------------------------- |
192
+ | 404 | `PCO::API::Errors::NotFound` |
193
+ | 4xx (except 404) | `PCO::API::Errors::ClientError` |
194
+ | 5xx | `PCO::API::Errors::ServerError` |
195
+
196
+ The exception class has the following methods:
197
+
198
+ | Method | Content |
199
+ | ------- | ----------------------------------------------- |
200
+ | status | HTTP status code returned by the server |
201
+ | message | the body of the response returned by the server |
202
+
203
+ The `message` will usually be a hash (produced by parsing the response JSON),
204
+ but in the case of some server errors, may be a string containing the raw response.
205
+
206
+ ## Copyright & License
207
+
208
+ Copyright 2015, Ministry Centered Technologies. Licensed MIT.
data/lib/pco/api.rb ADDED
@@ -0,0 +1,11 @@
1
+ require_relative 'api/endpoint'
2
+ require_relative 'api/errors'
3
+
4
+ module PCO
5
+ module API
6
+ module_function
7
+ def new(*args)
8
+ Endpoint.new(*args)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,108 @@
1
+ require 'faraday'
2
+ require 'faraday_middleware'
3
+
4
+ module PCO
5
+ module API
6
+ URL = 'https://api.planningcenteronline.com'
7
+
8
+ class Endpoint
9
+ attr_reader :url, :last_result
10
+
11
+ def initialize(url: URL, oauth_access_token: nil, basic_auth_token: nil, basic_auth_secret: nil, connection: nil)
12
+ @url = url
13
+ @oauth_access_token = oauth_access_token
14
+ @basic_auth_token = basic_auth_token
15
+ @basic_auth_secret = basic_auth_secret
16
+ @connection = connection || _build_connection
17
+ @cache = {}
18
+ end
19
+
20
+ def method_missing(method_name, *_args)
21
+ _build_endpoint(method_name.to_s)
22
+ end
23
+
24
+ def [](id)
25
+ _build_endpoint(id.to_s)
26
+ end
27
+
28
+ def respond_to?(method_name)
29
+ endpoint = _build_endpoint(method_name.to_s)
30
+ begin
31
+ endpoint.get
32
+ rescue Errors::NotFound
33
+ false
34
+ else
35
+ true
36
+ end
37
+ end
38
+
39
+ def get(params = {})
40
+ @last_result = @connection.get(@url, params)
41
+ _build_response(@last_result)
42
+ end
43
+
44
+ def post(body = {})
45
+ @last_result = @connection.post(@url) do |req|
46
+ req.body = body.to_json
47
+ end
48
+ _build_response(@last_result)
49
+ end
50
+
51
+ def patch(body = {})
52
+ @last_result = @connection.patch(@url) do |req|
53
+ req.body = body.to_json
54
+ end
55
+ _build_response(@last_result)
56
+ end
57
+
58
+ def delete
59
+ @last_result = @connection.delete(@url)
60
+ if @last_result.status == 204
61
+ true
62
+ else
63
+ _build_response(@last_result)
64
+ end
65
+ end
66
+
67
+ private
68
+
69
+ def _build_response(result)
70
+ case result.status
71
+ when 200..299
72
+ result.body
73
+ when 404
74
+ fail Errors::NotFound, result
75
+ when 400..499
76
+ fail Errors::ClientError, result
77
+ when 500..599
78
+ fail Errors::ServerError, result
79
+ else
80
+ fail "unknown status #{result.status}"
81
+ end
82
+ end
83
+
84
+ def _build_endpoint(path)
85
+ @cache[path] ||= begin
86
+ self.class.new(
87
+ url: File.join(url, path.to_s),
88
+ connection: @connection
89
+ )
90
+ end
91
+ end
92
+
93
+ def _build_connection
94
+ Faraday.new(url: url) do |faraday|
95
+ faraday.adapter :excon
96
+ faraday.response :json, content_type: /\bjson$/
97
+ if @basic_auth_token && @basic_auth_secret
98
+ faraday.basic_auth @basic_auth_token, @basic_auth_secret
99
+ elsif @oauth_access_token
100
+ faraday.headers['Authorization'] = "Bearer #{@oauth_access_token}"
101
+ else
102
+ fail Errors::AuthRequiredError, "You must specify either HTTP basic auth credentials or an OAuth2 access token."
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,28 @@
1
+ module PCO
2
+ module API
3
+ module Errors
4
+ class AuthRequiredError < StandardError; end
5
+
6
+ class BaseError < StandardError
7
+ attr_reader :status
8
+
9
+ def initialize(response)
10
+ @status = response.status
11
+ @message = response.body
12
+ end
13
+
14
+ def to_s
15
+ @message
16
+ end
17
+
18
+ def inspect
19
+ "<#{self.class.name} status=#{@status} message=#{@message}>"
20
+ end
21
+ end
22
+
23
+ class NotFound < BaseError; end
24
+ class ClientError < BaseError; end
25
+ class ServerError < BaseError; end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,5 @@
1
+ module PCO
2
+ module API
3
+ VERSION = '1.0.0'
4
+ end
5
+ end
data/lib/pco_api.rb ADDED
@@ -0,0 +1 @@
1
+ require_relative 'pco/api'
@@ -0,0 +1,200 @@
1
+ require_relative '../../spec_helper'
2
+ require 'json'
3
+
4
+ describe PCO::API::Endpoint do
5
+ let(:base) { described_class.new(auth_token: 'token', auth_secret: 'secret') }
6
+
7
+ subject { base }
8
+
9
+ describe '#method_missing' do
10
+ before do
11
+ @result = subject.people.v1
12
+ end
13
+
14
+ it 'returns a wrapper object with updated url' do
15
+ expect(@result).to be_a(described_class)
16
+ expect(@result.url).to match(%r{/people/v1$})
17
+ end
18
+ end
19
+
20
+ describe '#[]' do
21
+ before do
22
+ @result = subject.people.v1.people[1]
23
+ end
24
+
25
+ it 'returns a wrapper object with updated url' do
26
+ expect(@result).to be_a(described_class)
27
+ expect(@result.url).to match(%r{/people/v1/people/1$})
28
+ end
29
+ end
30
+
31
+ describe '#get' do
32
+ context 'given a good URL' do
33
+ subject { base.people.v1 }
34
+
35
+ let(:result) do
36
+ {
37
+ 'type' => 'Organization',
38
+ 'id' => '1',
39
+ 'name' => 'Ministry Centered Technologies',
40
+ 'links' => {}
41
+ }
42
+ end
43
+
44
+ before do
45
+ stub_request(:get, 'https://api.planningcenteronline.com/people/v1')
46
+ .to_return(status: 200, body: { data: result }.to_json, headers: { 'Content-Type' => 'application/vnd.api+json' })
47
+ @result = subject.get
48
+ end
49
+
50
+ it 'returns the result of making a GET request to the endpoint' do
51
+ expect(@result).to be_a(Hash)
52
+ expect(@result['data']).to eq(result)
53
+ end
54
+ end
55
+
56
+ context 'given a non-existent URL' do
57
+ subject { base.people.v1.non_existent }
58
+
59
+ let(:result) do
60
+ {
61
+ 'status' => 404,
62
+ 'message' => 'Resource Not Found'
63
+ }
64
+ end
65
+
66
+ before do
67
+ stub_request(:get, 'https://api.planningcenteronline.com/people/v1/non_existent')
68
+ .to_return(status: 404, body: result.to_json, headers: { 'Content-Type' => 'application/vnd.api+json' })
69
+ end
70
+
71
+ it 'raises a NotFound error' do
72
+ expect {
73
+ subject.get
74
+ }.to raise_error(PCO::API::Errors::NotFound)
75
+ end
76
+ end
77
+
78
+ context 'given a client error' do
79
+ subject { base.people.v1.error }
80
+
81
+ let(:result) do
82
+ {
83
+ 'status' => 400,
84
+ 'message' => 'Bad request'
85
+ }
86
+ end
87
+
88
+ before do
89
+ stub_request(:get, 'https://api.planningcenteronline.com/people/v1/error')
90
+ .to_return(status: 400, body: result.to_json, headers: { 'Content-Type' => 'application/vnd.api+json' })
91
+ end
92
+
93
+ it 'raises a ClientError error' do
94
+ expect {
95
+ subject.get
96
+ }.to raise_error(PCO::API::Errors::ClientError)
97
+ end
98
+ end
99
+
100
+ context 'given a server error' do
101
+ subject { base.people.v1.error }
102
+
103
+ let(:result) do
104
+ {
105
+ 'status' => 500,
106
+ 'message' => 'System error has occurred'
107
+ }
108
+ end
109
+
110
+ before do
111
+ stub_request(:get, 'https://api.planningcenteronline.com/people/v1/error')
112
+ .to_return(status: 500, body: result.to_json, headers: { 'Content-Type' => 'application/vnd.api+json' })
113
+ end
114
+
115
+ it 'raises a ServerError error' do
116
+ expect {
117
+ subject.get
118
+ }.to raise_error(PCO::API::Errors::ServerError)
119
+ end
120
+ end
121
+ end
122
+
123
+ describe '#post' do
124
+ subject { base.people.v1.people }
125
+
126
+ let(:resource) do
127
+ {
128
+ 'type' => 'Person',
129
+ 'first_name' => 'Tim',
130
+ 'last_name' => 'Morgan'
131
+ }
132
+ end
133
+
134
+ let(:result) do
135
+ {
136
+ 'type' => 'Person',
137
+ 'id' => '1',
138
+ 'first_name' => 'Tim',
139
+ 'last_name' => 'Morgan'
140
+ }
141
+ end
142
+
143
+ before do
144
+ stub_request(:post, 'https://api.planningcenteronline.com/people/v1/people')
145
+ .to_return(status: 201, body: { data: result }.to_json, headers: { 'Content-Type' => 'application/vnd.api+json' })
146
+ @result = subject.post(data: resource)
147
+ end
148
+
149
+ it 'returns the result of making a POST request to the endpoint' do
150
+ expect(@result).to be_a(Hash)
151
+ expect(@result['data']).to eq(result)
152
+ end
153
+ end
154
+
155
+ describe '#patch' do
156
+ subject { base.people.v1.people[1] }
157
+
158
+ let(:resource) do
159
+ {
160
+ 'type' => 'Person',
161
+ 'first_name' => 'Tim',
162
+ 'last_name' => 'Morgan'
163
+ }
164
+ end
165
+
166
+ let(:result) do
167
+ {
168
+ 'type' => 'Person',
169
+ 'id' => '1',
170
+ 'first_name' => 'Tim',
171
+ 'last_name' => 'Morgan'
172
+ }
173
+ end
174
+
175
+ before do
176
+ stub_request(:patch, 'https://api.planningcenteronline.com/people/v1/people/1')
177
+ .to_return(status: 200, body: { data: result }.to_json, headers: { 'Content-Type' => 'application/vnd.api+json' })
178
+ @result = subject.patch(data: resource)
179
+ end
180
+
181
+ it 'returns the result of making a PATCH request to the endpoint' do
182
+ expect(@result).to be_a(Hash)
183
+ expect(@result['data']).to eq(result)
184
+ end
185
+ end
186
+
187
+ describe '#delete' do
188
+ subject { base.people.v1.people[1] }
189
+
190
+ before do
191
+ stub_request(:delete, 'https://api.planningcenteronline.com/people/v1/people/1')
192
+ .to_return(status: 204, body: '')
193
+ @result = subject.delete
194
+ end
195
+
196
+ it 'returns true' do
197
+ expect(@result).to eq(true)
198
+ end
199
+ end
200
+ end
@@ -0,0 +1,18 @@
1
+ require 'webmock/rspec'
2
+
3
+ require_relative '../lib/pco_api'
4
+
5
+ RSpec.configure do |config|
6
+ config.expect_with :rspec do |expectations|
7
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
8
+ end
9
+
10
+ config.mock_with :rspec do |mocks|
11
+ mocks.verify_partial_doubles = true
12
+ end
13
+
14
+ config.color = true
15
+ config.order = 'random'
16
+ config.filter_run focus: true
17
+ config.run_all_when_everything_filtered = true
18
+ end
metadata ADDED
@@ -0,0 +1,136 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pco_api
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Planning Center Online
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-06-03 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: faraday
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.9.1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.9.1
27
+ - !ruby/object:Gem::Dependency
28
+ name: faraday_middleware
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.9.1
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.9.1
41
+ - !ruby/object:Gem::Dependency
42
+ name: excon
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: 0.30.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.30.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 3.2.0
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 3.2.0
69
+ - !ruby/object:Gem::Dependency
70
+ name: webmock
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 1.21.0
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 1.21.0
83
+ - !ruby/object:Gem::Dependency
84
+ name: pry
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 0.10.1
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 0.10.1
97
+ description: Ruby wrapper for the RESTful PCO API
98
+ email:
99
+ executables: []
100
+ extensions: []
101
+ extra_rdoc_files: []
102
+ files:
103
+ - README.md
104
+ - lib/pco/api.rb
105
+ - lib/pco/api/endpoint.rb
106
+ - lib/pco/api/errors.rb
107
+ - lib/pco/api/version.rb
108
+ - lib/pco_api.rb
109
+ - spec/pco/api/endpoint_spec.rb
110
+ - spec/spec_helper.rb
111
+ homepage: https://github.com/planningcenter/pco_api_ruby
112
+ licenses: []
113
+ metadata: {}
114
+ post_install_message:
115
+ rdoc_options: []
116
+ require_paths:
117
+ - lib
118
+ required_ruby_version: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - ">="
121
+ - !ruby/object:Gem::Version
122
+ version: '0'
123
+ required_rubygems_version: !ruby/object:Gem::Requirement
124
+ requirements:
125
+ - - ">="
126
+ - !ruby/object:Gem::Version
127
+ version: '0'
128
+ requirements: []
129
+ rubyforge_project:
130
+ rubygems_version: 2.4.6
131
+ signing_key:
132
+ specification_version: 4
133
+ summary: Ruby wrapper for the RESTful PCO API
134
+ test_files:
135
+ - spec/pco/api/endpoint_spec.rb
136
+ - spec/spec_helper.rb