motion_ocean 0.1.1 → 0.2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: cb14f92dec01163395d43525ad7e66664ff2e796
4
- data.tar.gz: 3ca20c500add45aacc4bb2c358276bca28461d0e
3
+ metadata.gz: 8e8d553b93122e2453a621d617a90831ec6e225d
4
+ data.tar.gz: ca435978b2a0e40383976aa343297413c49d80d4
5
5
  SHA512:
6
- metadata.gz: 0deb674b9e246ba6b9a5584cbf65ff19ceea0577892fb197390837534f27b697a9a9d7396e784447dbc1d04b13cf6fbc906c6360d701e034b7ef41e53d0cd2a6
7
- data.tar.gz: 7be9fba616e5fe10e218ba2330e6e64d75b60f4cd35538a2ffcaa50827b9b16c18cc4e3eb898f71218674d1b40b2814c06788b3bbb68814dc792d88c6d91a1fb
6
+ metadata.gz: aabec4b730b1aa08865c0e844eda3521f0d72781814479ffebde1c0ce0334568bb2e722998489d5a32d63c488d10a6da6783d7d144d0b41d62e73702b8945123
7
+ data.tar.gz: de1257b71353f6f6a278d04a0b9ca68d779857166e133801fd81360878fa49c713d32a8a12baaaff77b695825fc1bbb5bddd68504cc36f437ae44ba90854d384
data/README.md CHANGED
@@ -42,11 +42,13 @@ end
42
42
 
43
43
  ### Usage
44
44
 
45
- Since MotionOcean is based on [AFMotion][afmotion], requests are being made asynchronous. Every MotionOcean method call therefore requires a block, which yields the data (or nil if the request failed).
45
+ The API client is based on NSURLSession and every request is executed asynchronously. Every MotionOcean method call therefore requires a block, which yields a `MotionOcean::API::Response`.
46
46
 
47
47
  MotionOcean pretty much implements the DO API 1:1, so please check [their documentation](https://developers.digitalocean.com/v2/) for the available functions and options.
48
48
 
49
- ### success?
49
+ ### The response object
50
+
51
+ #### success?
50
52
 
51
53
  You can use `success?` to check if a successful HTTP status code was returned:
52
54
 
@@ -56,25 +58,41 @@ client.droplet.create(options) do |result|
56
58
  end
57
59
  ```
58
60
 
59
- ### response
61
+ #### data
62
+
63
+ The response data is assigned to the `data`-property of the response object. This is a `Hash` which
64
+ corresponds with the returned JSON by DigitalOcean.
60
65
 
61
- MotionOcean uses [AFMotion][afmotion]. You can use `response` to get to the response
66
+ ``` ruby
67
+ client.droplet.create(options) do |result|
68
+ result.data # => Hash with the JSON response
69
+ end
70
+ ```
71
+
72
+
73
+ #### response
74
+
75
+ MotionOcean uses NSURLSession. You can use `response` to get to the original response
62
76
  object:
63
77
 
64
78
  ``` ruby
65
79
  client.droplet.create(options) do |result|
66
- result.response # => AFMotion::HTTPResult
80
+ result.response # => NSHTTPURLResponse
67
81
  end
68
82
  ```
69
- [afmotion]: https://github.com/usepropellor/afmotion
83
+ #### error
84
+
85
+ When there is an error you can check this for the specific error. If you think it is a bug in MotionOcean, please open an issue or even better: submit a Pull Request with the fix!
70
86
 
71
87
  ## Aknowledgements
72
88
 
73
89
  This library is an adaptation for RubyMotion of [barge](https://github.com/boats/barge) by [Ørjan Blom](https://github.com/blom).
74
90
 
91
+ The brilliant [MotionInMotion](https://motioninmotion.tv) screencasts helped me with implementing the API client using NSURLSession (specifically [episode #33](https://motioninmotion.tv/screencasts/33)) and iOS development using RubyMotion in general.
92
+
75
93
  ## Contributing
76
94
 
77
- 1. Fork it ( https://github.com/[my-github-username]/motion_ocean/fork )
95
+ 1. Fork it ( https://github.com/datarift/motion_ocean/fork )
78
96
  2. Create your feature branch (`git checkout -b my-new-feature`)
79
97
  3. Commit your changes (`git commit -am 'Add some feature'`)
80
98
  4. Push to the branch (`git push origin my-new-feature`)
data/lib/motion_ocean.rb CHANGED
@@ -1,5 +1,4 @@
1
1
  require 'dbt'
2
- require 'afmotion'
3
2
 
4
3
  unless defined?(Motion::Project::Config)
5
4
  raise "This file must be required within a RubyMotion project Rakefile."
@@ -0,0 +1,5 @@
1
+ module MotionOcean
2
+ module API
3
+
4
+ end
5
+ end
@@ -0,0 +1,30 @@
1
+ module MotionOcean
2
+ module API
3
+ class Authorization
4
+ class << self
5
+ def build(options = {})
6
+ username, password = options.values_at :username, :password
7
+ token = options.fetch(:token, nil)
8
+ token_name = options.fetch(:token_name, "Token")
9
+
10
+ if username && password
11
+ http_basic_header(username, password)
12
+ elsif token
13
+ token_header(token, token_name)
14
+ else
15
+ nil
16
+ end
17
+ end
18
+
19
+ def http_basic_header(username, password)
20
+ token_data = "#{username}:#{password}".dataUsingEncoding(NSUTF8StringEncoding)
21
+ token_header(token_data.base64Encoding, "Basic")
22
+ end
23
+
24
+ def token_header(token, token_name)
25
+ "#{token_name} #{token}"
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,115 @@
1
+ module MotionOcean
2
+ module API
3
+ class Client
4
+ attr_reader :base_url
5
+
6
+ def initialize(base_url, &block)
7
+ base_url += '/' unless base_url.end_with? '/'
8
+ @base_url = NSURL.URLWithString(base_url)
9
+ @headers = {}
10
+
11
+ # yield(self) if block_given?
12
+ if block_given?
13
+ case block.arity
14
+ when 0
15
+ instance_eval(&block)
16
+ when 1
17
+ block.call(self)
18
+ end
19
+ end
20
+ end
21
+
22
+ [:delete, :get, :head, :post, :put].each do |verb|
23
+ define_method verb do |path, options = {}, &block|
24
+ request = create_request(path, options, verb)
25
+ create_task(request, &block).resume
26
+ end
27
+ end
28
+
29
+ def header(name, value)
30
+ @headers[name] = value
31
+ end
32
+
33
+ def authorization(options = {})
34
+ header "Authorization", MotionOcean::API::Authorization.build(options)
35
+ end
36
+
37
+ private
38
+ def config
39
+ NSURLSessionConfiguration.defaultSessionConfiguration
40
+ end
41
+
42
+ def session
43
+ NSURLSession.sessionWithConfiguration(config)
44
+ end
45
+
46
+ def create_request(path, options, method)
47
+ headers = options.fetch(:headers, {})
48
+ body = options.fetch(:body, nil)
49
+ query = options.fetch(:query, nil)
50
+
51
+ url = create_url(path, query)
52
+
53
+ request = NSMutableURLRequest.requestWithURL(url)
54
+
55
+ set_headers(request, @headers.merge(headers))
56
+ set_body(request, body) if body
57
+
58
+ request.setHTTPMethod(method.to_s.upcase)
59
+
60
+ request
61
+ end
62
+
63
+ def create_url(path, query)
64
+ query_string = query.map { |key, value| "#{key}=#{value}" }.join '&' unless query.nil?
65
+
66
+ path.sub!('/', '') if path.start_with? '/'
67
+
68
+ components = NSURLComponents.new
69
+ components.path = path
70
+ components.query = query_string
71
+ components.URLRelativeToURL @base_url
72
+ end
73
+
74
+ def set_headers(request, headers)
75
+ headers.each do |name, value|
76
+ request.addValue value.to_s, forHTTPHeaderField: name.to_s
77
+ end
78
+ end
79
+
80
+ def set_body(request, params)
81
+ data = json_params(params)
82
+ request.setHTTPBody(data)
83
+ end
84
+
85
+ def json_params(params)
86
+ NSJSONSerialization.dataWithJSONObject(params, options: 0, error: nil)
87
+ end
88
+
89
+ def create_task(request, &block)
90
+ if block_given?
91
+ session.dataTaskWithRequest(request, completionHandler: -> (data, response, error) {
92
+ block.call(APIResponse.new(data, response, error))
93
+ })
94
+ else
95
+ session.dataTaskWithRequest(request)
96
+ end
97
+ end
98
+
99
+ class APIResponse
100
+ attr_reader :success, :data, :error, :response
101
+
102
+ def initialize(data, response, error)
103
+ @success = (200...300).include?(response.statusCode) if response
104
+ @data = NSJSONSerialization.JSONObjectWithData(data, options: 0, error: nil)
105
+ @response = response
106
+ @error = error
107
+ end
108
+
109
+ def success?
110
+ @success
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
@@ -25,20 +25,18 @@ module MotionOcean
25
25
  @action = Resource::Action.new(api_client)
26
26
  @domain = Resource::Domain.new(api_client)
27
27
  @droplet = Resource::Droplet.new(api_client)
28
+ @image = Resource::Image.new(api_client)
29
+ @key = Resource::Key.new(api_client)
28
30
  @region = Resource::Region.new(api_client)
29
31
  @size = Resource::Size.new(api_client)
30
32
  end
31
33
 
32
34
  def api_client
33
- @api_client ||= AFMotion::SessionClient.build(DIGITAL_OCEAN_URL) do |client|
34
- client.session_configuration :default
35
-
35
+ @api_client ||= MotionOcean::API::Client.new(DIGITAL_OCEAN_URL) do |client|
36
36
  client.header 'Accept', 'application/json'
37
37
  client.header 'Content-Type', 'application/json'
38
- client.header 'Authorization', "Bearer #{access_token}"
39
38
 
40
- client.request_serializer :json
41
- client.response_serializer :json
39
+ client.authorization token: access_token, token_name: 'Bearer'
42
40
  end
43
41
  end
44
42
  end
@@ -21,13 +21,7 @@ module MotionOcean
21
21
  opts = { per_page: PER_PAGE } if verb == :get
22
22
  options = opts.merge(options)
23
23
 
24
- api_client.public_send(verb, url, options) do |response|
25
- data = response.object.tap do |r|
26
- r.define_singleton_method(:response) { response }
27
- r.define_singleton_method(:success?) { response.success? }
28
- end
29
- block.call(data)
30
- end
24
+ api_client.public_send(verb, url, options, &block)
31
25
  end
32
26
  end
33
27
  end
@@ -1,3 +1,3 @@
1
1
  module MotionOcean
2
- VERSION = "0.1.1"
2
+ VERSION = "0.2.1"
3
3
  end
@@ -0,0 +1,27 @@
1
+ describe "MotionOcean::API::Authorization" do
2
+ before do
3
+ @token = 'some_token'
4
+ @token_name = 'FooBar'
5
+ @username = 'foo'
6
+ @password = 'bar'
7
+ @auth_data = 'Zm9vOmJhcg=='
8
+ end
9
+
10
+ it "creates a token-based auth header" do
11
+ header_value = MotionOcean::API::Authorization.build(token: @token)
12
+
13
+ header_value.should == "Token #{@token}"
14
+ end
15
+
16
+ it "uses the specified token_name" do
17
+ header_value = MotionOcean::API::Authorization.build(token: @token, token_name: @token_name)
18
+
19
+ header_value.should == "#{@token_name} #{@token}"
20
+ end
21
+
22
+ it "accepts HTTP Basic parameters" do
23
+ header_value = MotionOcean::API::Authorization.build(username: @username, password: @password)
24
+
25
+ header_value.should == "Basic #{@auth_data}"
26
+ end
27
+ end
@@ -0,0 +1,53 @@
1
+ describe "MotionOcean::API::Client" do
2
+ before do
3
+ @base_url = 'http://httpbin.org/'
4
+ @base_url_no_slash = 'http://httpbin.org'
5
+ @token = 'some_test_token'
6
+ end
7
+
8
+ it "accepts a base URL and a block" do
9
+ client = MotionOcean::API::Client.new(@base_url) do
10
+
11
+ end
12
+
13
+ client.base_url.should != nil
14
+ client.base_url.absoluteString.should == @base_url
15
+ end
16
+
17
+ it "adds a slash to the base_url if it is not present" do
18
+ client = MotionOcean::API::Client.new(@base_url_no_slash) do
19
+
20
+ end
21
+
22
+ client.base_url.should != nil
23
+ client.base_url.absoluteString.should == @base_url
24
+ end
25
+
26
+ it "accepts headers" do
27
+ client = MotionOcean::API::Client.new(@base_url_no_slash) do
28
+ header 'Accept', 'application/json'
29
+ end
30
+
31
+ headers = client.instance_variable_get(:@headers)
32
+
33
+ headers.should != nil
34
+ headers.empty?.should == false
35
+ headers.should.be.kind_of Hash
36
+ headers.include?('Accept').should == true
37
+ headers['Accept'].should == 'application/json'
38
+ end
39
+
40
+ it "sets authorization headers" do
41
+ client = MotionOcean::API::Client.new(@base_url_no_slash) do
42
+ authorization token: 'some_test_token'
43
+ end
44
+
45
+ headers = client.instance_variable_get(:@headers)
46
+
47
+ headers.should != nil
48
+ headers.empty?.should == false
49
+
50
+ headers.include?('Authorization').should == true
51
+ headers['Authorization'].should == "Token #{@token}"
52
+ end
53
+ end
data/spec/client_spec.rb CHANGED
@@ -1,9 +1,16 @@
1
1
  describe "MotionOcean::Client" do
2
- describe "#access_token" do
3
- before do
4
- @token ||= "some_token"
5
- end
2
+ before do
3
+ @token ||= "some_token"
4
+ end
6
5
 
6
+ describe "class" do
7
+ it "is the right class" do
8
+ client = MotionOcean::Client.new(access_token: @token)
9
+ client.should.be.kind_of(MotionOcean::Client)
10
+ end
11
+ end
12
+
13
+ describe "#access_token" do
7
14
  it "can be set via a hash" do
8
15
  client = MotionOcean::Client.new(access_token: @token)
9
16
  expect(client.access_token).to eq(@token)
@@ -24,4 +31,22 @@ describe "MotionOcean::Client" do
24
31
  end
25
32
 
26
33
  end
34
+
35
+ describe "operation" do
36
+ it "can call the API" do
37
+ client = MotionOcean::Client.new(access_token: ENV['DO_API_TOKEN'])
38
+ client.should != nil
39
+
40
+ client.domain.all do |response|
41
+ expect(response).not_to be_nil
42
+ expect(response.success?).to be_true
43
+ expect(response.error).to be_nil
44
+ response.data.should.be.kind_of(Hash)
45
+
46
+ resume
47
+ end
48
+
49
+ wait_max(10) {}
50
+ end
51
+ end
27
52
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: motion_ocean
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Erwin Boskma
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-07-16 00:00:00.000000000 Z
11
+ date: 2014-08-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dbt
@@ -30,26 +30,6 @@ dependencies:
30
30
  - - ">="
31
31
  - !ruby/object:Gem::Version
32
32
  version: 1.1.0
33
- - !ruby/object:Gem::Dependency
34
- name: afmotion
35
- requirement: !ruby/object:Gem::Requirement
36
- requirements:
37
- - - "~>"
38
- - !ruby/object:Gem::Version
39
- version: '2.3'
40
- - - ">="
41
- - !ruby/object:Gem::Version
42
- version: 2.3.0
43
- type: :runtime
44
- prerelease: false
45
- version_requirements: !ruby/object:Gem::Requirement
46
- requirements:
47
- - - "~>"
48
- - !ruby/object:Gem::Version
49
- version: '2.3'
50
- - - ">="
51
- - !ruby/object:Gem::Version
52
- version: 2.3.0
53
33
  - !ruby/object:Gem::Dependency
54
34
  name: rake
55
35
  requirement: !ruby/object:Gem::Requirement
@@ -120,6 +100,9 @@ extra_rdoc_files: []
120
100
  files:
121
101
  - README.md
122
102
  - lib/motion_ocean.rb
103
+ - lib/motion_ocean/api.rb
104
+ - lib/motion_ocean/api/authorization.rb
105
+ - lib/motion_ocean/api/client.rb
123
106
  - lib/motion_ocean/client.rb
124
107
  - lib/motion_ocean/resource/action.rb
125
108
  - lib/motion_ocean/resource/base.rb
@@ -130,6 +113,8 @@ files:
130
113
  - lib/motion_ocean/resource/region.rb
131
114
  - lib/motion_ocean/resource/size.rb
132
115
  - lib/motion_ocean/version.rb
116
+ - spec/api/authorization_spec.rb
117
+ - spec/api/client_spec.rb
133
118
  - spec/client_spec.rb
134
119
  homepage: https://github.com/datarift/motion_ocean
135
120
  licenses:
@@ -156,4 +141,6 @@ signing_key:
156
141
  specification_version: 4
157
142
  summary: Digital Ocean API implementation for RubyMotion
158
143
  test_files:
144
+ - spec/api/authorization_spec.rb
145
+ - spec/api/client_spec.rb
159
146
  - spec/client_spec.rb