clever-ruby 0.0.1

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.
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ .rvmrc
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
19
+ *~
@@ -0,0 +1,10 @@
1
+ rvm:
2
+ - 1.8.7
3
+ - 1.9.3
4
+ - 1.9.2
5
+
6
+ script: "bundle exec rake"
7
+
8
+ branches:
9
+ only:
10
+ - master
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in clever-ruby.gemspec
4
+ gemspec
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Rafael Garcia
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,35 @@
1
+ # Clever::Ruby
2
+
3
+ [![Build Status](https://travis-ci.org/Clever/clever-ruby.png)](https://travis-ci.org/Clever/clever-ruby)
4
+
5
+ Ruby bindings to the Clever API.
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ gem 'clever-ruby'
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install clever-ruby
20
+
21
+ ## Usage
22
+
23
+ Configure clever-ruby to use your Clever API key:
24
+
25
+ Clever.configure do |config|
26
+ config.api_key = 'YOUR-API-KEY'
27
+ end
28
+
29
+ ## Contributing
30
+
31
+ 1. Fork it
32
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
33
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
34
+ 4. Push to the branch (`git push origin my-new-feature`)
35
+ 5. Create new Pull Request
@@ -0,0 +1,21 @@
1
+ # encoding: utf-8
2
+
3
+ require 'bundler/gem_tasks'
4
+
5
+ require 'rake/testtask'
6
+ Rake::TestTask.new(:test) do |test|
7
+ test.libs << 'test'
8
+ test.pattern = 'test/**/*_test.rb'
9
+ test.verbose = true
10
+ end
11
+
12
+ task :default => :test
13
+
14
+ require 'rdoc/task'
15
+ require 'clever-ruby/version'
16
+ Rake::RDocTask.new do |rdoc|
17
+ rdoc.rdoc_dir = 'rdoc'
18
+ rdoc.title = "clever-ruby #{Clever::VERSION}"
19
+ rdoc.rdoc_files.include('README*')
20
+ rdoc.rdoc_files.include('lib/**/*.rb')
21
+ end
@@ -0,0 +1,28 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'clever-ruby/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "clever-ruby"
8
+ gem.version = Clever::VERSION
9
+ gem.authors = ["Rafael Garcia", "Alex Zylman"]
10
+ gem.email = ["ruby@getclever.com"]
11
+ gem.description = %q{Ruby bindings to the Clever API.}
12
+ gem.summary = %q{Ruby bindings to the Clever API.}
13
+ gem.homepage = "http://github.com/Clever/clever-ruby"
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+
20
+ gem.add_runtime_dependency 'multi_json', '~> 1.5.0'
21
+ gem.add_runtime_dependency 'rest-client', '~> 1.6.7'
22
+ gem.add_development_dependency 'rake'
23
+ gem.add_development_dependency 'minitest', '~> 4.4.0'
24
+ gem.add_development_dependency 'shoulda', '~> 3.3.2'
25
+ gem.add_development_dependency 'rdoc', '~> 3.12'
26
+ gem.add_development_dependency 'vcr', '~> 2.4.0'
27
+ gem.add_development_dependency 'webmock', '~> 1.9.0'
28
+ end
@@ -0,0 +1,161 @@
1
+ require 'rest_client'
2
+ require 'multi_json'
3
+
4
+ require 'clever-ruby/version'
5
+
6
+ # API operations
7
+ require 'clever-ruby/api_operations/list'
8
+
9
+ # Helpers
10
+ require 'clever-ruby/util'
11
+ require 'clever-ruby/json'
12
+ require 'clever-ruby/configuration'
13
+
14
+ # Resources
15
+ require 'clever-ruby/clever_object'
16
+ require 'clever-ruby/api_resource'
17
+ require 'clever-ruby/district'
18
+ require 'clever-ruby/school'
19
+ require 'clever-ruby/student'
20
+ require 'clever-ruby/section'
21
+ require 'clever-ruby/teacher'
22
+
23
+ # Errors
24
+ require 'clever-ruby/errors/clever_error'
25
+ require 'clever-ruby/errors/authentication_error'
26
+ require 'clever-ruby/errors/api_error'
27
+
28
+ module Clever
29
+ class << self
30
+ def configure
31
+ yield configuration
32
+ end
33
+
34
+ def api_key
35
+ configuration.api_key
36
+ end
37
+
38
+ def configuration
39
+ @configuration ||= Clever::Configuration.new
40
+ end
41
+
42
+ def api_url(url = '')
43
+ configuration.api_base + url
44
+ end
45
+ end
46
+
47
+ def self.request(method, url, params=nil, headers={})
48
+ raise AuthenticationError.new('No API key provided. (HINT: set your API key using "Clever.configure { |config| config.api_key = <API-KEY> }")') unless Clever.api_key
49
+
50
+ params = Util.objects_to_ids(params)
51
+ url = self.api_url(url)
52
+
53
+ case method.to_s.downcase.to_sym
54
+ when :get, :head, :delete
55
+ # Make params into GET parameters
56
+ if params && params.count
57
+ query_string = Util.flatten_params(params).collect{|p| "#{p[0]}=#{p[1]}"}.join('&')
58
+ url += "?#{query_string}"
59
+ end
60
+ payload = nil
61
+ else
62
+ payload = params
63
+ end
64
+
65
+ opts = {
66
+ :user => Clever.api_key,
67
+ :password => "",
68
+ :method => method,
69
+ :url => url,
70
+ :headers => headers,
71
+ :open_timeout => 30,
72
+ :payload => payload,
73
+ :timeout => 80
74
+ }
75
+
76
+ begin
77
+ response = execute_request(opts)
78
+ rescue SocketError => e
79
+ self.handle_restclient_error(e)
80
+ rescue NoMethodError => e
81
+ # Work around RestClient bug
82
+ if e.message =~ /\WRequestFailed\W/
83
+ e = APIConnectionError.new('Unexpected HTTP response code')
84
+ self.handle_restclient_error(e)
85
+ else
86
+ raise
87
+ end
88
+ rescue RestClient::ExceptionWithResponse => e
89
+ if rcode = e.http_code and rbody = e.http_body
90
+ self.handle_api_error(rcode, rbody)
91
+ else
92
+ self.handle_restclient_error(e)
93
+ end
94
+ rescue RestClient::Exception, Errno::ECONNREFUSED => e
95
+ self.handle_restclient_error(e)
96
+ end
97
+
98
+ rbody = response.body
99
+ rcode = response.code
100
+ begin
101
+ # Would use :symbolize_names => true, but apparently there is
102
+ # some library out there that makes symbolize_names not work.
103
+ resp = Clever::JSON.load(rbody)
104
+ rescue MultiJson::DecodeError
105
+ raise APIError.new("Invalid response object from API: #{rbody.inspect} (HTTP response code was #{rcode})", rcode, rbody)
106
+ end
107
+
108
+ resp = Util.symbolize_names(resp)
109
+ resp
110
+ end
111
+
112
+ private
113
+
114
+ def self.execute_request(opts)
115
+ RestClient::Request.execute(opts)
116
+ end
117
+
118
+ def self.handle_restclient_error(e)
119
+ case e
120
+ when RestClient::ServerBrokeConnection, RestClient::RequestTimeout
121
+ message = "Could not connect to Clever (#{@@api_base}). Please check your internet connection and try again."
122
+ when SocketError
123
+ message = "Unexpected error communicating when trying to connect to Clever. HINT: You may be seeing this message because your DNS is not working. To check, try running 'host getclever.com' from the command line."
124
+ else
125
+ message = "Unexpected error communicating with Clever."
126
+ end
127
+ message += "\n\n(Network error: #{e.message})"
128
+ raise APIConnectionError.new(message)
129
+ end
130
+
131
+ def self.handle_api_error(rcode, rbody)
132
+ begin
133
+ error_obj = Clever::JSON.load(rbody)
134
+ error_obj = Util.symbolize_names(error_obj)
135
+ error = error_obj[:error] or raise StripeError.new # escape from parsing
136
+ rescue MultiJson::DecodeError, StripeError
137
+ raise APIError.new("Invalid response object from API: #{rbody.inspect} (HTTP response code was #{rcode})", rcode, rbody)
138
+ end
139
+
140
+ case rcode
141
+ when 400, 404 then
142
+ raise invalid_request_error(error, rcode, rbody, error_obj)
143
+ when 401
144
+ raise authentication_error(error, rcode, rbody, error_obj)
145
+ else
146
+ raise api_error(error, rcode, rbody, error_obj)
147
+ end
148
+ end
149
+
150
+ def self.invalid_request_error(error, rcode, rbody, error_obj)
151
+ InvalidRequestError.new(error[:message], error[:param], rcode, rbody, error_obj)
152
+ end
153
+
154
+ def self.authentication_error(error, rcode, rbody, error_obj)
155
+ AuthenticationError.new(error[:message], rcode, rbody, error_obj)
156
+ end
157
+
158
+ def self.api_error(error, rcode, rbody, error_obj)
159
+ APIError.new(error[:message], rcode, rbody, error_obj)
160
+ end
161
+ end
@@ -0,0 +1,16 @@
1
+ module Clever
2
+ module APIOperations
3
+ module List
4
+ module ClassMethods
5
+ def all(filters={})
6
+ response = Clever.request(:get, url, filters)
7
+ Util.convert_to_clever_object(response[:data])
8
+ end
9
+ end
10
+
11
+ def self.included(base)
12
+ base.extend(ClassMethods)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,30 @@
1
+ module Clever
2
+ class APIResource < CleverObject
3
+ def self.url
4
+ if self == APIResource
5
+ raise NotImplementedError.new('APIResource is an abstract class. You should perform actions on its subclasses (School, Student, etc.)')
6
+ end
7
+ shortname = self.name.split('::')[-1]
8
+ "/#{CGI.escape(shortname.downcase)}s"
9
+ end
10
+
11
+ def url
12
+ unless id = self['id']
13
+ raise InvalidRequestError.new("Could not determine which URL to request: #{self.class} instance has invalid ID: #{id.inspect}", 'id')
14
+ end
15
+ "#{self.class.url}/#{CGI.escape(id)}"
16
+ end
17
+
18
+ def refresh
19
+ response = Clever.request(:get, url)
20
+ refresh_from(response[:data])
21
+ self
22
+ end
23
+
24
+ def self.retrieve(id)
25
+ instance = self.new(id)
26
+ instance.refresh
27
+ instance
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,126 @@
1
+ module Clever
2
+ class CleverObject
3
+ include Enumerable
4
+
5
+ @@permanent_attributes = Set.new([])
6
+
7
+ # The default :id method is deprecated and isn't useful to us
8
+ if method_defined?(:id)
9
+ undef :id
10
+ end
11
+
12
+ def initialize(id=nil)
13
+ @values = {}
14
+ @values[:id] = id if id
15
+ end
16
+
17
+ def self.construct_from(values)
18
+ obj = self.new(values[:id])
19
+ obj.refresh_from(values)
20
+ obj
21
+ end
22
+
23
+ def to_s(*args)
24
+ Clever::JSON.dump(@values, :pretty => true)
25
+ end
26
+
27
+ def inspect()
28
+ id_string = (self.respond_to?(:id) && !self.id.nil?) ? " id=#{self.id}" : ""
29
+ "#<#{self.class}:0x#{self.object_id.to_s(16)}#{id_string}> JSON: " + Clever::JSON.dump(@values, :pretty => true)
30
+ end
31
+
32
+ def refresh_from(values, partial=false)
33
+
34
+ removed = partial ? Set.new : Set.new(@values.keys - values.keys)
35
+ added = Set.new(values.keys - @values.keys)
36
+
37
+ instance_eval do
38
+ remove_accessors(removed)
39
+ add_accessors(added)
40
+ end
41
+ removed.each do |k|
42
+ @values.delete(k)
43
+ end
44
+ values.each do |k, v|
45
+ # Stripe apparently allows you to have nested object types (e.g.
46
+ # InvoiceList of Charges). We don't and this was breaking our code
47
+ # @values[k] = Util.convert_to_clever_object(v)
48
+ @values[k] = v
49
+ end
50
+ end
51
+
52
+ def [](k)
53
+ k = k.to_sym if k.kind_of?(String)
54
+ @values[k]
55
+ end
56
+
57
+ def []=(k, v)
58
+ send(:"#{k}=", v)
59
+ end
60
+
61
+ def keys
62
+ @values.keys
63
+ end
64
+
65
+ def values
66
+ @values.values
67
+ end
68
+
69
+ def to_json(*a)
70
+ Clever::JSON.dump(@values)
71
+ end
72
+
73
+ def as_json(*a)
74
+ @values.as_json(*a)
75
+ end
76
+
77
+ def to_hash
78
+ @values
79
+ end
80
+
81
+ def each(&blk)
82
+ @values.each(&blk)
83
+ end
84
+
85
+ def ==( other )
86
+ if other.respond_to?( :values )
87
+ self.values == other.values
88
+ end
89
+ end
90
+
91
+ protected
92
+
93
+ def metaclass
94
+ class << self; self; end
95
+ end
96
+
97
+ def remove_accessors(keys)
98
+ metaclass.instance_eval do
99
+ keys.each do |k|
100
+ next if @@permanent_attributes.include?(k)
101
+ k_eq = :"#{k}="
102
+ remove_method(k) if method_defined?(k)
103
+ remove_method(k_eq) if method_defined?(k_eq)
104
+ end
105
+ end
106
+ end
107
+
108
+ def add_accessors(keys)
109
+ metaclass.instance_eval do
110
+ keys.each do |k|
111
+ next if @@permanent_attributes.include?(k)
112
+ k_eq = :"#{k}="
113
+ define_method(k) { @values[k] }
114
+ define_method(k_eq) do |v|
115
+ @values[k] = v
116
+ end
117
+ end
118
+ end
119
+ end
120
+
121
+ def method_missing(name, *args)
122
+ return @values[name] if @values.has_key?(name)
123
+ super
124
+ end
125
+ end
126
+ end