clever-ruby 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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