plangrade-ruby 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,61 @@
1
+ module Plangrade
2
+ module Error
3
+
4
+ class << self
5
+ def from_status(status=nil)
6
+ case status
7
+ when 400
8
+ BadRequest
9
+ when 401
10
+ Unauthorized
11
+ when 403
12
+ Forbidden
13
+ when 404
14
+ NotFound
15
+ when 406
16
+ NotAcceptable
17
+ when 429
18
+ RateLimitExceeded
19
+ when 500
20
+ InternalServerError
21
+ when 502
22
+ BadGateway
23
+ when 503
24
+ ServiceUnavailable
25
+ else
26
+ ApiError
27
+ end
28
+ end
29
+ end
30
+
31
+ # Raised when Plangrade returns unknown HTTP status code
32
+ class ApiError < StandardError; end
33
+
34
+ # Raised when Plangrade returns the HTTP status code 400
35
+ class BadRequest < ApiError; end
36
+
37
+ # Raised when Plangrade returns the HTTP status code 401
38
+ class Unauthorized < ApiError; end
39
+
40
+ # Raised when Plangrade returns the HTTP status code 403
41
+ class Forbidden < ApiError; end
42
+
43
+ # Raised when Plangrade returns the HTTP status code 404
44
+ class NotFound < ApiError; end
45
+
46
+ # Raised when Plangrade returns the HTTP status code 406
47
+ class NotAcceptable < ApiError; end
48
+
49
+ # Raised when Plangrade returns the HTTP status code 429
50
+ class RateLimitExceeded < ApiError; end
51
+
52
+ # Raised when Plangrade returns the HTTP status code 500
53
+ class InternalServerError < ApiError; end
54
+
55
+ # Raised when Plangrade returns the HTTP status code 502
56
+ class BadGateway < ApiError; end
57
+
58
+ # Raised when Plangrade returns the HTTP status code 503
59
+ class ServiceUnavailable < ApiError; end
60
+ end
61
+ end
@@ -0,0 +1,86 @@
1
+ require 'restclient'
2
+ require 'multi_json'
3
+ require 'addressable/uri'
4
+
5
+ module Plangrade
6
+ class HttpAdapter
7
+
8
+ def self.log=(output)
9
+ RestClient.log = output
10
+ end
11
+
12
+ attr_reader :site_url, :connection_options
13
+
14
+ def initialize(site_url, opts={})
15
+ unless site_url =~ /^https?/
16
+ raise ArgumentError, "site_url must include either http or https scheme"
17
+ end
18
+ @site_url = site_url
19
+ @connection_options = opts
20
+ end
21
+
22
+ # set the url to be used for creating an http connection
23
+ # @param url [string]
24
+ def site_url=(url)
25
+ @site_url = url
26
+ @host = nil
27
+ @scheme = nil
28
+ end
29
+
30
+ def host
31
+ @host ||= parsed_url.host
32
+ end
33
+
34
+ def scheme
35
+ @scheme ||= parsed_url.scheme
36
+ end
37
+
38
+ def absolute_url(path='')
39
+ "#{@site_url}#{path}"
40
+ end
41
+
42
+ def connection_options=(opts)
43
+ raise ArgumentError, 'expected Hash' unless opts.is_a?(Hash)
44
+ @connection_options = opts
45
+ end
46
+
47
+ def send_request(method, path, opts={})
48
+ begin
49
+ params = opts.fetch(:params, {})
50
+
51
+ req_opts = self.connection_options.merge({
52
+ :method => method,
53
+ :headers => opts.fetch(:headers, {})
54
+ })
55
+
56
+ case method
57
+ when :get, :delete
58
+ query = Addressable::URI.form_encode(params)
59
+ normalized_path = query.empty? ? path : [path, query].join("?")
60
+ req_opts[:url] = absolute_url(normalized_path)
61
+ when :post, :put
62
+ req_opts[:payload] = params
63
+ req_opts[:url] = absolute_url(path)
64
+ else
65
+ raise "Unsupported HTTP method, #{method}"
66
+ end
67
+
68
+ resp = RestClient::Request.execute(req_opts)
69
+
70
+ result = Plangrade::ApiResponse.new(resp.headers, resp.body, resp.code)
71
+ rescue => e
72
+ if e.is_a?(RestClient::ExceptionWithResponse)
73
+ e.response
74
+ else
75
+ raise e
76
+ end
77
+ end
78
+ end
79
+
80
+ private
81
+ def parsed_url
82
+ Addressable::URI.parse(@site_url)
83
+ end
84
+
85
+ end
86
+ end
@@ -0,0 +1,79 @@
1
+ require 'oauth2-client'
2
+
3
+ module Plangrade
4
+ class OAuth2Client < OAuth2Client::Client
5
+
6
+ SITE_URL = 'https://plangrade.com'
7
+ TOKEN_PATH = '/oauth/token'
8
+ AUTHORIZE_PATH = '/oauth/authorize'
9
+
10
+ def initialize(client_id, client_secret, opts={})
11
+ site_url = opts.delete(:site_url) || SITE_URL
12
+ opts[:token_path] ||= TOKEN_PATH
13
+ opts[:authorize_path] ||= AUTHORIZE_PATH
14
+ super(site_url, client_id, client_secret, opts)
15
+ yield self if block_given?
16
+ self
17
+ end
18
+
19
+ # Generates the Plangrade URL that the user will be redirected to in order to
20
+ # authorize your application
21
+ #
22
+ # @see http://docs.plangrade.com/#request-authorization
23
+ #
24
+ # @opts [Hash] additional parameters to be include in URL eg. scope, state, etc
25
+ #
26
+ # >> client = Plangrade::OAuth2Client.new('ETSIGVSxmgZitijWZr0G6w', '4bJZY38TCBB9q8IpkeualA2lZsPhOSclkkSKw3RXuE')
27
+ # >> client.webclient_authorization_url({
28
+ # :redirect_uri => 'http://localhost:3000/auth/plangrade/callback',
29
+ # })
30
+ # >> https://plangrade.com/oauth/authorize/?client_id={client_id}&
31
+ # redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fauth%2F%2Fplangrade%2Fcallback&response_type=token
32
+ #
33
+ def webclient_authorization_url(opts={})
34
+ implicit.token_url(opts)
35
+ end
36
+
37
+ # Generates the Plangrade URL that the user will be redirected to in order to
38
+ # authorize your application
39
+ #
40
+ # @see http://docs.plangrade.com/#request-authorization
41
+ #
42
+ # @opts [Hash] additional parameters to be include in URL eg. scope, state, etc
43
+ #
44
+ # >> client = Plangrade::OAuth2Client.new('ETSIGVSxmgZitijWZr0G6w', '4bJZY38TCBB9q8IpkeualA2lZsPhOSclkkSKw3RXuE')
45
+ # >> client.webserver_authorization_url({
46
+ # :redirect_uri => 'http://localhost:3000/auth/plangrade/callback',
47
+ # })
48
+ # >> https://plangrade.com/oauth/authorize/?client_id={client_id}&
49
+ # redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fauth%2Fplangrade%2Fcallback&response_type=code
50
+ #
51
+ def webserver_authorization_url(opts={})
52
+ opts[:scope] = normalize_scope(opts[:scope]) if opts[:scope]
53
+ authorization_code.authorization_url(opts)
54
+ end
55
+
56
+ # Makes a request to Plangrade server that will swap your authorization code for an access
57
+ # token
58
+ #
59
+ # @see http://docs.plangrade.com/#finish-authorization
60
+ #
61
+ # @opts [Hash] may include redirect uri and other query parameters
62
+ #
63
+ # >> client = PlangradeClient.new(config)
64
+ # >> client.access_token_from_authorization_code('G3Y6jU3a', {
65
+ # :redirect_uri => 'http://localhost:3000/auth/plangrade/callback',
66
+ # })
67
+ #
68
+ # POST /oauth2/access_token HTTP/1.1
69
+ # Host: www.plangrade.com
70
+ # Content-Type: application/x-www-form-urlencoded
71
+
72
+ # client_id={client_id}&code=G3Y6jU3a&grant_type=authorization_code&
73
+ # redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fauth%2Fplangrade%2Fcallback&client_secret={client_secret}
74
+ def access_token_from_authorization_code(code, opts={})
75
+ opts[:authenticate] ||= :body
76
+ authorization_code.get_token(code, opts)
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,5 @@
1
+ require 'plangrade/resources/identity_map'
2
+ require 'plangrade/resources/base'
3
+ require 'plangrade/resources/user'
4
+ require 'plangrade/resources/company'
5
+ require 'plangrade/resources/participant'
@@ -0,0 +1,196 @@
1
+ module Plangrade
2
+ module Resources
3
+ class Base
4
+ class << self
5
+ include ApiHandler
6
+
7
+ # Returns the non-qualified class name
8
+ # @!scope class
9
+ def base_name
10
+ @base_name ||= begin
11
+ word = "#{name.split(/::/).last}"
12
+ word.gsub!(/::/, '/')
13
+ word.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
14
+ word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
15
+ word.tr!("-", "_")
16
+ word.downcase!
17
+ word
18
+ end
19
+ end
20
+
21
+ # Fetches JSON reprsentation for object model with provided `id`
22
+ # and returns a model instance with attributes
23
+ # @return [Yammer::Base]
24
+ # @param id [Integer]
25
+ # @!scope class
26
+ def get(id)
27
+ attrs = fetch(id)
28
+ attrs ? new(attrs) : nil
29
+ end
30
+
31
+
32
+ # @!scope class
33
+ def fetch(id)
34
+ return unless identity_map
35
+ attributes = identity_map.get("#{base_name}_#{id}")
36
+ unless attributes
37
+ result = api_handler.send("get_#{base_name}", id)
38
+ attributes = result.empty? ? nil : result.body
39
+ unless attributes.empty?
40
+ identity_map.put("#{base_name}_#{id}", attributes)
41
+ end
42
+ end
43
+ attributes
44
+ end
45
+
46
+ # @!scope class
47
+ def identity_map
48
+ @identity_map ||= Plangrade::Resources::IdentityMap.new
49
+ end
50
+
51
+ # Returns a hash of all attributes that are meant to trigger an HTTP request
52
+ # @!scope class
53
+ def model_attributes
54
+ @model_attributes ||= {}
55
+ end
56
+
57
+ protected
58
+
59
+ def attr_accessor_deffered(*symbols)
60
+ symbols.each do |key|
61
+ # track attributes that should trigger a fetch
62
+ model_attributes[key] = false
63
+
64
+ # getter
65
+ define_method(key.to_s) do
66
+ load_deferred_attribute!(key)
67
+ instance_variable_get("@#{key}")
68
+ end
69
+
70
+ # setter
71
+ define_method("#{key}=") do |value|
72
+ load_deferred_attribute!(key)
73
+ if persisted? && loaded?
74
+ @modified_attributes[key] = value
75
+ else
76
+ @attrs[key] = value
77
+ end
78
+ instance_variable_set("@#{key}", value)
79
+ end
80
+ end
81
+ end
82
+ end
83
+
84
+ attr_reader :id, :attrs
85
+
86
+ def initialize(props={})
87
+ @klass = self.class
88
+ @modified_attributes = {}
89
+ @new_record = true
90
+ @loaded = false
91
+ @attrs = props
92
+ self.id = @attrs.delete(:id)
93
+ self.update(@attrs)
94
+
95
+ yield self if block_given?
96
+ end
97
+
98
+ def api_handler
99
+ @klass.api_handler
100
+ end
101
+
102
+ def base_name
103
+ @klass.base_name
104
+ end
105
+
106
+ def new_record?
107
+ @new_record
108
+ end
109
+
110
+ def persisted?
111
+ !new_record?
112
+ end
113
+
114
+ def changes
115
+ @modified_attributes
116
+ end
117
+
118
+ def modified?
119
+ !changes.empty?
120
+ end
121
+
122
+ def loaded?
123
+ @loaded
124
+ end
125
+
126
+ def load!
127
+ @attrs = @klass.fetch(@id)
128
+ @loaded = true
129
+ update(@attrs)
130
+ self
131
+ end
132
+
133
+ def reload!
134
+ reset!
135
+ load!
136
+ end
137
+
138
+ def save
139
+ return self if ((persisted? && @modified_attributes.empty?) || @attrs.empty?)
140
+
141
+ result = if new_record?
142
+ api_handler.send("create_#{base_name}", @attrs)
143
+ else
144
+ api_handler.send("update_#{base_name}", @id, @modified_attributes)
145
+ end
146
+ @modified_attributes = {}
147
+ self
148
+ end
149
+
150
+ def delete!
151
+ return if new_record?
152
+ result = api_handler.send("delete_#{base_name}", @id)
153
+ result.success?
154
+ end
155
+
156
+ private
157
+
158
+ def id=(model_id)
159
+ return if model_id.nil?
160
+ @id = model_id.to_i
161
+ @new_record = false
162
+ end
163
+
164
+ # clear the entire class
165
+ def reset!
166
+ @modified_attributes = {}
167
+ @attrs = {}
168
+ @new_record = true
169
+ @loaded = false
170
+ end
171
+
172
+ protected
173
+ # loads model
174
+ def load_deferred_attribute!(key)
175
+ if @attrs.empty? && persisted? && !loaded?
176
+ load!
177
+ if !@attrs.has_key?(key)
178
+ raise "The key: #{key} appears not to be supported for model: #{self.base_name} \n #{@attrs.keys.inspect}"
179
+ end
180
+ end
181
+ end
182
+
183
+ # set all fetchable attributes
184
+ def update(attrs={})
185
+ attrs.each do |key, value|
186
+ send("#{key}=", value) if self.respond_to?("#{key}=")
187
+ end
188
+ if persisted? && !loaded?
189
+ @loaded = @klass.model_attributes.keys.inject(true) do |result, key|
190
+ result && @attrs.has_key?(key)
191
+ end
192
+ end
193
+ end
194
+ end
195
+ end
196
+ end
@@ -0,0 +1,19 @@
1
+ module Plangrade
2
+ module Resources
3
+ class Company < Plangrade::Resources::Base
4
+
5
+ def self.create(ein, name)
6
+ result = api_handler.create_company(:ein => ein, :name => name)
7
+ return nil unless result.created?
8
+ id = result.headers[:location].split('/').last.to_i
9
+ new(:id => id)
10
+ end
11
+
12
+ attr_accessor_deffered :id, :name, :ein, :grade
13
+
14
+ def update!(params)
15
+ api_handler.update_company(@id, params)
16
+ end
17
+ end
18
+ end
19
+ end