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,49 @@
|
|
1
|
+
module Asana
|
2
|
+
module Resources
|
3
|
+
# An _event_ is an object representing a change to a resource that was
|
4
|
+
# observed by an event subscription.
|
5
|
+
#
|
6
|
+
# In general, requesting events on a resource is faster and subject to
|
7
|
+
# higher rate limits than requesting the resource itself. Additionally,
|
8
|
+
# change events bubble up - listening to events on a project would include
|
9
|
+
# when stories are added to tasks in the project, even on subtasks.
|
10
|
+
#
|
11
|
+
# Establish an initial sync token by making a request with no sync token.
|
12
|
+
# The response will be a `412` error - the same as if the sync token had
|
13
|
+
# expired.
|
14
|
+
#
|
15
|
+
# Subsequent requests should always provide the sync token from the
|
16
|
+
# immediately preceding call.
|
17
|
+
#
|
18
|
+
# Sync tokens may not be valid if you attempt to go 'backward' in the
|
19
|
+
# history by requesting previous tokens, though re-requesting the current
|
20
|
+
# sync token is generally safe, and will always return the same results.
|
21
|
+
#
|
22
|
+
# When you receive a `412 Precondition Failed` error, it means that the sync
|
23
|
+
# token is either invalid or expired. If you are attempting to keep a set of
|
24
|
+
# data in sync, this signals you may need to re-crawl the data.
|
25
|
+
#
|
26
|
+
# Sync tokens always expire after 24 hours, but may expire sooner, depending
|
27
|
+
# on load on the service.
|
28
|
+
class Event < Resource
|
29
|
+
attr_reader :type
|
30
|
+
|
31
|
+
class << self
|
32
|
+
# Returns the plural name of the resource.
|
33
|
+
def plural_name
|
34
|
+
'events'
|
35
|
+
end
|
36
|
+
|
37
|
+
# Public: Returns an infinite collection of events on a particular
|
38
|
+
# resource.
|
39
|
+
#
|
40
|
+
# client - [Asana::Client] the client to perform the requests.
|
41
|
+
# id - [String] the id of the resource to get events from.
|
42
|
+
# wait - [Integer] the number of seconds to wait between each poll.
|
43
|
+
def for(client, id, wait: 1)
|
44
|
+
Events.new(resource: id, client: client, wait: wait)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Asana
|
2
|
+
module Resources
|
3
|
+
# Public: Mixin to enable a resource with the ability to fetch events about
|
4
|
+
# itself.
|
5
|
+
module EventSubscription
|
6
|
+
# Public: Returns an infinite collection of events on the resource.
|
7
|
+
def events(wait: 1, options: {})
|
8
|
+
Events.new(resource: id, client: client, wait: wait, options: options)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
module Asana
|
2
|
+
module Resources
|
3
|
+
# Public: An infinite collection of events.
|
4
|
+
#
|
5
|
+
# Since they are infinite, if you want to filter or do other collection
|
6
|
+
# operations without blocking indefinitely you should call #lazy on them to
|
7
|
+
# turn them into a lazy collection.
|
8
|
+
#
|
9
|
+
# Examples:
|
10
|
+
#
|
11
|
+
# # Subscribes to an event stream and blocks indefinitely, printing
|
12
|
+
# # information of every event as it comes in.
|
13
|
+
# events = Events.new(resource: 'someresourceID', client: client)
|
14
|
+
# events.each do |event|
|
15
|
+
# puts [event.type, event.action]
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# # Lazily filters events as they come in and prints them.
|
19
|
+
# events = Events.new(resource: 'someresourceID', client: client)
|
20
|
+
# events.lazy.select { |e| e.type == 'task' }.each do |event|
|
21
|
+
# puts [event.type, event.action]
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
class Events
|
25
|
+
include Enumerable
|
26
|
+
|
27
|
+
# Public: Initializes a new Events instance, subscribed to a resource ID.
|
28
|
+
#
|
29
|
+
# resource - [String] a resource ID. Can be a task id or a workspace id.
|
30
|
+
# client - [Asana::Client] a client to perform the requests.
|
31
|
+
# wait - [Integer] the number of seconds to wait between each poll.
|
32
|
+
# options - [Hash] the request I/O options
|
33
|
+
def initialize(resource: required('resource'),
|
34
|
+
client: required('client'),
|
35
|
+
wait: 1, options: {})
|
36
|
+
@resource = resource
|
37
|
+
@client = client
|
38
|
+
@events = []
|
39
|
+
@wait = wait
|
40
|
+
@options = options
|
41
|
+
@sync = nil
|
42
|
+
@last_poll = nil
|
43
|
+
end
|
44
|
+
|
45
|
+
# Public: Iterates indefinitely over all events happening to a particular
|
46
|
+
# resource from the @sync timestamp or from now if it is nil.
|
47
|
+
def each(&block)
|
48
|
+
if block
|
49
|
+
loop do
|
50
|
+
poll if @events.empty?
|
51
|
+
event = @events.shift
|
52
|
+
yield event if event
|
53
|
+
end
|
54
|
+
else
|
55
|
+
to_enum
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
# Internal: Polls and fetches all events that have occurred since the sync
|
62
|
+
# token was created. Updates the sync token as it comes back from the
|
63
|
+
# response.
|
64
|
+
#
|
65
|
+
# If we polled less than @wait seconds ago, we don't do anything.
|
66
|
+
#
|
67
|
+
# Notes:
|
68
|
+
#
|
69
|
+
# On the first request, the sync token is not passed (because it is
|
70
|
+
# nil). The response will be the same as for an expired sync token, and
|
71
|
+
# will include a new valid sync token.
|
72
|
+
#
|
73
|
+
# If the sync token is too old (which may happen from time to time)
|
74
|
+
# the API will return a `412 Precondition Failed` error, and include
|
75
|
+
# a fresh `sync` token in the response.
|
76
|
+
def poll
|
77
|
+
rate_limiting do
|
78
|
+
body = @client.get('/events',
|
79
|
+
params: params,
|
80
|
+
options: @options).body
|
81
|
+
@sync = body['sync']
|
82
|
+
@events += body.fetch('data', []).map do |event_data|
|
83
|
+
Event.new(event_data, client: @client)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Internal: Returns the formatted params for the poll request.
|
89
|
+
def params
|
90
|
+
{ resource: @resource, sync: @sync }.reject { |_, v| v.nil? }
|
91
|
+
end
|
92
|
+
|
93
|
+
# Internal: Executes a block if at least @wait seconds have passed since
|
94
|
+
# @last_poll.
|
95
|
+
def rate_limiting(&block)
|
96
|
+
return if @last_poll && Time.now - @last_poll <= @wait
|
97
|
+
block.call.tap { @last_poll = Time.now }
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -1,28 +1,154 @@
|
|
1
|
+
### WARNING: This file is auto-generated by the asana-api-meta repo. Do not
|
2
|
+
### edit it manually.
|
3
|
+
|
1
4
|
module Asana
|
2
|
-
|
5
|
+
module Resources
|
6
|
+
# A _project_ represents a prioritized list of tasks in Asana. It exists in a
|
7
|
+
# single workspace or organization and is accessible to a subset of users in
|
8
|
+
# that workspace or organization, depending on its permissions.
|
9
|
+
#
|
10
|
+
# Projects in organizations are shared with a single team. You cannot currently
|
11
|
+
# change the team of a project via the API. Non-organization workspaces do not
|
12
|
+
# have teams and so you should not specify the team of project in a
|
13
|
+
# regular workspace.
|
14
|
+
class Project < Resource
|
3
15
|
|
4
|
-
|
5
|
-
alias :destroy :method_not_allowed
|
16
|
+
include EventSubscription
|
6
17
|
|
7
|
-
def self.all_by_task(*args)
|
8
|
-
parent_resources :task
|
9
|
-
all(*args)
|
10
|
-
end
|
11
18
|
|
12
|
-
|
13
|
-
parent_resources :workspace
|
14
|
-
all(*args)
|
15
|
-
end
|
19
|
+
attr_reader :id
|
16
20
|
|
17
|
-
|
18
|
-
|
19
|
-
|
21
|
+
class << self
|
22
|
+
# Returns the plural name of the resource.
|
23
|
+
def plural_name
|
24
|
+
'projects'
|
25
|
+
end
|
20
26
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
27
|
+
# Creates a new project in a workspace or team.
|
28
|
+
#
|
29
|
+
# Every project is required to be created in a specific workspace or
|
30
|
+
# organization, and this cannot be changed once set. Note that you can use
|
31
|
+
# the `workspace` parameter regardless of whether or not it is an
|
32
|
+
# organization.
|
33
|
+
#
|
34
|
+
# If the workspace for your project _is_ an organization, you must also
|
35
|
+
# supply a `team` to share the project with.
|
36
|
+
#
|
37
|
+
# Returns the full record of the newly created project.
|
38
|
+
#
|
39
|
+
# workspace - [Id] The workspace or organization to create the project in.
|
40
|
+
# team - [Id] If creating in an organization, the specific team to create the
|
41
|
+
# project in.
|
42
|
+
#
|
43
|
+
# options - [Hash] the request I/O options.
|
44
|
+
# data - [Hash] the attributes to post.
|
45
|
+
def create(client, workspace: required("workspace"), team: nil, options: {}, **data)
|
46
|
+
with_params = data.merge(workspace: workspace, team: team).reject { |_,v| v.nil? || Array(v).empty? }
|
47
|
+
self.new(parse(client.post("/projects", body: with_params, options: options)).first, client: client)
|
48
|
+
end
|
49
|
+
|
50
|
+
# If the workspace for your project _is_ an organization, you must also
|
51
|
+
# supply a `team` to share the project with.
|
52
|
+
#
|
53
|
+
# Returns the full record of the newly created project.
|
54
|
+
#
|
55
|
+
# workspace - [Id] The workspace or organization to create the project in.
|
56
|
+
# options - [Hash] the request I/O options.
|
57
|
+
# data - [Hash] the attributes to post.
|
58
|
+
def create_in_workspace(client, workspace: required("workspace"), options: {}, **data)
|
59
|
+
|
60
|
+
self.new(parse(client.post("/workspaces/#{workspace}/projects", body: data, options: options)).first, client: client)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Creates a project shared with the given team.
|
64
|
+
#
|
65
|
+
# Returns the full record of the newly created project.
|
66
|
+
#
|
67
|
+
# team - [Id] The team to create the project in.
|
68
|
+
# options - [Hash] the request I/O options.
|
69
|
+
# data - [Hash] the attributes to post.
|
70
|
+
def create_in_team(client, team: required("team"), options: {}, **data)
|
71
|
+
|
72
|
+
self.new(parse(client.post("/teams/#{team}/projects", body: data, options: options)).first, client: client)
|
73
|
+
end
|
74
|
+
|
75
|
+
# Returns the complete project record for a single project.
|
76
|
+
#
|
77
|
+
# id - [Id] The project to get.
|
78
|
+
# options - [Hash] the request I/O options.
|
79
|
+
def find_by_id(client, id, options: {})
|
26
80
|
|
81
|
+
self.new(parse(client.get("/projects/#{id}", options: options)).first, client: client)
|
82
|
+
end
|
83
|
+
|
84
|
+
# Returns the compact project records for some filtered set of projects.
|
85
|
+
# Use one or more of the parameters provided to filter the projects returned.
|
86
|
+
#
|
87
|
+
# workspace - [Id] The workspace or organization to filter projects on.
|
88
|
+
# team - [Id] The team to filter projects on.
|
89
|
+
# archived - [Boolean] Only return projects whose `archived` field takes on the value of
|
90
|
+
# this parameter.
|
91
|
+
#
|
92
|
+
# per_page - [Integer] the number of records to fetch per page.
|
93
|
+
# options - [Hash] the request I/O options.
|
94
|
+
def find_all(client, workspace: nil, team: nil, archived: nil, per_page: 20, options: {})
|
95
|
+
params = { workspace: workspace, team: team, archived: archived, limit: per_page }.reject { |_,v| v.nil? || Array(v).empty? }
|
96
|
+
Collection.new(parse(client.get("/projects", params: params, options: options)), type: self, client: client)
|
97
|
+
end
|
98
|
+
|
99
|
+
# Returns the compact project records for all projects in the workspace.
|
100
|
+
#
|
101
|
+
# workspace - [Id] The workspace or organization to find projects in.
|
102
|
+
# archived - [Boolean] Only return projects whose `archived` field takes on the value of
|
103
|
+
# this parameter.
|
104
|
+
#
|
105
|
+
# per_page - [Integer] the number of records to fetch per page.
|
106
|
+
# options - [Hash] the request I/O options.
|
107
|
+
def find_by_workspace(client, workspace: required("workspace"), archived: nil, per_page: 20, options: {})
|
108
|
+
params = { archived: archived, limit: per_page }.reject { |_,v| v.nil? || Array(v).empty? }
|
109
|
+
Collection.new(parse(client.get("/workspaces/#{workspace}/projects", params: params, options: options)), type: self, client: client)
|
110
|
+
end
|
111
|
+
|
112
|
+
# Returns the compact project records for all projects in the team.
|
113
|
+
#
|
114
|
+
# team - [Id] The team to find projects in.
|
115
|
+
# archived - [Boolean] Only return projects whose `archived` field takes on the value of
|
116
|
+
# this parameter.
|
117
|
+
#
|
118
|
+
# per_page - [Integer] the number of records to fetch per page.
|
119
|
+
# options - [Hash] the request I/O options.
|
120
|
+
def find_by_team(client, team: required("team"), archived: nil, per_page: 20, options: {})
|
121
|
+
params = { archived: archived, limit: per_page }.reject { |_,v| v.nil? || Array(v).empty? }
|
122
|
+
Collection.new(parse(client.get("/teams/#{team}/projects", params: params, options: options)), type: self, client: client)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# A specific, existing project can be updated by making a PUT request on the
|
127
|
+
# URL for that project. Only the fields provided in the `data` block will be
|
128
|
+
# updated; any unspecified fields will remain unchanged.
|
129
|
+
#
|
130
|
+
# When using this method, it is best to specify only those fields you wish
|
131
|
+
# to change, or else you may overwrite changes made by another user since
|
132
|
+
# you last retrieved the task.
|
133
|
+
#
|
134
|
+
# Returns the complete updated project record.
|
135
|
+
#
|
136
|
+
# options - [Hash] the request I/O options.
|
137
|
+
# data - [Hash] the attributes to post.
|
138
|
+
def update(options: {}, **data)
|
139
|
+
|
140
|
+
refresh_with(parse(client.put("/projects/#{id}", body: data, options: options)).first)
|
141
|
+
end
|
142
|
+
|
143
|
+
# A specific, existing project can be deleted by making a DELETE request
|
144
|
+
# on the URL for that project.
|
145
|
+
#
|
146
|
+
# Returns an empty data record.
|
147
|
+
def delete()
|
148
|
+
|
149
|
+
client.delete("/projects/#{id}") && true
|
150
|
+
end
|
151
|
+
|
152
|
+
end
|
27
153
|
end
|
28
154
|
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module Asana
|
4
|
+
module Resources
|
5
|
+
# Internal: Global registry of Resource subclasses. It provides lookup from
|
6
|
+
# singular and plural names to the actual class objects.
|
7
|
+
#
|
8
|
+
# Examples
|
9
|
+
#
|
10
|
+
# class Unicorn < Asana::Resources::Resource
|
11
|
+
# path '/unicorns'
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# Registry.lookup(:unicorn) # => Unicorn
|
15
|
+
# Registry.lookup_many(:unicorns) # => Unicorn
|
16
|
+
#
|
17
|
+
module Registry
|
18
|
+
class << self
|
19
|
+
# Public: Registers a new resource class.
|
20
|
+
#
|
21
|
+
# resource_klass - [Class] the resource class.
|
22
|
+
#
|
23
|
+
# Returns nothing.
|
24
|
+
def register(resource_klass)
|
25
|
+
resources << resource_klass
|
26
|
+
end
|
27
|
+
|
28
|
+
# Public: Looks up a resource class by its singular name.
|
29
|
+
#
|
30
|
+
# singular_name - [#to_s] the name of the resource, e.g :unicorn.
|
31
|
+
#
|
32
|
+
# Returns the resource class or {Asana::Resources::Resource}.
|
33
|
+
def lookup(singular_name)
|
34
|
+
resources.detect do |klass|
|
35
|
+
klass.singular_name.to_s == singular_name.to_s
|
36
|
+
end || Resource
|
37
|
+
end
|
38
|
+
|
39
|
+
# Public: Looks up a resource class by its plural name.
|
40
|
+
#
|
41
|
+
# plural_name - [#to_s] the plural name of the resource, e.g :unicorns.
|
42
|
+
#
|
43
|
+
# Returns the resource class or {Asana::Resources::Resource}.
|
44
|
+
def lookup_many(plural_name)
|
45
|
+
resources.detect do |klass|
|
46
|
+
klass.plural_name.to_s == plural_name.to_s
|
47
|
+
end || Resource
|
48
|
+
end
|
49
|
+
|
50
|
+
# Internal: A set of Resource classes.
|
51
|
+
#
|
52
|
+
# Returns the Set, defaulting to the empty set.
|
53
|
+
#
|
54
|
+
# Note: this object is a mutable singleton, so it should not be accessed
|
55
|
+
# from multiple threads.
|
56
|
+
def resources
|
57
|
+
@resources ||= Set.new
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
require_relative 'registry'
|
2
|
+
require_relative 'response_helper'
|
3
|
+
|
4
|
+
module Asana
|
5
|
+
module Resources
|
6
|
+
# Public: The base resource class which provides some sugar over common
|
7
|
+
# resource functionality.
|
8
|
+
class Resource
|
9
|
+
include ResponseHelper
|
10
|
+
extend ResponseHelper
|
11
|
+
|
12
|
+
def self.inherited(base)
|
13
|
+
Registry.register(base)
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(data, client: required('client'))
|
17
|
+
@_client = client
|
18
|
+
@_data = data
|
19
|
+
data.each do |k, v|
|
20
|
+
instance_variable_set(:"@#{k}", v) if respond_to?(k)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# If it has findById, it implements #refresh
|
25
|
+
def refresh
|
26
|
+
if self.class.respond_to?(:find_by_id)
|
27
|
+
self.class.find_by_id(client, id)
|
28
|
+
else
|
29
|
+
fail "#{self.class.name} does not respond to #find_by_id"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Internal: Proxies method calls to the data, wrapping it accordingly and
|
34
|
+
# caching the result by defining a real reader method.
|
35
|
+
#
|
36
|
+
# Returns the value for the requested property.
|
37
|
+
#
|
38
|
+
# Raises a NoMethodError if the property doesn't exist.
|
39
|
+
def method_missing(m, *args)
|
40
|
+
super unless respond_to_missing?(m, *args)
|
41
|
+
cache(m, wrapped(to_h[m.to_s]))
|
42
|
+
end
|
43
|
+
|
44
|
+
# Internal: Guard for the method_missing proxy. Checks if the resource
|
45
|
+
# actually has a specific piece of data at all.
|
46
|
+
#
|
47
|
+
# Returns true if the resource has the property, false otherwise.
|
48
|
+
def respond_to_missing?(m, *)
|
49
|
+
to_h.key?(m.to_s)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Public:
|
53
|
+
# Returns the raw Hash representation of the data.
|
54
|
+
def to_h
|
55
|
+
@_data
|
56
|
+
end
|
57
|
+
|
58
|
+
def to_s
|
59
|
+
attrs = to_h.map { |k, _| "#{k}: #{public_send(k).inspect}" }.join(', ')
|
60
|
+
"#<Asana::#{self.class.name.split('::').last} #{attrs}>"
|
61
|
+
end
|
62
|
+
|
63
|
+
alias_method :inspect, :to_s
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
# Internal: The Asana::Client instance.
|
68
|
+
def client # rubocop:disable Style/TrivialAccessors
|
69
|
+
@_client
|
70
|
+
end
|
71
|
+
|
72
|
+
# Internal: Caches a property and a value by defining a reader method for
|
73
|
+
# it.
|
74
|
+
#
|
75
|
+
# property - [#to_s] the property
|
76
|
+
# value - [Object] the corresponding value
|
77
|
+
#
|
78
|
+
# Returns the value.
|
79
|
+
def cache(property, value)
|
80
|
+
field = :"@#{property}"
|
81
|
+
instance_variable_set(field, value)
|
82
|
+
define_singleton_method(property) { instance_variable_get(field) }
|
83
|
+
value
|
84
|
+
end
|
85
|
+
|
86
|
+
# Internal: Wraps a value in a more useful class if possible, namely a
|
87
|
+
# Resource or a Collection.
|
88
|
+
#
|
89
|
+
# Returns the wrapped value or the plain value if it couldn't be wrapped.
|
90
|
+
def wrapped(value)
|
91
|
+
case value
|
92
|
+
when Hash then Resource.new(value, client: client)
|
93
|
+
when Array then value.map(&method(:wrapped))
|
94
|
+
else value
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def refresh_with(data)
|
99
|
+
self.class.new(data, client: @client)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|