pco_api 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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