openc-asana 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +4 -0
  3. data/.gitignore +13 -0
  4. data/.rspec +4 -0
  5. data/.rubocop.yml +11 -0
  6. data/.travis.yml +12 -0
  7. data/.yardopts +5 -0
  8. data/CODE_OF_CONDUCT.md +13 -0
  9. data/Gemfile +21 -0
  10. data/Guardfile +86 -0
  11. data/LICENSE.txt +21 -0
  12. data/README.md +355 -0
  13. data/Rakefile +65 -0
  14. data/examples/Gemfile +6 -0
  15. data/examples/Gemfile.lock +59 -0
  16. data/examples/api_token.rb +21 -0
  17. data/examples/cli_app.rb +25 -0
  18. data/examples/events.rb +38 -0
  19. data/examples/omniauth_integration.rb +54 -0
  20. data/lib/asana.rb +12 -0
  21. data/lib/asana/authentication.rb +8 -0
  22. data/lib/asana/authentication/oauth2.rb +42 -0
  23. data/lib/asana/authentication/oauth2/access_token_authentication.rb +51 -0
  24. data/lib/asana/authentication/oauth2/bearer_token_authentication.rb +32 -0
  25. data/lib/asana/authentication/oauth2/client.rb +50 -0
  26. data/lib/asana/authentication/token_authentication.rb +20 -0
  27. data/lib/asana/client.rb +124 -0
  28. data/lib/asana/client/configuration.rb +165 -0
  29. data/lib/asana/errors.rb +92 -0
  30. data/lib/asana/http_client.rb +155 -0
  31. data/lib/asana/http_client/environment_info.rb +53 -0
  32. data/lib/asana/http_client/error_handling.rb +103 -0
  33. data/lib/asana/http_client/response.rb +32 -0
  34. data/lib/asana/resource_includes/attachment_uploading.rb +33 -0
  35. data/lib/asana/resource_includes/collection.rb +68 -0
  36. data/lib/asana/resource_includes/event.rb +51 -0
  37. data/lib/asana/resource_includes/event_subscription.rb +14 -0
  38. data/lib/asana/resource_includes/events.rb +103 -0
  39. data/lib/asana/resource_includes/registry.rb +63 -0
  40. data/lib/asana/resource_includes/resource.rb +103 -0
  41. data/lib/asana/resource_includes/response_helper.rb +14 -0
  42. data/lib/asana/resources.rb +14 -0
  43. data/lib/asana/resources/attachment.rb +44 -0
  44. data/lib/asana/resources/project.rb +154 -0
  45. data/lib/asana/resources/story.rb +64 -0
  46. data/lib/asana/resources/tag.rb +120 -0
  47. data/lib/asana/resources/task.rb +300 -0
  48. data/lib/asana/resources/team.rb +55 -0
  49. data/lib/asana/resources/user.rb +72 -0
  50. data/lib/asana/resources/workspace.rb +91 -0
  51. data/lib/asana/ruby2_0_0_compatibility.rb +3 -0
  52. data/lib/asana/version.rb +5 -0
  53. data/lib/templates/index.js +8 -0
  54. data/lib/templates/resource.ejs +225 -0
  55. data/openc-asana.gemspec +32 -0
  56. data/package.json +7 -0
  57. metadata +200 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 5e8b344a69d1e1f4e4179036f6ea1f8fbd0683e9
4
+ data.tar.gz: c563fc621a50385012b203543ad8e4abd37f4069
5
+ SHA512:
6
+ metadata.gz: 13d6487fa1e320009acb725ee360e917ca9f27ac95c3d37dab7076a81ac711aa696d9ad3f9827b826cbd139c486e8465bd18466a3ef2fb10705cce55c47fb897
7
+ data.tar.gz: 3d734aa235a08302f979644bec4d0230784346449ec165b6f98ad4035cad459cd8ca3e7b653b88a420aff9e90a59a194bc5e7ab046d68b2d1d92db44a9d2040f
data/.codeclimate.yml ADDED
@@ -0,0 +1,4 @@
1
+ languages:
2
+ Ruby: true
3
+ exclude_paths:
4
+ - "lib/asana/resources/*"
data/.gitignore ADDED
@@ -0,0 +1,13 @@
1
+ .idea
2
+ /.bundle/
3
+ /.yardoc
4
+ /Gemfile.lock
5
+ /_yardoc/
6
+ /coverage/
7
+ /doc/
8
+ /pkg/
9
+ /spec/reports/
10
+ /tmp/
11
+ /bin/
12
+ test.rb
13
+ /node_modules/
data/.rspec ADDED
@@ -0,0 +1,4 @@
1
+ --color
2
+ --require spec_helper
3
+ --require asana
4
+ --order random
data/.rubocop.yml ADDED
@@ -0,0 +1,11 @@
1
+ AllCops:
2
+ Include:
3
+ - '**/Rakefile'
4
+ Exclude:
5
+ - 'bin/**/*'
6
+ - 'examples/**/*'
7
+ - 'lib/asana/resources/*'
8
+ - 'spec/templates/unicorn.rb'
9
+ - 'spec/templates/world.rb'
10
+ - 'test.rb'
11
+ require: rubocop-rspec
data/.travis.yml ADDED
@@ -0,0 +1,12 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.2
4
+ - 2.0.0
5
+ deploy:
6
+ provider: rubygems
7
+ api_key:
8
+ secure: ZFzExKt6auBOcQyg8955GwlZW2OaS64Q+XHGSay2MzjALw1iiy5uuMdwkueCKrGWSv6/eBe9jjsmYBe3OfkUIpIBbUacnbEYeaC70AyucNjvFrrl0YVYHb7neojarJUmKz9bz9Pkju/jdxksaYaj58xfq5YPQDfjFtdmylvuNEYpujT6goPEbxG4U4PpIhhQOZRDRXXAPS+f7jHejTSK06kvJjiJw0d51VJtBbp+0TKNKL6BDKdOKjKeHuebuUmSw8crDyaYdnwYwmNg1cJrGOv2t76M08zoKkkIO2lwPMHisi1/+cbVcZfxM4SfdHJeU6cQuRdb0uCUbbj6GsGwT8vWP2mGUrLe4UV/GfZDmvK3MKeKIlkgig31a3Qny9yjn8EjSnKHYuHBbJvPQDPPpFUfgEneUxn2t4P6m+epkd1gldWqTWf8mhMR/6xAFT4s+BaxnMMJsTC3Ea+dZZ30EqCw/kx5B2Z1KVLgsxHeMN/Q+AeOcbOvlGDsFL0Mjk/PqDTW1AWKLs/D1ohcxjSmlNJGWR6JHa/Ei0GqjDE2+/ZGsKsRfcDD4kU5qnKdqdzDlbL3cL4tChzuWVcguYdrg1yZzqPrCPzmy+2D7Hphyaj9CPKEh7qwT+IQU5o/V2peOJUjKrMlJS4gFq6MvTDh5U59J88Kkg72DXhcEUcySkU=
9
+ gem: asana
10
+ on:
11
+ tags: true
12
+ repo: Asana/ruby-asana
data/.yardopts ADDED
@@ -0,0 +1,5 @@
1
+ --title "Asana API Ruby Client"
2
+ --readme README.md
3
+ --plugin tomdoc
4
+ lib
5
+ - LICENSE.txt
@@ -0,0 +1,13 @@
1
+ # Contributor Code of Conduct
2
+
3
+ As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
4
+
5
+ We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, age, or religion.
6
+
7
+ Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
8
+
9
+ Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
10
+
11
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
12
+
13
+ This Code of Conduct is adapted from the [Contributor Covenant](http:contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/)
data/Gemfile ADDED
@@ -0,0 +1,21 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in asana.gemspec
4
+ gemspec
5
+
6
+ group :tools do
7
+ gem 'rubocop'
8
+ gem 'rubocop-rspec'
9
+
10
+ gem 'guard'
11
+ gem 'guard-rspec'
12
+ gem 'guard-rubocop'
13
+ gem 'guard-yard'
14
+
15
+ gem 'yard'
16
+ gem 'yard-tomdoc'
17
+
18
+ gem 'byebug'
19
+
20
+ gem 'simplecov', require: false
21
+ end
data/Guardfile ADDED
@@ -0,0 +1,86 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ ## Uncomment and set this to only include directories you want to watch
5
+ # directories %w(app lib config test spec features)
6
+
7
+ ## Uncomment to clear the screen before every task
8
+ # clearing :on
9
+
10
+ ## Guard internally checks for changes in the Guardfile and exits.
11
+ ## If you want Guard to automatically start up again, run guard in a
12
+ ## shell loop, e.g.:
13
+ ##
14
+ ## $ while bundle exec guard; do echo "Restarting Guard..."; done
15
+ ##
16
+ ## Note: if you are using the `directories` clause above and you are not
17
+ ## watching the project directory ('.'), then you will want to move
18
+ ## the Guardfile to a watched dir and symlink it back, e.g.
19
+ #
20
+ # $ mkdir config
21
+ # $ mv Guardfile config/
22
+ # $ ln -s config/Guardfile .
23
+ #
24
+ # and, you'll have to watch "config/Guardfile" instead of "Guardfile"
25
+
26
+ # Note: The cmd option is now required due to the increasing number of ways
27
+ # rspec may be run, below are examples of the most common uses.
28
+ # * bundler: 'bundle exec rspec'
29
+ # * bundler binstubs: 'bin/rspec'
30
+ # * spring: 'bin/rspec' (This will use spring if running and you have
31
+ # installed the spring binstubs per the docs)
32
+ # * zeus: 'zeus rspec' (requires the server to be started separately)
33
+ # * 'just' rspec: 'rspec'
34
+
35
+ guard :rspec, cmd: "bundle exec rspec" do
36
+ require "guard/rspec/dsl"
37
+ dsl = Guard::RSpec::Dsl.new(self)
38
+
39
+ # Feel free to open issues for suggestions and improvements
40
+
41
+ # RSpec files
42
+ rspec = dsl.rspec
43
+ watch(rspec.spec_helper) { rspec.spec_dir }
44
+ watch(rspec.spec_support) { rspec.spec_dir }
45
+ watch(rspec.spec_files)
46
+
47
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
48
+
49
+ # Rails files
50
+ rails = dsl.rails(view_extensions: %w(erb haml slim))
51
+ dsl.watch_spec_files_for(rails.app_files)
52
+ dsl.watch_spec_files_for(rails.views)
53
+
54
+ watch(rails.controllers) do |m|
55
+ [
56
+ rspec.spec.("routing/#{m[1]}_routing"),
57
+ rspec.spec.("controllers/#{m[1]}_controller"),
58
+ rspec.spec.("acceptance/#{m[1]}")
59
+ ]
60
+ end
61
+
62
+ # Rails config changes
63
+ watch(rails.spec_helper) { rspec.spec_dir }
64
+ watch(rails.routes) { "#{rspec.spec_dir}/routing" }
65
+ watch(rails.app_controller) { "#{rspec.spec_dir}/controllers" }
66
+
67
+ # Capybara features specs
68
+ watch(rails.view_dirs) { |m| rspec.spec.("features/#{m[1]}") }
69
+
70
+ # Turnip features and steps
71
+ watch(%r{^spec/acceptance/(.+)\.feature$})
72
+ watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) do |m|
73
+ Dir[File.join("**/#{m[1]}.feature")][0] || "spec/acceptance"
74
+ end
75
+ end
76
+
77
+ guard :rubocop do
78
+ watch(%r{.+\.rb$})
79
+ watch(%r{(?:.+/)?\.rubocop\.yml$}) { |m| File.dirname(m[0]) }
80
+ end
81
+
82
+ guard 'yard' do
83
+ watch(%r{app/.+\.rb})
84
+ watch(%r{lib/.+\.rb})
85
+ watch(%r{ext/.+\.c})
86
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Asana, Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,355 @@
1
+ # Asana
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/asana.svg)](http://badge.fury.io/rb/asana)
4
+ [![Build Status](https://travis-ci.org/Asana/ruby-asana.svg?branch=master)](https://travis-ci.org/Asana/ruby-asana)
5
+ [![Code Climate](https://codeclimate.com/github/Asana/ruby-asana/badges/gpa.svg)](https://codeclimate.com/github/Asana/ruby-asana)
6
+ [![Dependency Status](https://gemnasium.com/Asana/ruby-asana.svg)](https://gemnasium.com/Asana/ruby-asana)
7
+
8
+
9
+ A Ruby client for the 1.0 version of the Asana API.
10
+
11
+ Supported rubies:
12
+
13
+ * MRI 2.0.0 up to 2.2.x stable
14
+
15
+ ## Installation
16
+
17
+ Add this line to your application's Gemfile:
18
+
19
+ ```ruby
20
+ gem 'asana'
21
+ ```
22
+
23
+ And then execute:
24
+
25
+ $ bundle
26
+
27
+ Or install it yourself as:
28
+
29
+ $ gem install asana
30
+
31
+ ## Usage
32
+
33
+ To do anything, you'll need always an instance of `Asana::Client` configured
34
+ with your preferred authentication method (see the Authentication section below
35
+ for more complex scenarios) and other options.
36
+
37
+ The most minimal example would be as follows:
38
+
39
+ ```ruby
40
+ require 'asana'
41
+
42
+ client = Asana::Client.new do |c|
43
+ c.authentication :api_token, 'my_api_token'
44
+ end
45
+
46
+ client.workspaces.find_all.first
47
+ ```
48
+
49
+ A full-blown customized client using OAuth2 wih a previously obtained refresh
50
+ token, Typhoeus as a Faraday adapter, a custom user agent and custom Faraday
51
+ middleware:
52
+
53
+ ```ruby
54
+ require 'asana'
55
+
56
+ client = Asana::Client.new do |c|
57
+ c.authentication :oauth2,
58
+ refresh_token: 'abc',
59
+ client_id: 'bcd',
60
+ client_secret: 'cde',
61
+ redirect_uri: 'http://example.org/auth'
62
+ c.faraday_adapter :typhoeus
63
+ c.configure_faraday { |conn| conn.use SomeFaradayMiddleware }
64
+ end
65
+
66
+ workspace = client.workspaces.find_by_id(12)
67
+ workspace.users
68
+ # => #<Asana::Collection<User> ...>
69
+ client.tags.create_in_workspace(workspace: workspace.id, name: 'foo')
70
+ # => #<Asana::Tag id: ..., name: "foo">
71
+ ```
72
+
73
+ All resources are exposed as methods on the `Asana::Client` instance. Check out
74
+ the [documentation for each of them][docs].
75
+
76
+ ### Authentication
77
+
78
+ This gem supports authenticating against the Asana API with either an API token or through OAuth2.
79
+
80
+ #### API Token
81
+
82
+ ```ruby
83
+ Asana::Client.new do |c|
84
+ c.authentication :api_token, 'my_api_token'
85
+ end
86
+ ```
87
+
88
+ #### OAuth2
89
+
90
+ Authenticating through OAuth2 is preferred. There are many ways you can do this.
91
+
92
+ ##### With a plain bearer token (doesn't support auto-refresh)
93
+
94
+ If you have a plain bearer token obtained somewhere else and you don't mind not
95
+ having your token auto-refresh, you can authenticate with it as follows:
96
+
97
+ ```ruby
98
+ Asana::Client.new do |c|
99
+ c.authentication :oauth2, bearer_token: 'my_bearer_token'
100
+ end
101
+ ```
102
+
103
+ ##### With a refresh token and client credentials
104
+
105
+ If you obtained a refresh token, you can use it together with your client
106
+ credentials to authenticate:
107
+
108
+ ```ruby
109
+ Asana::Client.new do |c|
110
+ c.authentication :oauth2,
111
+ refresh_token: 'abc',
112
+ client_id: 'bcd',
113
+ client_secret: 'cde',
114
+ redirect_uri: 'http://example.org/auth'
115
+ end
116
+ ```
117
+
118
+ ##### With an ::OAuth2::AccessToken object (from `omniauth-asana` for example)
119
+
120
+ If you use `omniauth-asana` or a browser-based OAuth2 authentication strategy in
121
+ general, possibly because your application is a web application, you can reuse
122
+ those credentials to authenticate with this API client. Here's how to do it from
123
+ the callback method:
124
+
125
+ ```ruby
126
+ # assuming we're using Sinatra and omniauth-asana
127
+ get '/auth/:name/callback' do
128
+ creds = request.env["omniauth.auth"]["credentials"].tap { |h| h.delete('expires') }
129
+ strategy = request.env["omniauth.strategy"]
130
+
131
+ # We need to refresh the omniauth OAuth2 token
132
+ access_token = OAuth2::AccessToken.from_hash(strategy.client, creds).refresh!
133
+
134
+ $client = Asana::Client.new do |c|
135
+ c.authentication :oauth2, access_token
136
+ end
137
+
138
+ redirect '/'
139
+ end
140
+ ```
141
+
142
+ See `examples/omniauth_integration.rb` for a working example of this.
143
+
144
+ ##### Using an OAuth2 offline authentication flow (for CLI applications)
145
+
146
+ If your application can't receive HTTP requests and thus you can't use
147
+ `omniauth-asana`, for example if it's a CLI application, you can authenticate as
148
+ follows:
149
+
150
+ ```ruby
151
+ access_token = Asana::Authentication::OAuth2.offline_flow(client_id: ...,
152
+ client_secret: ...)
153
+ client = Asana::Client.new do |c|
154
+ c.authentication :oauth2, access_token
155
+ end
156
+
157
+ client.tasks.find_by_id(12)
158
+ ```
159
+
160
+ This will print an authorization URL on STDOUT, and block until you paste in the
161
+ authorization code, which you can get by visiting that URL and granting the
162
+ necessary permissions.
163
+
164
+ ### Pagination
165
+
166
+ Whenever you ask for a collection of resources, you can provide a number of
167
+ results per page to fetch, between 1 and 100. If you don't provide any, it
168
+ defaults to 20.
169
+
170
+ ```ruby
171
+ my_tasks = client.tasks.find_by_tag(tag: tag_id, per_page: 5)
172
+ # => #<Asana::Collection<Task> ...>
173
+ ```
174
+
175
+ An `Asana::Collection` is a paginated collection -- it holds the first
176
+ `per_page` results, and a reference to the next page if any.
177
+
178
+ When you iterate an `Asana::Collection`, it'll transparently keep fetching all
179
+ the pages, and caching them along the way:
180
+
181
+ ```ruby
182
+ my_tasks.size # => 23, not 5
183
+ my_tasks.take(14)
184
+ # => [#<Asana::Task ...>, #<Asana::Task ...>, ... until 14]
185
+ ```
186
+
187
+ #### Manual pagination
188
+
189
+ If you only want to deal with one page at a time and manually paginate, you can
190
+ get the elements of the current page with `#elements` and ask for the next page
191
+ with `#next_page`, which will return an `Asana::Collection` with the next page
192
+ of elements:
193
+
194
+ ```ruby
195
+ my_tasks.elements # => [#<Asana::Task ...>, #<Asana::Task ...>, ... until 5]
196
+ my_tasks.next_page # => #<Asana::Collection ...>
197
+ ```
198
+
199
+ #### Lazy pagination
200
+
201
+ Because an `Asana::Collection` represents the entire collection, it is often
202
+ handy to just take what you need from it, rather than let it fetch all its
203
+ contents from the network. You can accomplish this by turning it into a lazy
204
+ collection with `#lazy`:
205
+
206
+ ```ruby
207
+ # let my_tasks be an Asana::Collection of 10 pages of 100 elements each
208
+ my_tasks.lazy.drop(120).take(15).to_a
209
+ # Fetches only 2 pages, enough to get elements 120 to 135
210
+ # => [#<Asana::Task ...>, #<Asana::Task ...>, ...]
211
+ ```
212
+
213
+ ### Error handling
214
+
215
+ In any request against the Asana API, there a number of errors that could
216
+ arise. Those are well documented in the [Asana API Documentation][apidocs], and
217
+ are represented as exceptions under the namespace `Asana::Errors`.
218
+
219
+ All errors are subclasses of `Asana::Errors::APIError`, so make sure to rescue
220
+ instances of this class if you want to handle them yourself.
221
+
222
+ ### I/O options
223
+
224
+ All requests (except `DELETE`) accept extra I/O options
225
+ [as documented in the API docs][io]. Just pass an extra `options` hash to any
226
+ request:
227
+
228
+ ```ruby
229
+ client.tasks.find_by_id(12, options: { expand: ['workspace'] })
230
+ ```
231
+
232
+ ### Attachment uploading
233
+
234
+ To attach a file to a task or a project, you just need its absolute path on your
235
+ filesystem and its MIME type, and the file will be uploaded for you:
236
+
237
+ ```ruby
238
+ task = client.tasks.find_by_id(12)
239
+ attachment = task.attach(filename: '/absolute/path/to/my/file.png',
240
+ mime: 'image/png')
241
+ attachment.name # => 'file.png'
242
+ ```
243
+
244
+ ### Event streams
245
+
246
+ To subscribe to an event stream of a task or a project, just call `#events` on
247
+ it:
248
+
249
+ ```ruby
250
+ task = client.tasks.find_by_id(12)
251
+ task.events # => #<Asana::Events ...>
252
+
253
+ # You can do the same with only the task id:
254
+ events = client.events.for(task.id)
255
+ ```
256
+
257
+ An `Asana::Events` object is an infinite collection of `Asana::Event`
258
+ instances. Be warned that if you call `#each` on it, it will block forever!
259
+
260
+ Note that, by default, an event stream will wait at least 1 second between
261
+ polls, but that's configurable with the `wait` parameter:
262
+
263
+ ```ruby
264
+ # wait at least 3 and a half seconds between each poll to the API
265
+ task.events(wait: 3.5) # => #<Asana::Events ...>
266
+ ```
267
+
268
+ There are some interesting things you can do with an event stream, as it is a
269
+ normal Ruby Enumerable. Read below to get some ideas.
270
+
271
+ #### Subscribe to the event stream with a callback, polling every 2 seconds
272
+
273
+ ```ruby
274
+ # Run this in another thread so that we don't block forever
275
+ events = client.tasks.find_by_id(12).events(wait: 2)
276
+ Thread.new do
277
+ events.each do |event|
278
+ notify_someone "New event arrived! #{event}"
279
+ end
280
+ end
281
+ ```
282
+
283
+ #### Make the stream lazy and filter it by a specific pattern
284
+
285
+ To do that we need to call `#lazy` on the `Events` instance, just like with any
286
+ other `Enumerable`.
287
+
288
+ ```ruby
289
+ events = client.tasks.find_by_id(12).events
290
+ only_change_events = events.lazy.select { |event| event.action == 'changed' }
291
+ Thread.new do
292
+ only_change_events.each do |event|
293
+ notify_someone "New change event arrived! #{event}"
294
+ end
295
+ end
296
+ ```
297
+
298
+ ## Development
299
+
300
+ You'll need Ruby 2.1+ and Node v0.10.26+ / NPM 1.4.3+ installed.
301
+
302
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run
303
+ `bin/console` for an interactive prompt that will allow you to experiment.
304
+
305
+ Run the build with `rake`. This is equivalent to:
306
+
307
+ $ rake spec && rake rubocop && rake yard
308
+
309
+ To install this gem onto your local machine, run `bundle exec rake install`.
310
+
311
+ ## Releasing a new version
312
+
313
+ To release a new version, run either of these commands:
314
+
315
+ rake bump:patch
316
+ rake bump:minor
317
+ rake bump:major
318
+
319
+ This will: update `lib/asana/version.rb`, commit and tag the commit. Then you
320
+ just need to `push --tags` to let Travis build and release the new version to
321
+ Rubygems:
322
+
323
+ git push --tags
324
+
325
+ ### Code generation
326
+
327
+ The specific Asana resource classes (`Tag`, `Workspace`, `Task`, etc) are
328
+ generated code, hence they shouldn't be modified by hand. The code that
329
+ generates it lives in `lib/templates/resource.ejs`, and is tested by generating
330
+ `spec/templates/unicorn.rb` and running `spec/templates/unicorn_spec.rb` as part
331
+ of the build.
332
+
333
+ If you wish to make changes on the code generation script:
334
+
335
+ 1. Add/modify a spec on `spec/templates/unicorn_spec.rb`
336
+ 2. Add your new feature or change to `lib/templates/resource.ejs`
337
+ 3. Run `rake` or, more granularly, `rake codegen && rspec
338
+ spec/templates/unicorn_spec.rb`
339
+
340
+ Once you're sure your code works, submit a pull request and ask the maintainer
341
+ to make a release, as they'll need to run a release script from the
342
+ [asana-api-meta][meta] repository.
343
+
344
+ ## Contributing
345
+
346
+ 1. Fork it ( https://github.com/[my-github-username]/asana/fork )
347
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
348
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
349
+ 4. Push to the branch (`git push origin my-new-feature`)
350
+ 5. Create a new Pull Request
351
+
352
+ [apidocs]: https://asana.com/developers
353
+ [io]: https://asana.com/developers/documentation/getting-started/input-output-options
354
+ [docs]: https://asana.github.com/ruby-asana
355
+ [meta]: https://github.com/asana/asana-api-meta