asana 0.0.6 → 0.1.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 +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
|