asana 0.0.6 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +9 -9
- data/.codeclimate.yml +4 -0
- data/.gitignore +12 -20
- data/.rspec +4 -0
- data/.rubocop.yml +18 -0
- data/.travis.yml +12 -0
- data/.yardopts +5 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +17 -0
- data/Guardfile +85 -4
- data/LICENSE.txt +21 -0
- data/README.md +264 -135
- data/Rakefile +62 -7
- data/asana.gemspec +27 -21
- data/examples/Gemfile +6 -0
- data/examples/Gemfile.lock +56 -0
- data/examples/api_token.rb +21 -0
- data/examples/cli_app.rb +25 -0
- data/examples/events.rb +38 -0
- data/examples/omniauth_integration.rb +54 -0
- data/lib/asana.rb +8 -11
- data/lib/asana/authentication.rb +8 -0
- data/lib/asana/authentication/oauth2.rb +42 -0
- data/lib/asana/authentication/oauth2/access_token_authentication.rb +51 -0
- data/lib/asana/authentication/oauth2/bearer_token_authentication.rb +32 -0
- data/lib/asana/authentication/oauth2/client.rb +50 -0
- data/lib/asana/authentication/token_authentication.rb +20 -0
- data/lib/asana/client.rb +124 -0
- data/lib/asana/client/configuration.rb +165 -0
- data/lib/asana/errors.rb +90 -0
- data/lib/asana/http_client.rb +155 -0
- data/lib/asana/http_client/environment_info.rb +53 -0
- data/lib/asana/http_client/error_handling.rb +103 -0
- data/lib/asana/http_client/response.rb +32 -0
- data/lib/asana/resources.rb +11 -0
- data/lib/asana/resources/attachment.rb +44 -0
- data/lib/asana/resources/attachment_uploading.rb +33 -0
- data/lib/asana/resources/collection.rb +68 -0
- data/lib/asana/resources/event.rb +49 -0
- data/lib/asana/resources/event_subscription.rb +12 -0
- data/lib/asana/resources/events.rb +101 -0
- data/lib/asana/resources/project.rb +145 -19
- data/lib/asana/resources/registry.rb +62 -0
- data/lib/asana/resources/resource.rb +103 -0
- data/lib/asana/resources/response_helper.rb +14 -0
- data/lib/asana/resources/story.rb +58 -7
- data/lib/asana/resources/tag.rb +111 -19
- data/lib/asana/resources/task.rb +284 -57
- data/lib/asana/resources/team.rb +55 -0
- data/lib/asana/resources/user.rb +65 -10
- data/lib/asana/resources/workspace.rb +79 -34
- data/lib/asana/ruby2_0_0_compatibility.rb +3 -0
- data/lib/asana/version.rb +3 -1
- data/lib/templates/index.js +8 -0
- data/lib/templates/resource.ejs +225 -0
- data/package.json +7 -0
- metadata +91 -51
- data/LICENSE +0 -22
- data/lib/asana/config.rb +0 -23
- data/lib/asana/resource.rb +0 -52
- data/spec/asana/resources/project_spec.rb +0 -63
- data/spec/asana/resources/story_spec.rb +0 -39
- data/spec/asana/resources/tag_spec.rb +0 -63
- data/spec/asana/resources/task_spec.rb +0 -95
- data/spec/asana/resources/user_spec.rb +0 -64
- data/spec/asana/resources/workspace_spec.rb +0 -108
- data/spec/spec_helper.rb +0 -9
@@ -0,0 +1,155 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
require 'faraday_middleware'
|
3
|
+
require 'faraday_middleware/multi_json'
|
4
|
+
|
5
|
+
require_relative 'http_client/error_handling'
|
6
|
+
require_relative 'http_client/environment_info'
|
7
|
+
require_relative 'http_client/response'
|
8
|
+
|
9
|
+
module Asana
|
10
|
+
# Internal: Wrapper over Faraday that abstracts authentication, request
|
11
|
+
# parsing and common options.
|
12
|
+
class HttpClient
|
13
|
+
# Internal: The API base URI.
|
14
|
+
BASE_URI = 'https://app.asana.com/api/1.0'
|
15
|
+
|
16
|
+
# Public: Initializes an HttpClient to make requests to the Asana API.
|
17
|
+
#
|
18
|
+
# authentication - [Asana::Authentication] An authentication strategy.
|
19
|
+
# adapter - [Symbol, Proc] A Faraday adapter, eiter a Symbol for
|
20
|
+
# registered adapters or a Proc taking a builder for a
|
21
|
+
# custom one. Defaults to Faraday.default_adapter.
|
22
|
+
# user_agent - [String] The user agent. Defaults to "ruby-asana vX.Y.Z".
|
23
|
+
# config - [Proc] An optional block that yields the Faraday builder
|
24
|
+
# object for customization.
|
25
|
+
def initialize(authentication: required('authentication'),
|
26
|
+
adapter: nil,
|
27
|
+
user_agent: nil,
|
28
|
+
debug_mode: false,
|
29
|
+
&config)
|
30
|
+
@authentication = authentication
|
31
|
+
@adapter = adapter || Faraday.default_adapter
|
32
|
+
@environment_info = EnvironmentInfo.new(user_agent)
|
33
|
+
@debug_mode = debug_mode
|
34
|
+
@config = config
|
35
|
+
end
|
36
|
+
|
37
|
+
# Public: Performs a GET request against the API.
|
38
|
+
#
|
39
|
+
# resource_uri - [String] the resource URI relative to the base Asana API
|
40
|
+
# URL, e.g "/users/me".
|
41
|
+
# params - [Hash] the request parameters
|
42
|
+
# options - [Hash] the request I/O options
|
43
|
+
#
|
44
|
+
# Returns an [Asana::HttpClient::Response] if everything went well.
|
45
|
+
# Raises [Asana::Errors::APIError] if anything went wrong.
|
46
|
+
def get(resource_uri, params: {}, options: {})
|
47
|
+
opts = options.reduce({}) do |acc, (k, v)|
|
48
|
+
acc.tap do |hash|
|
49
|
+
hash[:"opt_#{k}"] = v.is_a?(Array) ? v.join(',') : v
|
50
|
+
end
|
51
|
+
end
|
52
|
+
perform_request(:get, resource_uri, params.merge(opts))
|
53
|
+
end
|
54
|
+
|
55
|
+
# Public: Performs a PUT request against the API.
|
56
|
+
#
|
57
|
+
# resource_uri - [String] the resource URI relative to the base Asana API
|
58
|
+
# URL, e.g "/users/me".
|
59
|
+
# body - [Hash] the body to PUT.
|
60
|
+
# options - [Hash] the request I/O options
|
61
|
+
#
|
62
|
+
# Returns an [Asana::HttpClient::Response] if everything went well.
|
63
|
+
# Raises [Asana::Errors::APIError] if anything went wrong.
|
64
|
+
def put(resource_uri, body: {}, options: {})
|
65
|
+
params = { data: body }.merge(options.empty? ? {} : { options: options })
|
66
|
+
perform_request(:put, resource_uri, params)
|
67
|
+
end
|
68
|
+
|
69
|
+
# Public: Performs a POST request against the API.
|
70
|
+
#
|
71
|
+
# resource_uri - [String] the resource URI relative to the base Asana API
|
72
|
+
# URL, e.g "/tags".
|
73
|
+
# body - [Hash] the body to POST.
|
74
|
+
# upload - [Faraday::UploadIO] an upload object to post as multipart.
|
75
|
+
# Defaults to nil.
|
76
|
+
# options - [Hash] the request I/O options
|
77
|
+
#
|
78
|
+
# Returns an [Asana::HttpClient::Response] if everything went well.
|
79
|
+
# Raises [Asana::Errors::APIError] if anything went wrong.
|
80
|
+
def post(resource_uri, body: {}, upload: nil, options: {})
|
81
|
+
params = { data: body }.merge(options.empty? ? {} : { options: options })
|
82
|
+
if upload
|
83
|
+
perform_request(:post, resource_uri, params.merge(file: upload)) do |c|
|
84
|
+
c.request :multipart
|
85
|
+
end
|
86
|
+
else
|
87
|
+
perform_request(:post, resource_uri, params)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# Public: Performs a DELETE request against the API.
|
92
|
+
#
|
93
|
+
# resource_uri - [String] the resource URI relative to the base Asana API
|
94
|
+
# URL, e.g "/tags".
|
95
|
+
#
|
96
|
+
# Returns an [Asana::HttpClient::Response] if everything went well.
|
97
|
+
# Raises [Asana::Errors::APIError] if anything went wrong.
|
98
|
+
def delete(resource_uri)
|
99
|
+
perform_request(:delete, resource_uri)
|
100
|
+
end
|
101
|
+
|
102
|
+
private
|
103
|
+
|
104
|
+
def connection(&request_config)
|
105
|
+
Faraday.new do |builder|
|
106
|
+
@authentication.configure(builder)
|
107
|
+
@environment_info.configure(builder)
|
108
|
+
request_config.call(builder) if request_config
|
109
|
+
configure_format(builder)
|
110
|
+
add_middleware(builder)
|
111
|
+
@config.call(builder) if @config
|
112
|
+
use_adapter(builder, @adapter)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def perform_request(method, resource_uri, body = {}, &request_config)
|
117
|
+
handling_errors do
|
118
|
+
url = BASE_URI + resource_uri
|
119
|
+
log_request(method, url, body) if @debug_mode
|
120
|
+
Response.new(connection(&request_config).public_send(method, url, body))
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def configure_format(builder)
|
125
|
+
builder.request :multi_json
|
126
|
+
builder.response :multi_json
|
127
|
+
end
|
128
|
+
|
129
|
+
def add_middleware(builder)
|
130
|
+
builder.use Faraday::Response::RaiseError
|
131
|
+
builder.use FaradayMiddleware::FollowRedirects
|
132
|
+
end
|
133
|
+
|
134
|
+
def use_adapter(builder, adapter)
|
135
|
+
case adapter
|
136
|
+
when Symbol
|
137
|
+
builder.adapter(adapter)
|
138
|
+
when Proc
|
139
|
+
adapter.call(builder)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def handling_errors(&request)
|
144
|
+
ErrorHandling.handle(&request)
|
145
|
+
end
|
146
|
+
|
147
|
+
def log_request(method, url, body)
|
148
|
+
STDERR.puts format('[%s] %s %s (%s)',
|
149
|
+
self.class,
|
150
|
+
method.to_s.upcase,
|
151
|
+
url,
|
152
|
+
body.inspect)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require_relative '../version'
|
2
|
+
require 'openssl'
|
3
|
+
|
4
|
+
module Asana
|
5
|
+
class HttpClient
|
6
|
+
# Internal: Adds environment information to a Faraday request.
|
7
|
+
class EnvironmentInfo
|
8
|
+
# Internal: The default user agent to use in all requests to the API.
|
9
|
+
USER_AGENT = "ruby-asana v#{Asana::VERSION}"
|
10
|
+
|
11
|
+
def initialize(user_agent = nil)
|
12
|
+
@user_agent = user_agent || USER_AGENT
|
13
|
+
@openssl_version = OpenSSL::OPENSSL_VERSION
|
14
|
+
@client_version = Asana::VERSION
|
15
|
+
@os = os
|
16
|
+
end
|
17
|
+
|
18
|
+
# Public: Augments a Faraday connection with information about the
|
19
|
+
# environment.
|
20
|
+
def configure(builder)
|
21
|
+
builder.headers[:user_agent] = @user_agent
|
22
|
+
builder.headers[:"X-Asana-Client-Lib"] = header
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def header
|
28
|
+
{ os: @os,
|
29
|
+
language: 'ruby',
|
30
|
+
language_version: RUBY_VERSION,
|
31
|
+
version: @client_version,
|
32
|
+
openssl_version: @openssl_version }
|
33
|
+
.map { |k, v| "#{k}=#{v}" }.join('&')
|
34
|
+
end
|
35
|
+
|
36
|
+
# rubocop:disable Metrics/MethodLength
|
37
|
+
def os
|
38
|
+
if RUBY_PLATFORM =~ /win32/ || RUBY_PLATFORM =~ /mingw/
|
39
|
+
'windows'
|
40
|
+
elsif RUBY_PLATFORM =~ /linux/
|
41
|
+
'linux'
|
42
|
+
elsif RUBY_PLATFORM =~ /darwin/
|
43
|
+
'darwin'
|
44
|
+
elsif RUBY_PLATFORM =~ /freebsd/
|
45
|
+
'freebsd'
|
46
|
+
else
|
47
|
+
'unknown'
|
48
|
+
end
|
49
|
+
end
|
50
|
+
# rubocop:enable Metrics/MethodLength
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
require 'multi_json'
|
2
|
+
|
3
|
+
require_relative '../errors'
|
4
|
+
|
5
|
+
module Asana
|
6
|
+
class HttpClient
|
7
|
+
# Internal: Handles errors from the API and re-raises them as proper
|
8
|
+
# exceptions.
|
9
|
+
module ErrorHandling
|
10
|
+
include Errors
|
11
|
+
|
12
|
+
module_function
|
13
|
+
|
14
|
+
# Public: Perform a request handling any API errors correspondingly.
|
15
|
+
#
|
16
|
+
# request - [Proc] a block that will execute the request.
|
17
|
+
#
|
18
|
+
# Returns a [Faraday::Response] object.
|
19
|
+
#
|
20
|
+
# Raises [Asana::Errors::InvalidRequest] for invalid requests.
|
21
|
+
# Raises [Asana::Errors::NotAuthorized] for unauthorized requests.
|
22
|
+
# Raises [Asana::Errors::Forbidden] for forbidden requests.
|
23
|
+
# Raises [Asana::Errors::NotFound] when a resource can't be found.
|
24
|
+
# Raises [Asana::Errors::RateLimitEnforced] when the API is throttling.
|
25
|
+
# Raises [Asana::Errors::ServerError] when there's a server problem.
|
26
|
+
# Raises [Asana::Errors::APIError] when the API returns an unknown error.
|
27
|
+
#
|
28
|
+
# rubocop:disable all
|
29
|
+
def handle(&request)
|
30
|
+
request.call
|
31
|
+
rescue Faraday::ClientError => e
|
32
|
+
raise e unless e.response
|
33
|
+
case e.response[:status]
|
34
|
+
when 400 then raise invalid_request(e.response)
|
35
|
+
when 401 then raise not_authorized(e.response)
|
36
|
+
when 403 then raise forbidden(e.response)
|
37
|
+
when 404 then raise not_found(e.response)
|
38
|
+
when 412 then recover_response(e.response)
|
39
|
+
when 429 then raise rate_limit_enforced(e.response)
|
40
|
+
when 500 then raise server_error(e.response)
|
41
|
+
else raise api_error(e.response)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
# rubocop:enable all
|
45
|
+
|
46
|
+
# Internal: Returns an InvalidRequest exception including a list of
|
47
|
+
# errors.
|
48
|
+
def invalid_request(response)
|
49
|
+
errors = body(response).fetch('errors', []).map { |e| e['message'] }
|
50
|
+
InvalidRequest.new(errors).tap do |exception|
|
51
|
+
exception.response = response
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Internal: Returns a NotAuthorized exception.
|
56
|
+
def not_authorized(response)
|
57
|
+
NotAuthorized.new.tap { |exception| exception.response = response }
|
58
|
+
end
|
59
|
+
|
60
|
+
# Internal: Returns a Forbidden exception.
|
61
|
+
def forbidden(response)
|
62
|
+
Forbidden.new.tap { |exception| exception.response = response }
|
63
|
+
end
|
64
|
+
|
65
|
+
# Internal: Returns a NotFound exception.
|
66
|
+
def not_found(response)
|
67
|
+
NotFound.new.tap { |exception| exception.response = response }
|
68
|
+
end
|
69
|
+
|
70
|
+
# Internal: Returns a RateLimitEnforced exception with a retry after
|
71
|
+
# field.
|
72
|
+
def rate_limit_enforced(response)
|
73
|
+
retry_after_seconds = response[:headers]['Retry-After']
|
74
|
+
RateLimitEnforced.new(retry_after_seconds).tap do |exception|
|
75
|
+
exception.response = response
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# Internal: Returns a ServerError exception with a unique phrase.
|
80
|
+
def server_error(response)
|
81
|
+
phrase = body(response).fetch('errors', []).first['phrase']
|
82
|
+
ServerError.new(phrase).tap do |exception|
|
83
|
+
exception.response = response
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Internal: Returns an APIError exception.
|
88
|
+
def api_error(response)
|
89
|
+
APIError.new.tap { |exception| exception.response = response }
|
90
|
+
end
|
91
|
+
|
92
|
+
# Internal: Parser a response body from JSON.
|
93
|
+
def body(response)
|
94
|
+
MultiJson.load(response[:body])
|
95
|
+
end
|
96
|
+
|
97
|
+
def recover_response(response)
|
98
|
+
r = response.dup.tap { |res| res[:body] = body(response) }
|
99
|
+
Response.new(OpenStruct.new(env: OpenStruct.new(r)))
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Asana
|
2
|
+
class HttpClient
|
3
|
+
# Internal: Represents a response from the Asana API.
|
4
|
+
class Response
|
5
|
+
# Public:
|
6
|
+
# Returns a [Faraday::Env] object for debugging.
|
7
|
+
attr_reader :faraday_env
|
8
|
+
# Public:
|
9
|
+
# Returns the [Integer] status code of the response.
|
10
|
+
attr_reader :status
|
11
|
+
# Public:
|
12
|
+
# Returns the [Hash] representing the parsed JSON body.
|
13
|
+
attr_reader :body
|
14
|
+
|
15
|
+
# Public: Wraps a Faraday response.
|
16
|
+
#
|
17
|
+
# faraday_response - [Faraday::Response] the Faraday response to wrap.
|
18
|
+
def initialize(faraday_response)
|
19
|
+
@faraday_env = faraday_response.env
|
20
|
+
@status = faraday_env.status
|
21
|
+
@body = faraday_env.body
|
22
|
+
end
|
23
|
+
|
24
|
+
# Public:
|
25
|
+
# Returns a [String] representation of the response.
|
26
|
+
def to_s
|
27
|
+
"#<Asana::HttpClient::Response status=#{@status} body=#{@body}>"
|
28
|
+
end
|
29
|
+
alias_method :inspect, :to_s
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require_relative 'resources/resource'
|
2
|
+
require_relative 'resources/collection'
|
3
|
+
|
4
|
+
Dir[File.join(File.dirname(__FILE__), 'resources', '*.rb')]
|
5
|
+
.each { |resource| require resource }
|
6
|
+
|
7
|
+
module Asana
|
8
|
+
# Public: Contains all the resources that the Asana API can return.
|
9
|
+
module Resources
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
### WARNING: This file is auto-generated by the asana-api-meta repo. Do not
|
2
|
+
### edit it manually.
|
3
|
+
|
4
|
+
module Asana
|
5
|
+
module Resources
|
6
|
+
# An _attachment_ object represents any file attached to a task in Asana,
|
7
|
+
# whether it's an uploaded file or one associated via a third-party service
|
8
|
+
# such as Dropbox or Google Drive.
|
9
|
+
class Attachment < Resource
|
10
|
+
|
11
|
+
|
12
|
+
attr_reader :id
|
13
|
+
|
14
|
+
class << self
|
15
|
+
# Returns the plural name of the resource.
|
16
|
+
def plural_name
|
17
|
+
'attachments'
|
18
|
+
end
|
19
|
+
|
20
|
+
# Returns the full record for a single attachment.
|
21
|
+
#
|
22
|
+
# id - [Id] Globally unique identifier for the attachment.
|
23
|
+
#
|
24
|
+
# options - [Hash] the request I/O options.
|
25
|
+
def find_by_id(client, id, options: {})
|
26
|
+
|
27
|
+
self.new(parse(client.get("/attachments/#{id}", options: options)).first, client: client)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Returns the compact records for all attachments on the task.
|
31
|
+
#
|
32
|
+
# task - [Id] Globally unique identifier for the task.
|
33
|
+
#
|
34
|
+
# per_page - [Integer] the number of records to fetch per page.
|
35
|
+
# options - [Hash] the request I/O options.
|
36
|
+
def find_by_task(client, task: required("task"), per_page: 20, options: {})
|
37
|
+
params = { limit: per_page }.reject { |_,v| v.nil? || Array(v).empty? }
|
38
|
+
Collection.new(parse(client.get("/tasks/#{task}/attachments", params: params, options: options)), type: self, client: client)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Asana
|
2
|
+
module Resources
|
3
|
+
# Internal: Mixin to add the ability to upload an attachment to a specific
|
4
|
+
# Asana resource (a Task, really).
|
5
|
+
module AttachmentUploading
|
6
|
+
# Uploads a new attachment to the resource.
|
7
|
+
#
|
8
|
+
# filename - [String] the absolute path of the file to upload.
|
9
|
+
# mime - [String] the MIME type of the file
|
10
|
+
# options - [Hash] the request I/O options
|
11
|
+
# data - [Hash] extra attributes to post
|
12
|
+
#
|
13
|
+
# rubocop:disable Metrics/AbcSize
|
14
|
+
# rubocop:disable Metrics/MethodLength
|
15
|
+
def attach(filename: required('filename'),
|
16
|
+
mime: required('mime'),
|
17
|
+
options: {}, **data)
|
18
|
+
path = File.expand_path(filename)
|
19
|
+
unless File.exist?(path)
|
20
|
+
fail ArgumentError, "file #{filename} doesn't exist"
|
21
|
+
end
|
22
|
+
upload = Faraday::UploadIO.new(path, mime)
|
23
|
+
response = client.post("/#{self.class.plural_name}/#{id}/attachments",
|
24
|
+
body: data,
|
25
|
+
upload: upload,
|
26
|
+
options: options)
|
27
|
+
Attachment.new(parse(response).first, client: client)
|
28
|
+
end
|
29
|
+
# rubocop:enable Metrics/MethodLength
|
30
|
+
# rubocop:enable Metrics/AbcSize
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require_relative 'response_helper'
|
2
|
+
|
3
|
+
module Asana
|
4
|
+
module Resources
|
5
|
+
# Public: Represents a paginated collection of Asana resources.
|
6
|
+
class Collection
|
7
|
+
include Enumerable
|
8
|
+
include ResponseHelper
|
9
|
+
|
10
|
+
attr_reader :elements
|
11
|
+
|
12
|
+
# Public: Initializes a collection representing a page of resources of a
|
13
|
+
# given type.
|
14
|
+
#
|
15
|
+
# (elements, extra) - [Array] an (String, Hash) tuple coming from the
|
16
|
+
# response parser.
|
17
|
+
# type - [Class] the type of resource that the collection
|
18
|
+
# contains. Defaults to the generic Resource.
|
19
|
+
# client - [Asana::Client] the client to perform requests.
|
20
|
+
def initialize((elements, extra),
|
21
|
+
type: Resource,
|
22
|
+
client: required('client'))
|
23
|
+
@elements = elements.map { |elem| type.new(elem, client: client) }
|
24
|
+
@type = type
|
25
|
+
@next_page_data = extra['next_page']
|
26
|
+
@client = client
|
27
|
+
end
|
28
|
+
|
29
|
+
# Public: Iterates over the elements of the collection.
|
30
|
+
def each(&block)
|
31
|
+
if block
|
32
|
+
@elements.each(&block)
|
33
|
+
(next_page || []).each(&block)
|
34
|
+
else
|
35
|
+
to_enum
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Public: Returns the size of the collection.
|
40
|
+
def size
|
41
|
+
to_a.size
|
42
|
+
end
|
43
|
+
alias_method :length, :size
|
44
|
+
|
45
|
+
# Public: Returns a String representation of the collection.
|
46
|
+
def to_s
|
47
|
+
"#<Asana::Collection<#{@type}> " \
|
48
|
+
"[#{@elements.map(&:inspect).join(', ')}" +
|
49
|
+
(@next_page_data ? ', ...' : '') + ']>'
|
50
|
+
end
|
51
|
+
|
52
|
+
alias_method :inspect, :to_s
|
53
|
+
|
54
|
+
# Public: Returns a new Asana::Resources::Collection with the next page
|
55
|
+
# or nil if there are no more pages. Caches the result.
|
56
|
+
def next_page
|
57
|
+
if defined?(@next_page)
|
58
|
+
@next_page
|
59
|
+
else
|
60
|
+
@next_page = if @next_page_data
|
61
|
+
response = parse(@client.get(@next_page_data['path']))
|
62
|
+
self.class.new(response, type: @type, client: @client)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|