New Upstream Release - ruby-asana

Ready changes

Summary

Merged new upstream version: 2.0.3 (was: 2.0.0).

Diff

diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 6b7ae9e..3c54e2a 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -14,7 +14,7 @@ jobs:
     runs-on: ubuntu-latest
     strategy:
       matrix:
-        ruby: [2.5, 2.7, 3.0]
+        ruby: [2.7, 3.0, 3.1]
     steps:
       - uses: actions/checkout@v3
       - uses: ruby/setup-ruby@v1
diff --git a/.rubocop.yml b/.rubocop.yml
index fcc177f..ee6d0a5 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -1,14 +1,49 @@
+require: rubocop-rspec
+
 AllCops:
-  Include:
-    - '**/Rakefile'
+  TargetRubyVersion: 2.7
+  NewCops: enable
   Exclude:
     - 'bin/**/*'
     - 'examples/**/*'
     - 'lib/asana/resources/*'
+    - 'lib/asana/resources/gen/**/*'
     - 'spec/templates/unicorn.rb'
     - 'spec/templates/world.rb'
     - 'test.rb'
 
 LineLength:
   Max: 120
-require: rubocop-rspec
+
+Metrics/ParameterLists:
+  Enabled: false
+
+Metrics/ClassLength:
+  Enabled: false
+
+Metrics/MethodLength:
+  Max: 20
+
+Metrics/CyclomaticComplexity:
+  Max: 20
+
+Metrics/PerceivedComplexity:
+  Max: 20
+
+RSpec/ContextWording:
+  Enabled: false
+
+RSpec/ExampleLength:
+  Enabled: false
+
+RSpec/MultipleExpectations:
+  Enabled: false
+
+RSpec/NestedGroups:
+  Enabled: false
+
+RSpec/FilePath:
+  CustomTransform: {'OAuth2': 'oauth2'}
+
+RSpec/MultipleMemoizedHelpers:
+  Enabled: false
\ No newline at end of file
diff --git a/.ruby-version b/.ruby-version
index 197c4d5..49cdd66 100644
--- a/.ruby-version
+++ b/.ruby-version
@@ -1 +1 @@
-2.4.0
+2.7.6
diff --git a/Appraisals b/Appraisals
index df9544c..4341800 100644
--- a/Appraisals
+++ b/Appraisals
@@ -1,4 +1,9 @@
+# frozen_string_literal: true
 
-  appraise "faraday-1.0.0" do
-    gem "faraday", "1.0.0"
-  end
+appraise 'faraday-2.7.0' do
+  gem 'faraday', '2.7.0'
+end
+
+appraise 'faraday-2.6.0' do
+  gem 'faraday', '2.6.0'
+end
diff --git a/Gemfile b/Gemfile
index 2719830..fca6901 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 source 'https://rubygems.org'
 
 # Specify your gem's dependencies in asana.gemspec
@@ -9,8 +11,10 @@ group :tools do
   # routinely adds new checks which can cause our build to "break" even when no
   # changes have been made. In this situation it's better to intentionally
   # upgrade Rubocop and fix issues at that time.
-  gem 'rubocop', '~> 0.52.1'
-  gem 'rubocop-rspec', '~> 1.22.2'
+  gem 'rubocop', '~> 1.47.0'
+  gem 'rubocop-rspec', '~> 2.18.1'
+
+  gem 'oauth2', '~> 2.0.3'
 
   gem 'guard'
   gem 'guard-rspec'
@@ -24,5 +28,5 @@ group :tools do
 
   gem 'simplecov', require: false
 
-  gem "rack-protection", "1.5.5"
+  gem 'rack-protection', '1.5.5'
 end
diff --git a/Gemfile.lock b/Gemfile.lock
index 55e0171..550c951 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,10 +1,10 @@
 PATH
   remote: .
   specs:
-    asana (0.10.13)
-      faraday (~> 1.0)
-      faraday_middleware (~> 1.0)
-      faraday_middleware-multi_json (~> 0.0)
+    asana (2.0.3)
+      faraday (~> 2.0)
+      faraday-follow_redirects
+      faraday-multipart
       oauth2 (>= 1.4, < 3)
 
 GEM
@@ -19,34 +19,14 @@ GEM
     coderay (1.1.3)
     diff-lcs (1.5.0)
     docile (1.4.0)
-    faraday (1.10.0)
-      faraday-em_http (~> 1.0)
-      faraday-em_synchrony (~> 1.0)
-      faraday-excon (~> 1.1)
-      faraday-httpclient (~> 1.0)
-      faraday-multipart (~> 1.0)
-      faraday-net_http (~> 1.0)
-      faraday-net_http_persistent (~> 1.0)
-      faraday-patron (~> 1.0)
-      faraday-rack (~> 1.0)
-      faraday-retry (~> 1.0)
+    faraday (2.7.4)
+      faraday-net_http (>= 2.0, < 3.1)
       ruby2_keywords (>= 0.0.4)
-    faraday-em_http (1.0.0)
-    faraday-em_synchrony (1.0.0)
-    faraday-excon (1.1.0)
-    faraday-httpclient (1.0.1)
+    faraday-follow_redirects (0.3.0)
+      faraday (>= 1, < 3)
     faraday-multipart (1.0.4)
       multipart-post (~> 2)
-    faraday-net_http (1.0.1)
-    faraday-net_http_persistent (1.2.0)
-    faraday-patron (1.0.0)
-    faraday-rack (1.0.0)
-    faraday-retry (1.0.3)
-    faraday_middleware (1.2.0)
-      faraday (~> 1.0)
-    faraday_middleware-multi_json (0.0.6)
-      faraday_middleware
-      multi_json
+    faraday-net_http (3.0.2)
     ffi (1.15.5)
     formatador (1.1.0)
     guard (2.18.0)
@@ -70,78 +50,88 @@ GEM
       guard (>= 1.1.0)
       yard (>= 0.7.0)
     hashie (5.0.0)
-    jwt (2.4.1)
-    listen (3.7.1)
+    json (2.6.3)
+    jwt (2.7.0)
+    listen (3.8.0)
       rb-fsevent (~> 0.10, >= 0.10.3)
       rb-inotify (~> 0.9, >= 0.9.10)
     lumberjack (1.2.8)
     method_source (1.0.0)
-    multi_json (1.15.0)
     multi_xml (0.6.0)
-    multipart-post (2.2.3)
+    multipart-post (2.3.0)
     nenv (0.3.0)
     notiffany (0.1.3)
       nenv (~> 0.1)
       shellany (~> 0.0)
-    oauth2 (2.0.0)
+    oauth2 (2.0.9)
       faraday (>= 0.17.3, < 3.0)
       jwt (>= 1.0, < 3.0)
       multi_xml (~> 0.5)
-      rack (>= 1.2, < 3)
-      rash_alt (>= 0.4, < 1)
-      version_gem (~> 1.0)
+      rack (>= 1.2, < 4)
+      snaky_hash (~> 2.0)
+      version_gem (~> 1.1)
     parallel (1.22.1)
-    parser (2.7.2.0)
+    parser (3.2.2.0)
       ast (~> 2.4.1)
-    powerpack (0.1.3)
-    pry (0.14.1)
+    pry (0.14.2)
       coderay (~> 1.1)
       method_source (~> 1.0)
-    rack (2.2.3.1)
+    rack (3.0.7)
     rack-protection (1.5.5)
       rack
     rainbow (3.1.1)
     rake (13.0.6)
-    rash_alt (0.4.12)
-      hashie (>= 3.4)
-    rb-fsevent (0.11.1)
+    rb-fsevent (0.11.2)
     rb-inotify (0.10.1)
       ffi (~> 1.0)
-    rspec (3.11.0)
-      rspec-core (~> 3.11.0)
-      rspec-expectations (~> 3.11.0)
-      rspec-mocks (~> 3.11.0)
-    rspec-core (3.11.0)
-      rspec-support (~> 3.11.0)
-    rspec-expectations (3.11.0)
+    regexp_parser (2.7.0)
+    rexml (3.2.5)
+    rspec (3.12.0)
+      rspec-core (~> 3.12.0)
+      rspec-expectations (~> 3.12.0)
+      rspec-mocks (~> 3.12.0)
+    rspec-core (3.12.1)
+      rspec-support (~> 3.12.0)
+    rspec-expectations (3.12.2)
       diff-lcs (>= 1.2.0, < 2.0)
-      rspec-support (~> 3.11.0)
-    rspec-mocks (3.11.1)
+      rspec-support (~> 3.12.0)
+    rspec-mocks (3.12.5)
       diff-lcs (>= 1.2.0, < 2.0)
-      rspec-support (~> 3.11.0)
-    rspec-support (3.11.0)
-    rubocop (0.52.1)
+      rspec-support (~> 3.12.0)
+    rspec-support (3.12.0)
+    rubocop (1.47.0)
+      json (~> 2.3)
       parallel (~> 1.10)
-      parser (>= 2.4.0.2, < 3.0)
-      powerpack (~> 0.1)
+      parser (>= 3.2.0.0)
       rainbow (>= 2.2.2, < 4.0)
+      regexp_parser (>= 1.8, < 3.0)
+      rexml (>= 3.2.5, < 4.0)
+      rubocop-ast (>= 1.26.0, < 2.0)
       ruby-progressbar (~> 1.7)
-      unicode-display_width (~> 1.0, >= 1.0.1)
-    rubocop-rspec (1.22.2)
-      rubocop (>= 0.52.1)
-    ruby-progressbar (1.11.0)
+      unicode-display_width (>= 2.4.0, < 3.0)
+    rubocop-ast (1.28.0)
+      parser (>= 3.2.1.0)
+    rubocop-capybara (2.17.1)
+      rubocop (~> 1.41)
+    rubocop-rspec (2.18.1)
+      rubocop (~> 1.33)
+      rubocop-capybara (~> 2.17)
+    ruby-progressbar (1.13.0)
     ruby2_keywords (0.0.5)
     shellany (0.0.1)
-    simplecov (0.21.2)
+    simplecov (0.22.0)
       docile (~> 1.1)
       simplecov-html (~> 0.11)
       simplecov_json_formatter (~> 0.1)
     simplecov-html (0.12.3)
     simplecov_json_formatter (0.1.4)
+    snaky_hash (2.0.1)
+      hashie
+      version_gem (~> 1.1, >= 1.1.1)
     thor (1.2.1)
     tomparse (0.4.2)
-    unicode-display_width (1.8.0)
-    version_gem (1.0.0)
+    unicode-display_width (2.4.2)
+    version_gem (1.1.2)
     webrick (1.7.0)
     yard (0.9.28)
       webrick (~> 1.7.0)
@@ -150,6 +140,7 @@ GEM
       yard
 
 PLATFORMS
+  ruby
   universal-darwin-21
   x86_64-linux
 
@@ -161,11 +152,12 @@ DEPENDENCIES
   guard-rspec
   guard-rubocop
   guard-yard
+  oauth2 (~> 2.0.3)
   rack-protection (= 1.5.5)
   rake (~> 13.0)
   rspec (~> 3.2)
-  rubocop (~> 0.52.1)
-  rubocop-rspec (~> 1.22.2)
+  rubocop (~> 1.47.0)
+  rubocop-rspec (~> 2.18.1)
   simplecov
   yard
   yard-tomdoc
diff --git a/Guardfile b/Guardfile
index f62c490..d2358d9 100644
--- a/Guardfile
+++ b/Guardfile
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 # A sample Guardfile
 # More info at https://github.com/guard/guard#readme
 
@@ -23,7 +25,7 @@
 #
 # and, you'll have to watch "config/Guardfile" instead of "Guardfile"
 
-# Note: The cmd option is now required due to the increasing number of ways
+# NOTE: The cmd option is now required due to the increasing number of ways
 #       rspec may be run, below are examples of the most common uses.
 #  * bundler: 'bundle exec rspec'
 #  * bundler binstubs: 'bin/rspec'
@@ -32,8 +34,8 @@
 #  * zeus: 'zeus rspec' (requires the server to be started separately)
 #  * 'just' rspec: 'rspec'
 
-guard :rspec, cmd: "bundle exec rspec" do
-  require "guard/rspec/dsl"
+guard :rspec, cmd: 'bundle exec rspec' do
+  require 'guard/rspec/dsl'
   dsl = Guard::RSpec::Dsl.new(self)
 
   # Feel free to open issues for suggestions and improvements
@@ -47,15 +49,15 @@ guard :rspec, cmd: "bundle exec rspec" do
   watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
 
   # Rails files
-  rails = dsl.rails(view_extensions: %w(erb haml slim))
+  rails = dsl.rails(view_extensions: %w[erb haml slim])
   dsl.watch_spec_files_for(rails.app_files)
   dsl.watch_spec_files_for(rails.views)
 
   watch(rails.controllers) do |m|
     [
-      rspec.spec.("routing/#{m[1]}_routing"),
-      rspec.spec.("controllers/#{m[1]}_controller"),
-      rspec.spec.("acceptance/#{m[1]}")
+      rspec.spec.call("routing/#{m[1]}_routing"),
+      rspec.spec.call("controllers/#{m[1]}_controller"),
+      rspec.spec.call("acceptance/#{m[1]}")
     ]
   end
 
@@ -65,17 +67,17 @@ guard :rspec, cmd: "bundle exec rspec" do
   watch(rails.app_controller)  { "#{rspec.spec_dir}/controllers" }
 
   # Capybara features specs
-  watch(rails.view_dirs)     { |m| rspec.spec.("features/#{m[1]}") }
+  watch(rails.view_dirs) { |m| rspec.spec.call("features/#{m[1]}") }
 
   # Turnip features and steps
   watch(%r{^spec/acceptance/(.+)\.feature$})
   watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) do |m|
-    Dir[File.join("**/#{m[1]}.feature")][0] || "spec/acceptance"
+    Dir[File.join("**/#{m[1]}.feature")][0] || 'spec/acceptance'
   end
 end
 
 guard :rubocop do
-  watch(%r{.+\.rb$})
+  watch(/.+\.rb$/)
   watch(%r{(?:.+/)?\.rubocop\.yml$}) { |m| File.dirname(m[0]) }
 end
 
diff --git a/README.md b/README.md
index b34ee34..88cfa91 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,9 @@ A Ruby client for the 1.0 version of the Asana API.
 
 Supported rubies:
 
-* MRI 2.0.0 up to 2.2.x stable
+* MRI 2.7.x
+* MRI 3.0.x
+* MRI 3.1.x
 
 ## Gem Installation
 Add this line to your application's Gemfile:
@@ -41,7 +43,7 @@ client = Asana::Client.new do |c|
   c.authentication :access_token, 'personal_access_token'
 end
 
-client.workspaces.find_all.first
+client.workspaces.get_workspaces.first
 ```
 
 A full-blown customized client using OAuth2 wih a previously obtained refresh
@@ -61,7 +63,7 @@ client = Asana::Client.new do |c|
   c.configure_faraday { |conn| conn.use SomeFaradayMiddleware }
 end
 
-workspace = client.workspaces.find_by_id(12)
+workspace = client.workspaces.get_workspace(12)
 workspace.users
 # => #<Asana::Collection<User> ...>
 client.tags.create_in_workspace(workspace: workspace.id, name: 'foo')
@@ -152,7 +154,7 @@ client = Asana::Client.new do |c|
   c.authentication :oauth2, access_token
 end
 
-client.tasks.find_by_id(12)
+client.tasks.get_task(12)
 ```
 
 This will print an authorization URL on STDOUT, and block until you paste in the
@@ -166,7 +168,7 @@ results per page to fetch, between 1 and 100. If you don't provide any, it
 defaults to 20.
 
 ```ruby
-my_tasks = client.tasks.find_by_tag(tag: tag_id, per_page: 5)
+my_tasks = client.tasks.get_tasks_for_tag(tag: tag_id, per_page: 5)
 # => #<Asana::Collection<Task> ...>
 ```
 
@@ -226,7 +228,7 @@ All requests (except `DELETE`) accept extra I/O options
 request:
 
 ```ruby
-client.tasks.find_by_id(12, options: { expand: ['workspace'] })
+client.tasks.get_task(12, options: { expand: ['workspace'] })
 ```
 
 ### Attachment uploading
@@ -235,7 +237,7 @@ To attach a file to a task or a project, you just need its absolute path on your
 filesystem and its MIME type, and the file will be uploaded for you:
 
 ```ruby
-task = client.tasks.find_by_id(12)
+task = client.tasks.get_task(12)
 attachment = task.attach(filename: '/absolute/path/to/my/file.png',
                          mime: 'image/png')
 attachment.name # => 'file.png'
@@ -247,7 +249,7 @@ To subscribe to an event stream of a task or a project, just call `#events` on
 it:
 
 ```ruby
-task = client.tasks.find_by_id(12)
+task = client.tasks.get_task(12)
 task.events # => #<Asana::Events ...>
 
 # You can do the same with only the task id:
@@ -272,7 +274,7 @@ normal Ruby Enumerable. Read below to get some ideas.
 
 ```ruby
 # Run this in another thread so that we don't block forever
-events = client.tasks.find_by_id(12).events(wait: 2)
+events = client.tasks.get_task(12).events(wait: 2)
 Thread.new do
   events.each do |event|
     notify_someone "New event arrived! #{event}"
@@ -286,7 +288,7 @@ To do that we need to call `#lazy` on the `Events` instance, just like with any
 other `Enumerable`.
 
 ```ruby
-events = client.tasks.find_by_id(12).events
+events = client.tasks.get_task(12).events
 only_change_events = events.lazy.select { |event| event.action == 'changed' }
 Thread.new do
   only_change_events.each do |event|
@@ -316,7 +318,7 @@ If you would rather suppress these warnings, you can set
 
 ## Development
 
-You'll need Ruby 2.5+ and Node v0.10.26+ / NPM 1.4.3+ installed.
+You'll need Ruby 2.7+ and Node v0.10.26+ / NPM 1.4.3+ installed.
 
 After checking out the repo, run `bin/setup` to install dependencies. Then, run
 `bin/console` for an interactive prompt that will allow you to experiment.
@@ -329,7 +331,17 @@ To install this gem onto your local machine, run `bundle exec rake install`.
 
 ## Releasing a new version
 
-To release a new version, run either of these commands:
+Prerequisite: Before deployment, make sure you have Ruby version `2.7` installed
+
+### Automatic Deployment
+
+First, install dependencies:
+
+```
+bundle install
+```
+
+Then, to release a new version, run one of these commands:
 
     rake bump:patch
     rake bump:minor
diff --git a/Rakefile b/Rakefile
index cf697b3..a9d1a2e 100644
--- a/Rakefile
+++ b/Rakefile
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 require 'bundler/setup'
 require 'bundler/gem_tasks'
 require 'appraisal'
@@ -19,6 +21,7 @@ task :codegen do
   `node spec/support/codegen.js`
 end
 
+# rubocop:disable Metrics/BlockLength
 namespace :bump do
   def read_version
     File.readlines('./lib/asana/version.rb')
@@ -27,23 +30,17 @@ namespace :bump do
         .map { |n| Integer(n) }
   end
 
-  # rubocop:disable Metrics/MethodLength
   def write_version(major, minor, patch)
+    File.write('VERSION', "#{major}.#{minor}.#{patch}")
 
-    File.open('VERSION', 'w') do |f|
-      f.write "#{major}.#{minor}.#{patch}"
-    end
-
-    str = <<-EOS
-#:nodoc:
-module Asana
-  # Public: Version of the gem.
-  VERSION = '#{major}.#{minor}.#{patch}'
-end
-EOS
-    File.open('./lib/asana/version.rb', 'w') do |f|
-      f.write str
-    end
+    str = <<~RUBY
+      #:nodoc:
+      module Asana
+        # Public: Version of the gem.
+        VERSION = '#{major}.#{minor}.#{patch}'
+      end
+    RUBY
+    File.write('./lib/asana/version.rb', str)
 
     new_version = "#{major}.#{minor}.#{patch}"
     system('bundle lock --update')
@@ -73,10 +70,10 @@ EOS
     write_version major + 1, 0, 0
   end
 end
+# rubocop:enable Metrics/BlockLength
 
 desc 'Default: run the unit tests.'
-task default: [:all, :rubocop, :yard]
-
+task default: %i[all rubocop yard]
 
 desc 'Test the plugin under all supported Rails versions.'
 task :all do |_t|
diff --git a/VERSION b/VERSION
index 4dd35ad..6acdb44 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-0.10.13
\ No newline at end of file
+2.0.3
\ No newline at end of file
diff --git a/asana.gemspec b/asana.gemspec
index 24d38e0..2e96d1c 100644
--- a/asana.gemspec
+++ b/asana.gemspec
@@ -1,32 +1,34 @@
-# coding: utf-8
-lib = File.expand_path('../lib', __FILE__)
+# frozen_string_literal: true
+
+lib = File.expand_path('lib', __dir__)
 $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
 require 'asana/version'
 
 Gem::Specification.new do |spec|
-  spec.name          = "asana"
+  spec.name          = 'asana'
   spec.version       = Asana::VERSION
-  spec.authors       = ["Txus"]
-  spec.email         = ["me@txus.io"]
+  spec.authors       = ['Txus']
+  spec.email         = ['me@txus.io']
 
-  spec.summary       = %q{Official Ruby client for the Asana API}
-  spec.description   = %q{Official Ruby client for the Asana API}
-  spec.homepage      = "https://github.com/asana/ruby-asana"
-  spec.license       = "MIT"
+  spec.summary       = 'Official Ruby client for the Asana API'
+  spec.description   = 'Official Ruby client for the Asana API'
+  spec.homepage      = 'https://github.com/asana/ruby-asana'
+  spec.license       = 'MIT'
 
   spec.files         = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
-  spec.bindir        = "exe"
+  spec.bindir        = 'exe'
   spec.executables   = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
-  spec.require_paths = ["lib"]
+  spec.require_paths = ['lib']
 
-  spec.required_ruby_version = '>= 2.5'
+  spec.required_ruby_version = '>= 2.7'
 
-  spec.add_dependency "oauth2", ">= 1.4", '< 3'
-  spec.add_dependency "faraday", "~> 1.0"
-  spec.add_dependency "faraday_middleware", "~> 1.0"
-  spec.add_dependency "faraday_middleware-multi_json", "~> 0.0"
+  spec.add_dependency 'faraday', '~> 2.0'
+  spec.add_dependency 'faraday-follow_redirects'
+  spec.add_dependency 'faraday-multipart'
+  spec.add_dependency 'oauth2', '>= 1.4', '< 3'
 
-  spec.add_development_dependency "rake", "~> 13.0"
-  spec.add_development_dependency "rspec", "~> 3.2"
   spec.add_development_dependency 'appraisal', '~> 2.1', '>= 2.1'
+  spec.add_development_dependency 'rake', '~> 13.0'
+  spec.add_development_dependency 'rspec', '~> 3.2'
+  spec.metadata['rubygems_mfa_required'] = 'true'
 end
diff --git a/bin/console b/bin/console
new file mode 100755
index 0000000..c785fe8
--- /dev/null
+++ b/bin/console
@@ -0,0 +1,14 @@
+#!/usr/bin/env ruby
+
+require "bundler/setup"
+require "asana"
+
+# You can add fixtures and/or initialization code here to make experimenting
+# with your gem easier. You can also use a different console, if you like.
+
+# (If you use this, don't forget to add pry to your Gemfile!)
+# require "pry"
+# Pry.start
+
+require "irb"
+IRB.start
diff --git a/bin/setup b/bin/setup
new file mode 100755
index 0000000..ac051d5
--- /dev/null
+++ b/bin/setup
@@ -0,0 +1,7 @@
+#!/bin/bash
+set -euo pipefail
+IFS=$'\n\t'
+
+bundle install
+npm install
+# Do any other automated setup that you need to do here
diff --git a/debian/changelog b/debian/changelog
index d5ad94e..4d72d35 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,10 @@
+ruby-asana (2.0.3-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+  * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Sun, 11 Jun 2023 07:09:41 -0000
+
 ruby-asana (0.10.13-1) unstable; urgency=medium
 
   [ Debian Janitor ]
diff --git a/examples/cli_app.rb b/examples/cli_app.rb
index 0051daf..6d0727c 100644
--- a/examples/cli_app.rb
+++ b/examples/cli_app.rb
@@ -17,9 +17,9 @@ client = Asana::Client.new do |c|
 end
 
 puts "My Workspaces:"
-client.workspaces.find_all.each do |workspace|
+client.workspaces.get_workspaces.each do |workspace|
   puts "\t* #{workspace.name} - tags:"
-  client.tags.find_by_workspace(workspace: workspace.id).each do |tag|
+  client.tags.get_tags_for_workspace(workspace: workspace.id).each do |tag|
     puts "\t\t- #{tag.name}"
   end
 end
diff --git a/examples/events.rb b/examples/events.rb
index 6f8a98f..3bd2ead 100644
--- a/examples/events.rb
+++ b/examples/events.rb
@@ -13,10 +13,10 @@ client = Asana::Client.new do |c|
   c.authentication :access_token, access_token
 end
 
-workspace = client.workspaces.find_all.first
-task = client.tasks.find_all(assignee: "me", workspace: workspace.id).first
+workspace = client.workspaces.get_workspaces.first
+task = client.tasks.get_tasks(assignee: "me", workspace: workspace.id).first
 unless task
-  task = client.tasks.create(workspace: workspace.id, name: "Hello world!", assignee: "me")
+  task = client.tasks.create_task(workspace: workspace.id, name: "Hello world!", assignee: "me")
 end
 
 Thread.abort_on_exception = true
diff --git a/examples/personal_access_token.rb b/examples/personal_access_token.rb
index d5d2b40..f1dd719 100644
--- a/examples/personal_access_token.rb
+++ b/examples/personal_access_token.rb
@@ -13,9 +13,9 @@ client = Asana::Client.new do |c|
 end
 
 puts "My Workspaces:"
-client.workspaces.find_all.each do |workspace|
+client.workspaces.get_workspaces.each do |workspace|
   puts "\t* #{workspace.name} - tags:"
-  client.tags.find_by_workspace(workspace: workspace.id).each do |tag|
+  client.tags.get_tags_for_workspace(workspace: workspace.id).each do |tag|
     puts "\t\t- #{tag.name}"
   end
 end
diff --git a/lib/asana.rb b/lib/asana.rb
index 9ddeec1..78c8967 100644
--- a/lib/asana.rb
+++ b/lib/asana.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 require 'asana/ruby2_0_0_compatibility'
 require 'asana/authentication'
 require 'asana/resources'
diff --git a/lib/asana/authentication.rb b/lib/asana/authentication.rb
index 1644f07..1b42483 100644
--- a/lib/asana/authentication.rb
+++ b/lib/asana/authentication.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 require_relative 'authentication/oauth2'
 require_relative 'authentication/token_authentication'
 
diff --git a/lib/asana/authentication/oauth2.rb b/lib/asana/authentication/oauth2.rb
index dba700f..afb9555 100644
--- a/lib/asana/authentication/oauth2.rb
+++ b/lib/asana/authentication/oauth2.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 require_relative 'oauth2/bearer_token_authentication'
 require_relative 'oauth2/access_token_authentication'
 require_relative 'oauth2/client'
@@ -31,10 +33,10 @@ module Asana
         client = Client.new(client_id: client_id,
                             client_secret: client_secret,
                             redirect_uri: 'urn:ietf:wg:oauth:2.0:oob')
-        STDOUT.puts '1. Go to the following URL to authorize the ' \
-          " application: #{client.authorize_url}"
-        STDOUT.puts '2. Paste the authorization code here: '
-        auth_code = STDIN.gets.chomp
+        $stdout.puts '1. Go to the following URL to authorize the  ' \
+                     "application: #{client.authorize_url}"
+        $stdout.puts '2. Paste the authorization code here: '
+        auth_code = $stdin.gets.chomp
         client.token_from_auth_code(auth_code)
       end
     end
diff --git a/lib/asana/authentication/oauth2/access_token_authentication.rb b/lib/asana/authentication/oauth2/access_token_authentication.rb
index 6793c28..06841a8 100644
--- a/lib/asana/authentication/oauth2/access_token_authentication.rb
+++ b/lib/asana/authentication/oauth2/access_token_authentication.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 module Asana
   module Authentication
     module OAuth2
@@ -43,7 +45,8 @@ module Asana
         # Returns nothing.
         def configure(connection)
           @token = @token.refresh! if @token.expired?
-          connection.request :oauth2, @token.token
+
+          connection.request :authorization, 'Bearer', @token.token
         end
       end
     end
diff --git a/lib/asana/authentication/oauth2/bearer_token_authentication.rb b/lib/asana/authentication/oauth2/bearer_token_authentication.rb
index 06f6c36..4589f62 100644
--- a/lib/asana/authentication/oauth2/bearer_token_authentication.rb
+++ b/lib/asana/authentication/oauth2/bearer_token_authentication.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 module Asana
   module Authentication
     module OAuth2
@@ -24,8 +26,7 @@ module Asana
         #
         # Returns nothing.
         def configure(connection)
-          connection.authorization :Bearer, @token
-          connection.request :oauth2, token_type: 'bearer'
+          connection.request :authorization, 'Bearer', @token
         end
       end
     end
diff --git a/lib/asana/authentication/oauth2/client.rb b/lib/asana/authentication/oauth2/client.rb
index 009a8a4..80b71ee 100644
--- a/lib/asana/authentication/oauth2/client.rb
+++ b/lib/asana/authentication/oauth2/client.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 require 'oauth2'
 
 module Asana
diff --git a/lib/asana/authentication/token_authentication.rb b/lib/asana/authentication/token_authentication.rb
index ae762cf..4701107 100644
--- a/lib/asana/authentication/token_authentication.rb
+++ b/lib/asana/authentication/token_authentication.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 module Asana
   module Authentication
     # Public: Represents an API token authentication mechanism.
@@ -13,7 +15,7 @@ module Asana
       #
       # Returns nothing.
       def configure(connection)
-        connection.basic_auth(@token, '')
+        connection.request :authorization, :basic, @token, ''
       end
     end
   end
diff --git a/lib/asana/client.rb b/lib/asana/client.rb
index f3e2f11..6865f38 100644
--- a/lib/asana/client.rb
+++ b/lib/asana/client.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 require_relative 'authentication'
 require_relative 'client/configuration'
 require_relative 'resources'
@@ -59,12 +61,12 @@ module Asana
         @resource = resource
       end
 
-      def method_missing(m, *args, **kwargs, &block)
-        @resource.public_send(m, *([@client] + args), **kwargs, &block)
+      def method_missing(method_name, *args, **kwargs, &block)
+        @resource.public_send(method_name, *([@client] + args), **kwargs, &block)
       end
 
-      def respond_to_missing?(m, *)
-        @resource.respond_to?(m)
+      def respond_to_missing?(method_name, *)
+        @resource.respond_to?(method_name)
       end
     end
 
@@ -72,15 +74,15 @@ module Asana
     #
     # Yields a {Asana::Client::Configuration} object as a configuration
     # DSL. See {Asana::Client} for usage examples.
-    def initialize
-      config = Configuration.new.tap { |c| yield c }.to_h
+    def initialize(&block)
+      config = Configuration.new.tap(&block).to_h
       @http_client =
-        HttpClient.new(authentication:            config.fetch(:authentication),
-                       adapter:                   config[:faraday_adapter],
-                       user_agent:                config[:user_agent],
-                       debug_mode:                config[:debug_mode],
+        HttpClient.new(authentication: config.fetch(:authentication),
+                       adapter: config[:faraday_adapter],
+                       user_agent: config[:user_agent],
+                       debug_mode: config[:debug_mode],
                        log_asana_change_warnings: config[:log_asana_change_warnings],
-                       default_headers:           config[:default_headers],
+                       default_headers: config[:default_headers],
                        &config[:faraday_configuration])
     end
 
diff --git a/lib/asana/client/configuration.rb b/lib/asana/client/configuration.rb
index aa46e16..c863412 100644
--- a/lib/asana/client/configuration.rb
+++ b/lib/asana/client/configuration.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 module Asana
   class Client
     # Internal: Represents a configuration DSL for an Asana::Client.
@@ -17,7 +19,7 @@ module Asana
       # Public: Initializes an empty configuration object.
       def initialize
         @configuration = {
-            :log_asana_change_warnings => true
+          log_asana_change_warnings: true
         }
       end
 
@@ -100,7 +102,6 @@ module Asana
       #
       # Raises ArgumentError if the OAuth2 configuration arguments are invalid.
       #
-      # rubocop:disable Metrics/MethodLength
       def oauth2(value)
         case value
         when ::OAuth2::AccessToken
@@ -111,8 +112,8 @@ module Asana
           from_bearer_token(value[:bearer_token])
         else
           error 'Invalid OAuth2 configuration: pass in either an ' \
-            '::OAuth2::AccessToken object of your own or a hash ' \
-            'containing :refresh_token or :bearer_token.'
+                '::OAuth2::AccessToken object of your own or a hash ' \
+                'containing :refresh_token or :bearer_token.'
         end
       end
 
@@ -158,7 +159,7 @@ module Asana
       end
 
       def requiring(hash, *keys)
-        missing_keys = keys.select { |k| !hash.key?(k) }
+        missing_keys = keys.reject { |k| hash.key?(k) }
         missing_keys.any? && error("Missing keys: #{missing_keys.join(', ')}")
         keys.map { |k| hash[k] }
       end
diff --git a/lib/asana/errors.rb b/lib/asana/errors.rb
index a0224c1..e16ac9e 100644
--- a/lib/asana/errors.rb
+++ b/lib/asana/errors.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 module Asana
   # Public: Defines the different errors that the Asana API may throw, which the
   # client code may want to catch.
@@ -19,8 +21,8 @@ module Asana
     # user could not be authenticated.
     NotAuthorized = Class.new(APIError) do
       def to_s
-        'Valid credentials were not provided with the request, so the API could '\
-        'not associate a user with the request.'
+        'Valid credentials were not provided with the request, so the API could ' \
+          'not associate a user with the request.'
       end
     end
 
@@ -28,8 +30,8 @@ module Asana
     # that requires a premium account (Payment Required).
     PremiumOnly = Class.new(APIError) do
       def to_s
-        'The endpoint that is being requested is only available to premium '\
-        'users.'
+        'The endpoint that is being requested is only available to premium ' \
+          'users.'
       end
     end
 
@@ -37,18 +39,18 @@ module Asana
     # access the requested resource or to perform the requested action on it.
     Forbidden = Class.new(APIError) do
       def to_s
-        'The authorization and request syntax was valid but the server is refusing '\
-        'to complete the request. This can happen if you try to read or write '\
-        'to objects or properties that the user does not have access to.'
+        'The authorization and request syntax was valid but the server is refusing ' \
+          'to complete the request. This can happen if you try to read or write ' \
+          'to objects or properties that the user does not have access to.'
       end
     end
 
     # Public: A 404 error. Raised when the requested resource doesn't exist.
     NotFound = Class.new(APIError) do
       def to_s
-        'Either the request method and path supplied do not specify a known '\
-        'action in the API, or the object specified by the request does not '\
-        'exist.'
+        'Either the request method and path supplied do not specify a known ' \
+          'action in the API, or the object specified by the request does not ' \
+          'exist.'
       end
     end
 
@@ -59,11 +61,12 @@ module Asana
       attr_accessor :phrase
 
       def initialize(phrase)
+        super()
         @phrase = phrase
       end
 
       def to_s
-        "There has been an error on Asana's end. Use this unique phrase to "\
+        "There has been an error on Asana's end. Use this unique phrase to " \
         'identify the problem when contacting support: ' + %("#{@phrase}")
       end
     end
@@ -74,6 +77,7 @@ module Asana
       attr_accessor :errors
 
       def initialize(errors)
+        super()
         @errors = errors
       end
 
@@ -89,6 +93,7 @@ module Asana
       attr_accessor :retry_after_seconds
 
       def initialize(retry_after_seconds)
+        super()
         @retry_after_seconds = retry_after_seconds
       end
 
diff --git a/lib/asana/http_client.rb b/lib/asana/http_client.rb
index 28c56b7..06f813d 100644
--- a/lib/asana/http_client.rb
+++ b/lib/asana/http_client.rb
@@ -1,6 +1,7 @@
+# frozen_string_literal: true
+
 require 'faraday'
-require 'faraday_middleware'
-require 'faraday_middleware/multi_json'
+require 'faraday/follow_redirects'
 
 require_relative 'http_client/error_handling'
 require_relative 'http_client/environment_info'
@@ -11,7 +12,7 @@ module Asana
   # parsing and common options.
   class HttpClient
     # Internal: The API base URI.
-    BASE_URI = 'https://app.asana.com/api/1.0'.freeze
+    BASE_URI = 'https://app.asana.com/api/1.0'
 
     # Public: Initializes an HttpClient to make requests to the Asana API.
     #
@@ -130,7 +131,8 @@ module Asana
         yield builder if request_config
         configure_format(builder)
         add_middleware(builder)
-        @config.call(builder) if @config
+        configure_redirects(builder)
+        @config&.call(builder)
         use_adapter(builder, @adapter)
       end
     end
@@ -147,13 +149,16 @@ module Asana
     end
 
     def configure_format(builder)
-      builder.request :multi_json
-      builder.response :multi_json
+      builder.request :json
+      builder.response :json
     end
 
     def add_middleware(builder)
       builder.use Faraday::Response::RaiseError
-      builder.use FaradayMiddleware::FollowRedirects
+    end
+
+    def configure_redirects(builder)
+      builder.response :follow_redirects
     end
 
     def use_adapter(builder, adapter)
@@ -170,79 +175,75 @@ module Asana
     end
 
     def log_request(method, url, body)
-      STDERR.puts format('[%s] %s %s (%s)',
-                         self.class,
-                         method.to_s.upcase,
-                         url,
-                         body.inspect)
+      warn format('[%<klass>s] %<method>s %<url>s (%<body>s)',
+                  klass: self.class,
+                  method: method.to_s.upcase,
+                  url: url,
+                  body: body.inspect)
     end
 
+    # rubocop:disable Metrics/AbcSize
+    # rubocop:disable Metrics/MethodLength
     def log_asana_change_headers(request_headers, response_headers)
       change_header_key = nil
 
       response_headers.each_key do |key|
-        if key.downcase == 'asana-change'
-            change_header_key = key
-        end
+        change_header_key = key if key.downcase == 'asana-change'
       end
 
-      if change_header_key != nil
-        accounted_for_flags = Array.new
+      return if change_header_key.nil?
 
-        if request_headers == nil
-          request_headers = {}
-        end
-        # Grab the request's asana-enable flags
-        request_headers.each_key do |req_header|
-          if req_header.downcase == 'asana-enable'
-            request_headers[req_header].split(',').each do |flag|
-              accounted_for_flags.push(flag)
-            end
-          elsif req_header.downcase == 'asana-disable'
-            request_headers[req_header].split(',').each do |flag|
-              accounted_for_flags.push(flag)
-            end
+      accounted_for_flags = []
+
+      request_headers = {} if request_headers.nil?
+      # Grab the request's asana-enable flags
+      request_headers.each_key do |req_header|
+        case req_header.downcase
+        when 'asana-enable', 'asana-disable'
+          request_headers[req_header].split(',').each do |flag|
+            accounted_for_flags.push(flag)
           end
         end
+      end
+
+      changes = response_headers[change_header_key].split(',')
 
-        changes = response_headers[change_header_key].split(',')
-
-        changes.each do |unsplit_change|
-          change = unsplit_change.split(';')
-
-          name = nil
-          info = nil
-          affected = nil
-
-          change.each do |unsplit_field|
-            field = unsplit_field.split('=')
-
-            field[0].strip!
-            field[1].strip!
-            if field[0] == 'name'
-                name = field[1]
-            elsif field[0] == 'info'
-                info = field[1]
-            elsif field[0] == 'affected'
-                affected = field[1]
-            end
-
-            # Only show the error if the flag was not in the request's asana-enable header
-            if !(accounted_for_flags.include? name) && (affected == 'true')
-              message1 = 'This request is affected by the "%s"' +
-              ' deprecation. Please visit this url for more info: %s'
-              message2 = 'Adding "%s" to your "Asana-Enable" or ' +
-              '"Asana-Disable" header will opt in/out to this deprecation ' +
-              'and suppress this warning.'
-
-              STDERR.puts format(message1, name, info)
-              STDERR.puts format(message2, name)
-            end
+      changes.each do |unsplit_change|
+        change = unsplit_change.split(';')
+
+        name = nil
+        info = nil
+        affected = nil
+
+        change.each do |unsplit_field|
+          field = unsplit_field.split('=')
+
+          field[0].strip!
+          field[1].strip!
+          case field[0]
+          when 'name'
+            name = field[1]
+          when 'info'
+            info = field[1]
+          when 'affected'
+            affected = field[1]
           end
+
+          # Only show the error if the flag was not in the request's asana-enable header
+          next unless !(accounted_for_flags.include? name) && (affected == 'true')
+
+          message1 = 'This request is affected by the "%s" ' \
+                     'deprecation. Please visit this url for more info: %s'
+          message2 = 'Adding "%s" to your "Asana-Enable" or ' \
+                     '"Asana-Disable" header will opt in/out to this deprecation ' \
+                     'and suppress this warning.'
+
+          warn format(message1, name, info)
+          warn format(message2, name)
         end
       end
     end
+    # rubocop:enable Metrics/AbcSize
+    # rubocop:enable Metrics/MethodLength
   end
 end
-
-
diff --git a/lib/asana/http_client/environment_info.rb b/lib/asana/http_client/environment_info.rb
index 85dd2bc..3988f89 100644
--- a/lib/asana/http_client/environment_info.rb
+++ b/lib/asana/http_client/environment_info.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 require_relative '../version'
 require 'openssl'
 
@@ -6,7 +8,7 @@ module Asana
     # Internal: Adds environment information to a Faraday request.
     class EnvironmentInfo
       # Internal: The default user agent to use in all requests to the API.
-      USER_AGENT = "ruby-asana v#{Asana::VERSION}".freeze
+      USER_AGENT = "ruby-asana v#{Asana::VERSION}"
 
       def initialize(user_agent = nil)
         @user_agent = user_agent || USER_AGENT
@@ -19,7 +21,7 @@ module Asana
       # environment.
       def configure(builder)
         builder.headers[:user_agent] = @user_agent
-        builder.headers[:"X-Asana-Client-Lib"] = header
+        builder.headers[:'X-Asana-Client-Lib'] = header
       end
 
       private
@@ -33,21 +35,20 @@ module Asana
           .map { |k, v| "#{k}=#{v}" }.join('&')
       end
 
-      # rubocop:disable Metrics/MethodLength
       def os
-        if RUBY_PLATFORM =~ /win32/ || RUBY_PLATFORM =~ /mingw/
+        case RUBY_PLATFORM
+        when /win32/, /mingw/
           'windows'
-        elsif RUBY_PLATFORM =~ /linux/
+        when /linux/
           'linux'
-        elsif RUBY_PLATFORM =~ /darwin/
+        when /darwin/
           'darwin'
-        elsif RUBY_PLATFORM =~ /freebsd/
+        when /freebsd/
           'freebsd'
         else
           'unknown'
         end
       end
-      # rubocop:enable Metrics/MethodLength
     end
   end
 end
diff --git a/lib/asana/http_client/error_handling.rb b/lib/asana/http_client/error_handling.rb
index 75996cd..d843769 100644
--- a/lib/asana/http_client/error_handling.rb
+++ b/lib/asana/http_client/error_handling.rb
@@ -1,4 +1,4 @@
-require 'multi_json'
+# frozen_string_literal: true
 
 require_relative '../errors'
 
@@ -27,37 +27,34 @@ module Asana
       # Raises [Asana::Errors::ServerError] when there's a server problem.
       # Raises [Asana::Errors::APIError] when the API returns an unknown error.
       #
-      # rubocop:disable all
-      def handle(num_retries=0, &request)
+      # rubocop:disable Metrics/AbcSize
+      def handle(num_retries = 0, &request)
         request.call
       rescue Faraday::ClientError => e
         raise e unless e.response
+
         case e.response[:status]
-          when 400 then raise invalid_request(e.response)
-          when 401 then raise not_authorized(e.response)
-          when 402 then raise payment_required(e.response)
-          when 403 then raise forbidden(e.response)
-          when 404 then raise not_found(e.response)
-          when 412 then recover_response(e.response)
-          when 429 then raise rate_limit_enforced(e.response)
-          when 500 then raise server_error(e.response)
-          else raise api_error(e.response)
+        when 400 then raise invalid_request(e.response)
+        when 401 then raise not_authorized(e.response)
+        when 402 then raise payment_required(e.response)
+        when 403 then raise forbidden(e.response)
+        when 404 then raise not_found(e.response)
+        when 412 then recover_response(e.response)
+        when 429 then raise rate_limit_enforced(e.response)
+        when 500 then raise server_error(e.response)
+        else raise api_error(e.response)
         end
       # Retry for timeouts or 500s from Asana
       rescue Faraday::ServerError => e
-        if num_retries < MAX_RETRIES
-          handle(num_retries + 1, &request)
-        else
-          raise server_error(e.response)
-        end
+        raise server_error(e.response) unless num_retries < MAX_RETRIES
+
+        handle(num_retries + 1, &request)
       rescue Net::ReadTimeout => e
-        if num_retries < MAX_RETRIES
-          handle(num_retries + 1, &request)
-        else
-          raise e
-        end
+        raise e unless num_retries < MAX_RETRIES
+
+        handle(num_retries + 1, &request)
       end
-      # rubocop:enable all
+      # rubocop:enable Metrics/AbcSize
 
       # Internal: Returns an InvalidRequest exception including a list of
       # errors.
@@ -112,13 +109,15 @@ module Asana
 
       # Internal: Parser a response body from JSON.
       def body(response)
-        MultiJson.load(response[:body])
+        JSON.parse(response[:body])
       end
 
+      # rubocop:disable Style/OpenStructUse
       def recover_response(response)
         r = response.dup.tap { |res| res[:body] = body(response) }
         Response.new(OpenStruct.new(env: OpenStruct.new(r)))
       end
+      # rubocop:enable Style/OpenStructUse
     end
   end
 end
diff --git a/lib/asana/http_client/response.rb b/lib/asana/http_client/response.rb
index 502e9b5..9d6e2ac 100644
--- a/lib/asana/http_client/response.rb
+++ b/lib/asana/http_client/response.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 module Asana
   class HttpClient
     # Internal: Represents a response from the Asana API.
diff --git a/lib/asana/resource_includes/attachment_uploading.rb b/lib/asana/resource_includes/attachment_uploading.rb
index d454e9d..989b9d7 100644
--- a/lib/asana/resource_includes/attachment_uploading.rb
+++ b/lib/asana/resource_includes/attachment_uploading.rb
@@ -1,3 +1,7 @@
+# frozen_string_literal: true
+
+require 'faraday/multipart'
+
 module Asana
   module Resources
     # Internal: Mixin to add the ability to upload an attachment to a specific
@@ -11,8 +15,6 @@ module Asana
       # options  - [Hash] the request I/O options
       # data     - [Hash] extra attributes to post
       #
-      # rubocop:disable Metrics/AbcSize
-      # rubocop:disable Metrics/MethodLength
       def attach(filename: required('filename'),
                  mime: required('mime'),
                  io: nil, options: {}, **data)
@@ -21,9 +23,9 @@ module Asana
                    path = File.expand_path(filename)
                    raise ArgumentError, "file #{filename} doesn't exist" unless File.exist?(path)
 
-                   Faraday::FilePart.new(path, mime)
+                   Faraday::Multipart::FilePart.new(path, mime)
                  else
-                   Faraday::FilePart.new(io, mime, filename)
+                   Faraday::Multipart::FilePart.new(io, mime, filename)
                  end
 
         response = client.post("/#{self.class.plural_name}/#{gid}/attachments",
@@ -33,8 +35,6 @@ module Asana
 
         Attachment.new(parse(response).first, client: client)
       end
-      # rubocop:enable Metrics/MethodLength
-      # rubocop:enable Metrics/AbcSize
     end
   end
 end
diff --git a/lib/asana/resource_includes/collection.rb b/lib/asana/resource_includes/collection.rb
index 887288c..325ba32 100644
--- a/lib/asana/resource_includes/collection.rb
+++ b/lib/asana/resource_includes/collection.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 require_relative 'response_helper'
 
 module Asana
@@ -40,7 +42,7 @@ module Asana
       def last
         @elements.last
       end
-      
+
       # Public: Returns the size of the collection.
       def size
         to_a.size
@@ -49,9 +51,7 @@ module Asana
 
       # Public: Returns a String representation of the collection.
       def to_s
-        "#<Asana::Collection<#{@type}> " \
-          "[#{@elements.map(&:inspect).join(', ')}" +
-          (@next_page_data ? ', ...' : '') + ']>'
+        "#<Asana::Collection<#{@type}> [#{@elements.map(&:inspect).join(', ')}#{@next_page_data ? ', ...' : ''}]>"
       end
       alias inspect to_s
 
diff --git a/lib/asana/resource_includes/event.rb b/lib/asana/resource_includes/event.rb
index bc60f87..d794fb2 100644
--- a/lib/asana/resource_includes/event.rb
+++ b/lib/asana/resource_includes/event.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 require_relative 'events'
 
 module Asana
diff --git a/lib/asana/resource_includes/event_subscription.rb b/lib/asana/resource_includes/event_subscription.rb
index 452d682..37cf165 100644
--- a/lib/asana/resource_includes/event_subscription.rb
+++ b/lib/asana/resource_includes/event_subscription.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 require_relative 'events'
 
 module Asana
diff --git a/lib/asana/resource_includes/events.rb b/lib/asana/resource_includes/events.rb
index ee7bb56..ea283f1 100644
--- a/lib/asana/resource_includes/events.rb
+++ b/lib/asana/resource_includes/events.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 require_relative 'event'
 
 module Asana
@@ -89,13 +91,14 @@ module Asana
 
       # Internal: Returns the formatted params for the poll request.
       def params
-        { resource: @resource, sync: @sync }.reject { |_, v| v.nil? }
+        { resource: @resource, sync: @sync }.compact
       end
 
       # Internal: Executes a block if at least @wait seconds have passed since
       # @last_poll.
       def rate_limiting
         return if @last_poll && Time.now - @last_poll <= @wait
+
         yield.tap { @last_poll = Time.now }
       end
     end
diff --git a/lib/asana/resource_includes/registry.rb b/lib/asana/resource_includes/registry.rb
index 4dc398b..9091a04 100644
--- a/lib/asana/resource_includes/registry.rb
+++ b/lib/asana/resource_includes/registry.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 require_relative 'resource'
 require 'set'
 
diff --git a/lib/asana/resource_includes/resource.rb b/lib/asana/resource_includes/resource.rb
index d5929f2..c89e524 100644
--- a/lib/asana/resource_includes/resource.rb
+++ b/lib/asana/resource_includes/resource.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 require_relative 'registry'
 require_relative 'response_helper'
 
@@ -21,6 +23,7 @@ module Asana
       def refresh
         raise "#{self.class.name} does not respond to #find_by_id" unless \
           self.class.respond_to?(:find_by_id)
+
         self.class.find_by_id(client, gid)
       end
 
@@ -30,17 +33,17 @@ module Asana
       # Returns the value for the requested property.
       #
       # Raises a NoMethodError if the property doesn't exist.
-      def method_missing(m, *args)
-        super unless respond_to_missing?(m, *args)
-        cache(m, wrapped(to_h[m.to_s]))
+      def method_missing(method_name, *args)
+        super unless respond_to_missing?(method_name, *args)
+        cache(method_name, wrapped(to_h[method_name.to_s]))
       end
 
       # Internal: Guard for the method_missing proxy. Checks if the resource
       # actually has a specific piece of data at all.
       #
       # Returns true if the resource has the property, false otherwise.
-      def respond_to_missing?(m, *)
-        to_h.key?(m.to_s)
+      def respond_to_missing?(method_name, *)
+        to_h.key?(method_name.to_s)
       end
 
       # Public:
diff --git a/lib/asana/resource_includes/response_helper.rb b/lib/asana/resource_includes/response_helper.rb
index 680c86d..f2c0905 100644
--- a/lib/asana/resource_includes/response_helper.rb
+++ b/lib/asana/resource_includes/response_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 module Asana
   module Resources
     # Internal: A helper to make response body parsing easier.
diff --git a/lib/asana/resources.rb b/lib/asana/resources.rb
index 076617d..37400d9 100644
--- a/lib/asana/resources.rb
+++ b/lib/asana/resources.rb
@@ -1,11 +1,11 @@
+# frozen_string_literal: true
+
 require_relative 'resource_includes/resource'
 require_relative 'resource_includes/collection'
 
-Dir[File.join(File.dirname(__FILE__), 'resource_includes', '*.rb')]
-  .each { |resource| require resource }
+Dir[File.join(File.dirname(__FILE__), 'resource_includes', '*.rb')].sort.each { |resource| require resource }
 
-Dir[File.join(File.dirname(__FILE__), 'resources', '*.rb')]
-  .each { |resource| require resource }
+Dir[File.join(File.dirname(__FILE__), 'resources', '*.rb')].sort.each { |resource| require resource }
 
 module Asana
   # Public: Contains all the resources that the Asana API can return.
diff --git a/lib/asana/resources/audit_log_api.rb b/lib/asana/resources/audit_log_api.rb
new file mode 100644
index 0000000..b9eecaf
--- /dev/null
+++ b/lib/asana/resources/audit_log_api.rb
@@ -0,0 +1,42 @@
+require_relative 'gen/audit_log_api_base'
+
+module Asana
+  module Resources
+    class AuditLogAPI < AuditLogAPIBase
+
+
+      attr_reader :gid
+
+      attr_reader :actor
+
+      attr_reader :context
+      
+      attr_reader :api_authentication_method
+
+      attr_reader :client_ip_address
+      
+      attr_reader :context_type
+
+      attr_reader :oauth_app_name
+
+      attr_reader :user_agent
+
+      attr_reader :created_at
+
+      attr_reader :details
+
+      attr_reader :event_category
+
+      attr_reader :event_type
+
+      attr_reader :resource
+
+      class << self
+        # Returns the plural name of the resource.
+        def plural_name
+          'audit_log_apis'
+        end
+      end
+    end
+  end
+end
diff --git a/lib/asana/resources/gen/attachments_base.rb b/lib/asana/resources/gen/attachments_base.rb
index 50cac16..eccf659 100644
--- a/lib/asana/resources/gen/attachments_base.rb
+++ b/lib/asana/resources/gen/attachments_base.rb
@@ -36,18 +36,19 @@ module Asana
           Attachment.new(parse(client.get(path, options: options)).first, client: client)
         end
 
-        # Get attachments for a task
+        # Get attachments from an object
         #
-        # task_gid - [str]  (required) The task to operate on.
+
+        # parent - [str]  (required) Globally unique identifier for object to fetch statuses from. Must be a GID for a `project`, `project_brief`, or `task`.
         # options - [Hash] the request I/O options
         # > offset - [str]  Offset token. An offset to the next page returned by the API. A pagination request will return an offset token, which can be used as an input parameter to the next request. If an offset is not passed in, the API will return the first page of results. 'Note: You can only pass in an offset that was returned to you via a previously paginated request.'
         # > limit - [int]  Results per page. The number of objects to return per page. The value must be between 1 and 100.
         # > opt_fields - [list[str]]  Defines fields to return. Some requests return *compact* representations of objects in order to conserve resources and complete the request more efficiently. Other times requests return more information than you may need. This option allows you to list the exact set of fields that the API should be sure to return for the objects. The field names should be provided as paths, described below. The id of included objects will always be returned, regardless of the field options.
         # > opt_pretty - [bool]  Provides “pretty” output. Provides the response in a “pretty” format. In the case of JSON this means doing proper line breaking and indentation to make it readable. This will take extra time and increase the response size so it is advisable only to use this during debugging.
-        def get_attachments_for_task(client, task_gid: required("task_gid"), options: {})
-          path = "/tasks/{task_gid}/attachments"
-          path["{task_gid}"] = task_gid
-          Collection.new(parse(client.get(path, options: options)), type: Attachment, client: client)
+        def get_attachments_for_object(client, parent: nil, options: {})
+          path = "/attachments"
+          params = { parent: parent }.reject { |_,v| v.nil? || Array(v).empty? }
+          Collection.new(parse(client.get(path, params: params, options: options)), type: Attachment, client: client)
         end
 
       end
diff --git a/lib/asana/resources/gen/audit_log_api_base.rb b/lib/asana/resources/gen/audit_log_api_base.rb
index 00dbdce..024f817 100644
--- a/lib/asana/resources/gen/audit_log_api_base.rb
+++ b/lib/asana/resources/gen/audit_log_api_base.rb
@@ -17,7 +17,7 @@ module Asana
         # workspace_gid - [str]  (required) Globally unique identifier for the workspace or organization.
         # start_at - [datetime]  Filter to events created after this time (inclusive).
         # end_at - [datetime]  Filter to events created before this time (exclusive).
-        # event_type - [str]  Filter to events of this type. Refer to the [Supported AuditLogEvents](/docs/supported-auditlogevents) for a full list of values.
+        # event_type - [str]  Filter to events of this type. Refer to the [supported audit log events](/docs/audit-log-events#supported-audit-log-events) for a full list of values.
         # actor_type - [str]  Filter to events with an actor of this type. This only needs to be included if querying for actor types without an ID. If `actor_gid` is included, this should be excluded.
         # actor_gid - [str]  Filter to events triggered by the actor with this ID.
         # resource_gid - [str]  Filter to events with this resource ID.
diff --git a/lib/asana/resources/gen/goal_relationships_base.rb b/lib/asana/resources/gen/goal_relationships_base.rb
new file mode 100644
index 0000000..ec17f8c
--- /dev/null
+++ b/lib/asana/resources/gen/goal_relationships_base.rb
@@ -0,0 +1,83 @@
+### WARNING: This file is auto-generated by our OpenAPI spec. Do not
+### edit it manually.
+
+require_relative '../../resource_includes/response_helper'
+
+module Asana
+  module Resources
+    class GoalRelationshipsBase < Resource
+
+      def self.inherited(base)
+        Registry.register(base)
+      end
+
+      class << self
+        # Add a supporting goal relationship
+        #
+        # goal_gid - [str]  (required) Globally unique identifier for the goal.
+        # options - [Hash] the request I/O options
+        # > opt_fields - [list[str]]  Defines fields to return. Some requests return *compact* representations of objects in order to conserve resources and complete the request more efficiently. Other times requests return more information than you may need. This option allows you to list the exact set of fields that the API should be sure to return for the objects. The field names should be provided as paths, described below. The id of included objects will always be returned, regardless of the field options.
+        # > opt_pretty - [bool]  Provides “pretty” output. Provides the response in a “pretty” format. In the case of JSON this means doing proper line breaking and indentation to make it readable. This will take extra time and increase the response size so it is advisable only to use this during debugging.
+        # data - [Hash] the attributes to POST
+        def add_supporting_relationship(client, goal_gid: required("goal_gid"), options: {}, **data)
+          path = "/goals/{goal_gid}/addSupportingRelationship"
+          path["{goal_gid}"] = goal_gid
+          parse(client.post(path, body: data, options: options)).first
+        end
+
+        # Get a goal relationship
+        #
+        # goal_relationship_gid - [str]  (required) Globally unique identifier for the goal relationship.
+        # options - [Hash] the request I/O options
+        # > opt_fields - [list[str]]  Defines fields to return. Some requests return *compact* representations of objects in order to conserve resources and complete the request more efficiently. Other times requests return more information than you may need. This option allows you to list the exact set of fields that the API should be sure to return for the objects. The field names should be provided as paths, described below. The id of included objects will always be returned, regardless of the field options.
+        # > opt_pretty - [bool]  Provides “pretty” output. Provides the response in a “pretty” format. In the case of JSON this means doing proper line breaking and indentation to make it readable. This will take extra time and increase the response size so it is advisable only to use this during debugging.
+        def get_goal_relationship(client, goal_relationship_gid: required("goal_relationship_gid"), options: {})
+          path = "/goal_relationships/{goal_relationship_gid}"
+          path["{goal_relationship_gid}"] = goal_relationship_gid
+          parse(client.get(path, options: options)).first
+        end
+
+        # Get goal relationships
+        #
+
+        # supported_goal - [str]  (required) Globally unique identifier for the supported goal in the goal relationship.
+        # resource_subtype - [str]  If provided, filter to goal relationships with a given resource_subtype.
+        # options - [Hash] the request I/O options
+        # > opt_fields - [list[str]]  Defines fields to return. Some requests return *compact* representations of objects in order to conserve resources and complete the request more efficiently. Other times requests return more information than you may need. This option allows you to list the exact set of fields that the API should be sure to return for the objects. The field names should be provided as paths, described below. The id of included objects will always be returned, regardless of the field options.
+        # > opt_pretty - [bool]  Provides “pretty” output. Provides the response in a “pretty” format. In the case of JSON this means doing proper line breaking and indentation to make it readable. This will take extra time and increase the response size so it is advisable only to use this during debugging.
+        def get_goal_relationships(client, supported_goal: nil, resource_subtype: nil, options: {})
+          path = "/goal_relationships"
+          params = { supported_goal: supported_goal, resource_subtype: resource_subtype }.reject { |_,v| v.nil? || Array(v).empty? }
+          Collection.new(parse(client.get(path, params: params, options: options)), type: Resource, client: client)
+        end
+
+        # Removes a supporting goal relationship
+        #
+        # goal_gid - [str]  (required) Globally unique identifier for the goal.
+        # options - [Hash] the request I/O options
+        # > opt_fields - [list[str]]  Defines fields to return. Some requests return *compact* representations of objects in order to conserve resources and complete the request more efficiently. Other times requests return more information than you may need. This option allows you to list the exact set of fields that the API should be sure to return for the objects. The field names should be provided as paths, described below. The id of included objects will always be returned, regardless of the field options.
+        # > opt_pretty - [bool]  Provides “pretty” output. Provides the response in a “pretty” format. In the case of JSON this means doing proper line breaking and indentation to make it readable. This will take extra time and increase the response size so it is advisable only to use this during debugging.
+        # data - [Hash] the attributes to POST
+        def remove_supporting_relationship(client, goal_gid: required("goal_gid"), options: {}, **data)
+          path = "/goals/{goal_gid}/removeSupportingRelationship"
+          path["{goal_gid}"] = goal_gid
+          parse(client.post(path, body: data, options: options)).first
+        end
+
+        # Update a goal relationship
+        #
+        # goal_relationship_gid - [str]  (required) Globally unique identifier for the goal relationship.
+        # options - [Hash] the request I/O options
+        # > opt_fields - [list[str]]  Defines fields to return. Some requests return *compact* representations of objects in order to conserve resources and complete the request more efficiently. Other times requests return more information than you may need. This option allows you to list the exact set of fields that the API should be sure to return for the objects. The field names should be provided as paths, described below. The id of included objects will always be returned, regardless of the field options.
+        # > opt_pretty - [bool]  Provides “pretty” output. Provides the response in a “pretty” format. In the case of JSON this means doing proper line breaking and indentation to make it readable. This will take extra time and increase the response size so it is advisable only to use this during debugging.
+        # data - [Hash] the attributes to PUT
+        def update_goal_relationship(client, goal_relationship_gid: required("goal_relationship_gid"), options: {}, **data)
+          path = "/goal_relationships/{goal_relationship_gid}"
+          path["{goal_relationship_gid}"] = goal_relationship_gid
+          parse(client.put(path, body: data, options: options)).first
+        end
+
+      end
+    end
+  end
+end
diff --git a/lib/asana/resources/gen/goals_base.rb b/lib/asana/resources/gen/goals_base.rb
index f1f2e8c..385edfe 100644
--- a/lib/asana/resources/gen/goals_base.rb
+++ b/lib/asana/resources/gen/goals_base.rb
@@ -14,37 +14,14 @@ module Asana
       class << self
         # Add a collaborator to a goal
         #
-
+        # goal_gid - [str]  (required) Globally unique identifier for the goal.
         # options - [Hash] the request I/O options
         # > opt_fields - [list[str]]  Defines fields to return. Some requests return *compact* representations of objects in order to conserve resources and complete the request more efficiently. Other times requests return more information than you may need. This option allows you to list the exact set of fields that the API should be sure to return for the objects. The field names should be provided as paths, described below. The id of included objects will always be returned, regardless of the field options.
         # > opt_pretty - [bool]  Provides “pretty” output. Provides the response in a “pretty” format. In the case of JSON this means doing proper line breaking and indentation to make it readable. This will take extra time and increase the response size so it is advisable only to use this during debugging.
         # data - [Hash] the attributes to POST
-        def add_followers(client, options: {}, **data)
+        def add_followers(client, goal_gid: required("goal_gid"), options: {}, **data)
           path = "/goals/{goal_gid}/addFollowers"
-          parse(client.post(path, body: data, options: options)).first
-        end
-
-        # Add a subgoal to a parent goal
-        #
-
-        # options - [Hash] the request I/O options
-        # > opt_fields - [list[str]]  Defines fields to return. Some requests return *compact* representations of objects in order to conserve resources and complete the request more efficiently. Other times requests return more information than you may need. This option allows you to list the exact set of fields that the API should be sure to return for the objects. The field names should be provided as paths, described below. The id of included objects will always be returned, regardless of the field options.
-        # > opt_pretty - [bool]  Provides “pretty” output. Provides the response in a “pretty” format. In the case of JSON this means doing proper line breaking and indentation to make it readable. This will take extra time and increase the response size so it is advisable only to use this during debugging.
-        # data - [Hash] the attributes to POST
-        def add_subgoal(client, options: {}, **data)
-          path = "/goals/{goal_gid}/addSubgoal"
-          parse(client.post(path, body: data, options: options)).first
-        end
-
-        # Add a project/portfolio as supporting work for a goal.
-        #
-
-        # options - [Hash] the request I/O options
-        # > opt_fields - [list[str]]  Defines fields to return. Some requests return *compact* representations of objects in order to conserve resources and complete the request more efficiently. Other times requests return more information than you may need. This option allows you to list the exact set of fields that the API should be sure to return for the objects. The field names should be provided as paths, described below. The id of included objects will always be returned, regardless of the field options.
-        # > opt_pretty - [bool]  Provides “pretty” output. Provides the response in a “pretty” format. In the case of JSON this means doing proper line breaking and indentation to make it readable. This will take extra time and increase the response size so it is advisable only to use this during debugging.
-        # data - [Hash] the attributes to POST
-        def add_supporting_work_for_goal(client, options: {}, **data)
-          path = "/goals/{goal_gid}/addSupportingWork"
+          path["{goal_gid}"] = goal_gid
           parse(client.post(path, body: data, options: options)).first
         end
 
@@ -64,13 +41,14 @@ module Asana
 
         # Create a goal metric
         #
-
+        # goal_gid - [str]  (required) Globally unique identifier for the goal.
         # options - [Hash] the request I/O options
         # > opt_fields - [list[str]]  Defines fields to return. Some requests return *compact* representations of objects in order to conserve resources and complete the request more efficiently. Other times requests return more information than you may need. This option allows you to list the exact set of fields that the API should be sure to return for the objects. The field names should be provided as paths, described below. The id of included objects will always be returned, regardless of the field options.
         # > opt_pretty - [bool]  Provides “pretty” output. Provides the response in a “pretty” format. In the case of JSON this means doing proper line breaking and indentation to make it readable. This will take extra time and increase the response size so it is advisable only to use this during debugging.
         # data - [Hash] the attributes to POST
-        def create_goal_metric(client, options: {}, **data)
+        def create_goal_metric(client, goal_gid: required("goal_gid"), options: {}, **data)
           path = "/goals/{goal_gid}/setMetric"
+          path["{goal_gid}"] = goal_gid
           parse(client.post(path, body: data, options: options)).first
         end
 
@@ -120,73 +98,29 @@ module Asana
 
         # Get parent goals from a goal
         #
-
+        # goal_gid - [str]  (required) Globally unique identifier for the goal.
         # options - [Hash] the request I/O options
         # > opt_fields - [list[str]]  Defines fields to return. Some requests return *compact* representations of objects in order to conserve resources and complete the request more efficiently. Other times requests return more information than you may need. This option allows you to list the exact set of fields that the API should be sure to return for the objects. The field names should be provided as paths, described below. The id of included objects will always be returned, regardless of the field options.
         # > opt_pretty - [bool]  Provides “pretty” output. Provides the response in a “pretty” format. In the case of JSON this means doing proper line breaking and indentation to make it readable. This will take extra time and increase the response size so it is advisable only to use this during debugging.
-        def get_parent_goals_for_goal(client, options: {})
+        def get_parent_goals_for_goal(client, goal_gid: required("goal_gid"), options: {})
           path = "/goals/{goal_gid}/parentGoals"
-          Collection.new(parse(client.get(path, options: options)), type: Resource, client: client)
-        end
-
-        # Get subgoals from a goal
-        #
-
-        # options - [Hash] the request I/O options
-        # > opt_fields - [list[str]]  Defines fields to return. Some requests return *compact* representations of objects in order to conserve resources and complete the request more efficiently. Other times requests return more information than you may need. This option allows you to list the exact set of fields that the API should be sure to return for the objects. The field names should be provided as paths, described below. The id of included objects will always be returned, regardless of the field options.
-        # > opt_pretty - [bool]  Provides “pretty” output. Provides the response in a “pretty” format. In the case of JSON this means doing proper line breaking and indentation to make it readable. This will take extra time and increase the response size so it is advisable only to use this during debugging.
-        def get_subgoals_for_goal(client, options: {})
-          path = "/goals/{goal_gid}/subgoals"
+          path["{goal_gid}"] = goal_gid
           Collection.new(parse(client.get(path, options: options)), type: Resource, client: client)
         end
 
         # Remove a collaborator from a goal
         #
-
+        # goal_gid - [str]  (required) Globally unique identifier for the goal.
         # options - [Hash] the request I/O options
         # > opt_fields - [list[str]]  Defines fields to return. Some requests return *compact* representations of objects in order to conserve resources and complete the request more efficiently. Other times requests return more information than you may need. This option allows you to list the exact set of fields that the API should be sure to return for the objects. The field names should be provided as paths, described below. The id of included objects will always be returned, regardless of the field options.
         # > opt_pretty - [bool]  Provides “pretty” output. Provides the response in a “pretty” format. In the case of JSON this means doing proper line breaking and indentation to make it readable. This will take extra time and increase the response size so it is advisable only to use this during debugging.
         # data - [Hash] the attributes to POST
-        def remove_followers(client, options: {}, **data)
+        def remove_followers(client, goal_gid: required("goal_gid"), options: {}, **data)
           path = "/goals/{goal_gid}/removeFollowers"
+          path["{goal_gid}"] = goal_gid
           parse(client.post(path, body: data, options: options)).first
         end
 
-        # Remove a subgoal from a goal
-        #
-
-        # options - [Hash] the request I/O options
-        # > opt_fields - [list[str]]  Defines fields to return. Some requests return *compact* representations of objects in order to conserve resources and complete the request more efficiently. Other times requests return more information than you may need. This option allows you to list the exact set of fields that the API should be sure to return for the objects. The field names should be provided as paths, described below. The id of included objects will always be returned, regardless of the field options.
-        # > opt_pretty - [bool]  Provides “pretty” output. Provides the response in a “pretty” format. In the case of JSON this means doing proper line breaking and indentation to make it readable. This will take extra time and increase the response size so it is advisable only to use this during debugging.
-        # data - [Hash] the attributes to POST
-        def remove_subgoal(client, options: {}, **data)
-          path = "/goals/{goal_gid}/removeSubgoal"
-          parse(client.post(path, body: data, options: options)).first
-        end
-
-        # Remove a project/portfolio as supporting work for a goal.
-        #
-
-        # options - [Hash] the request I/O options
-        # > opt_fields - [list[str]]  Defines fields to return. Some requests return *compact* representations of objects in order to conserve resources and complete the request more efficiently. Other times requests return more information than you may need. This option allows you to list the exact set of fields that the API should be sure to return for the objects. The field names should be provided as paths, described below. The id of included objects will always be returned, regardless of the field options.
-        # > opt_pretty - [bool]  Provides “pretty” output. Provides the response in a “pretty” format. In the case of JSON this means doing proper line breaking and indentation to make it readable. This will take extra time and increase the response size so it is advisable only to use this during debugging.
-        # data - [Hash] the attributes to POST
-        def remove_supporting_work_for_goal(client, options: {}, **data)
-          path = "/goals/{goal_gid}/removeSupportingWork"
-          parse(client.post(path, body: data, options: options)).first
-        end
-
-        # Get supporting work from a goal
-        #
-
-        # options - [Hash] the request I/O options
-        # > opt_fields - [list[str]]  Defines fields to return. Some requests return *compact* representations of objects in order to conserve resources and complete the request more efficiently. Other times requests return more information than you may need. This option allows you to list the exact set of fields that the API should be sure to return for the objects. The field names should be provided as paths, described below. The id of included objects will always be returned, regardless of the field options.
-        # > opt_pretty - [bool]  Provides “pretty” output. Provides the response in a “pretty” format. In the case of JSON this means doing proper line breaking and indentation to make it readable. This will take extra time and increase the response size so it is advisable only to use this during debugging.
-        def supporting_work(client, options: {})
-          path = "/goals/{goal_gid}/supportingWork"
-          Collection.new(parse(client.get(path, options: options)), type: Project, client: client)
-        end
-
         # Update a goal
         #
         # goal_gid - [str]  (required) Globally unique identifier for the goal.
@@ -202,13 +136,14 @@ module Asana
 
         # Update a goal metric
         #
-
+        # goal_gid - [str]  (required) Globally unique identifier for the goal.
         # options - [Hash] the request I/O options
         # > opt_fields - [list[str]]  Defines fields to return. Some requests return *compact* representations of objects in order to conserve resources and complete the request more efficiently. Other times requests return more information than you may need. This option allows you to list the exact set of fields that the API should be sure to return for the objects. The field names should be provided as paths, described below. The id of included objects will always be returned, regardless of the field options.
         # > opt_pretty - [bool]  Provides “pretty” output. Provides the response in a “pretty” format. In the case of JSON this means doing proper line breaking and indentation to make it readable. This will take extra time and increase the response size so it is advisable only to use this during debugging.
         # data - [Hash] the attributes to POST
-        def update_goal_metric(client, options: {}, **data)
+        def update_goal_metric(client, goal_gid: required("goal_gid"), options: {}, **data)
           path = "/goals/{goal_gid}/setMetricCurrentValue"
+          path["{goal_gid}"] = goal_gid
           parse(client.post(path, body: data, options: options)).first
         end
 
diff --git a/lib/asana/resources/gen/memberships_base.rb b/lib/asana/resources/gen/memberships_base.rb
new file mode 100644
index 0000000..4704bfe
--- /dev/null
+++ b/lib/asana/resources/gen/memberships_base.rb
@@ -0,0 +1,71 @@
+### WARNING: This file is auto-generated by our OpenAPI spec. Do not
+### edit it manually.
+
+require_relative '../../resource_includes/response_helper'
+
+module Asana
+  module Resources
+    class MembershipsBase < Resource
+
+      def self.inherited(base)
+        Registry.register(base)
+      end
+
+      class << self
+        # Create a membership
+        #
+
+        # options - [Hash] the request I/O options
+        # > opt_fields - [list[str]]  Defines fields to return. Some requests return *compact* representations of objects in order to conserve resources and complete the request more efficiently. Other times requests return more information than you may need. This option allows you to list the exact set of fields that the API should be sure to return for the objects. The field names should be provided as paths, described below. The id of included objects will always be returned, regardless of the field options.
+        # > opt_pretty - [bool]  Provides “pretty” output. Provides the response in a “pretty” format. In the case of JSON this means doing proper line breaking and indentation to make it readable. This will take extra time and increase the response size so it is advisable only to use this during debugging.
+        # data - [Hash] the attributes to POST
+        def create_membership(client, options: {}, **data)
+          path = "/memberships"
+          parse(client.post(path, body: data, options: options)).first
+        end
+
+        # Delete a membership
+        #
+        # membership_gid - [str]  (required) Globally unique identifier for the membership.
+        # options - [Hash] the request I/O options
+        # > opt_fields - [list[str]]  Defines fields to return. Some requests return *compact* representations of objects in order to conserve resources and complete the request more efficiently. Other times requests return more information than you may need. This option allows you to list the exact set of fields that the API should be sure to return for the objects. The field names should be provided as paths, described below. The id of included objects will always be returned, regardless of the field options.
+        # > opt_pretty - [bool]  Provides “pretty” output. Provides the response in a “pretty” format. In the case of JSON this means doing proper line breaking and indentation to make it readable. This will take extra time and increase the response size so it is advisable only to use this during debugging.
+        def delete_membership(client, membership_gid: required("membership_gid"), options: {})
+          path = "/memberships/{membership_gid}"
+          path["{membership_gid}"] = membership_gid
+          parse(client.delete(path, options: options)).first
+        end
+
+        # Get multiple memberships
+        #
+
+        # parent - [str]  (required) Globally unique identifier for `project`, `portfolio`,   `team`, `goal`, and `workspace`.
+        # member - [str]  Globally unique identifier for `team` and `user`.
+        # options - [Hash] the request I/O options
+        # > offset - [str]  Offset token. An offset to the next page returned by the API. A pagination request will return an offset token, which can be used as an input parameter to the next request. If an offset is not passed in, the API will return the first page of results. 'Note: You can only pass in an offset that was returned to you via a previously paginated request.'
+        # > limit - [int]  Pagination limit for the request.
+        # > opt_fields - [list[str]]  Defines fields to return. Some requests return *compact* representations of objects in order to conserve resources and complete the request more efficiently. Other times requests return more information than you may need. This option allows you to list the exact set of fields that the API should be sure to return for the objects. The field names should be provided as paths, described below. The id of included objects will always be returned, regardless of the field options.
+        # > opt_pretty - [bool]  Provides “pretty” output. Provides the response in a “pretty” format. In the case of JSON this means doing proper line breaking and indentation to make it readable. This will take extra time and increase the response size so it is advisable only to use this during debugging.
+        def get_memberships(client, parent: nil, member: nil, options: {})
+          path = "/memberships"
+          params = { parent: parent, member: member }.reject { |_,v| v.nil? || Array(v).empty? }
+          Collection.new(parse(client.get(path, params: params, options: options)), type: Resource, client: client)
+        end
+
+        # Update a membership
+        #
+        # membership_gid - [str]  (required) Globally unique identifier for the membership.
+        # options - [Hash] the request I/O options
+        # > opt_fields - [list[str]]  Defines fields to return. Some requests return *compact* representations of objects in order to conserve resources and complete the request more efficiently. Other times requests return more information than you may need. This option allows you to list the exact set of fields that the API should be sure to return for the objects. The field names should be provided as paths, described below. The id of included objects will always be returned, regardless of the field options.
+        # > opt_pretty - [bool]  Provides “pretty” output. Provides the response in a “pretty” format. In the case of JSON this means doing proper line breaking and indentation to make it readable. This will take extra time and increase the response size so it is advisable only to use this during debugging.
+        # data - [Hash] the attributes to PUT
+        def update_membership(client, membership_gid: required("membership_gid"), options: {}, **data)
+          path = "/memberships/{membership_gid}"
+          path["{membership_gid}"] = membership_gid
+          parse(client.put(path, body: data, options: options)).first
+        end
+
+      end
+    end
+  end
+end
diff --git a/lib/asana/resources/gen/portfolios_base.rb b/lib/asana/resources/gen/portfolios_base.rb
index b27ecb3..c181063 100644
--- a/lib/asana/resources/gen/portfolios_base.rb
+++ b/lib/asana/resources/gen/portfolios_base.rb
@@ -21,7 +21,7 @@ module Asana
         def add_custom_field_setting_for_portfolio(client, portfolio_gid: required("portfolio_gid"), options: {}, **data)
           path = "/portfolios/{portfolio_gid}/addCustomFieldSetting"
           path["{portfolio_gid}"] = portfolio_gid
-          parse(client.post(path, body: data, options: options)).first
+          CustomFieldSetting.new(parse(client.post(path, body: data, options: options)).first, client: client)
         end
 
         # Add a portfolio item
@@ -47,7 +47,7 @@ module Asana
         def add_members_for_portfolio(client, portfolio_gid: required("portfolio_gid"), options: {}, **data)
           path = "/portfolios/{portfolio_gid}/addMembers"
           path["{portfolio_gid}"] = portfolio_gid
-          parse(client.post(path, body: data, options: options)).first
+          Portfolio.new(parse(client.post(path, body: data, options: options)).first, client: client)
         end
 
         # Create a portfolio
@@ -151,7 +151,7 @@ module Asana
         def remove_members_for_portfolio(client, portfolio_gid: required("portfolio_gid"), options: {}, **data)
           path = "/portfolios/{portfolio_gid}/removeMembers"
           path["{portfolio_gid}"] = portfolio_gid
-          parse(client.post(path, body: data, options: options)).first
+          Portfolio.new(parse(client.post(path, body: data, options: options)).first, client: client)
         end
 
         # Update a portfolio
diff --git a/lib/asana/resources/gen/projects_base.rb b/lib/asana/resources/gen/projects_base.rb
index 63a5ab0..d834386 100644
--- a/lib/asana/resources/gen/projects_base.rb
+++ b/lib/asana/resources/gen/projects_base.rb
@@ -34,7 +34,7 @@ module Asana
         def add_followers_for_project(client, project_gid: required("project_gid"), options: {}, **data)
           path = "/projects/{project_gid}/addFollowers"
           path["{project_gid}"] = project_gid
-          parse(client.post(path, body: data, options: options)).first
+          Project.new(parse(client.post(path, body: data, options: options)).first, client: client)
         end
 
         # Add users to a project
@@ -47,7 +47,7 @@ module Asana
         def add_members_for_project(client, project_gid: required("project_gid"), options: {}, **data)
           path = "/projects/{project_gid}/addMembers"
           path["{project_gid}"] = project_gid
-          parse(client.post(path, body: data, options: options)).first
+          Project.new(parse(client.post(path, body: data, options: options)).first, client: client)
         end
 
         # Create a project
@@ -237,7 +237,7 @@ module Asana
         def remove_followers_for_project(client, project_gid: required("project_gid"), options: {}, **data)
           path = "/projects/{project_gid}/removeFollowers"
           path["{project_gid}"] = project_gid
-          parse(client.post(path, body: data, options: options)).first
+          Project.new(parse(client.post(path, body: data, options: options)).first, client: client)
         end
 
         # Remove users from a project
@@ -250,7 +250,7 @@ module Asana
         def remove_members_for_project(client, project_gid: required("project_gid"), options: {}, **data)
           path = "/projects/{project_gid}/removeMembers"
           path["{project_gid}"] = project_gid
-          parse(client.post(path, body: data, options: options)).first
+          Project.new(parse(client.post(path, body: data, options: options)).first, client: client)
         end
 
         # Update a project
diff --git a/lib/asana/resources/gen/status_updates_base.rb b/lib/asana/resources/gen/status_updates_base.rb
index 9d99158..4463be6 100644
--- a/lib/asana/resources/gen/status_updates_base.rb
+++ b/lib/asana/resources/gen/status_updates_base.rb
@@ -53,7 +53,7 @@ module Asana
         # Get status updates from an object
         #
 
-        # parent - [str]  (required) Globally unique identifier for object to fetch statuses from.
+        # parent - [str]  (required) Globally unique identifier for object to fetch statuses from. Must be a GID for a project, portfolio, or goal.
         # created_since - [datetime]  Only return statuses that have been created since the given time.
         # options - [Hash] the request I/O options
         # > offset - [str]  Offset token. An offset to the next page returned by the API. A pagination request will return an offset token, which can be used as an input parameter to the next request. If an offset is not passed in, the API will return the first page of results. 'Note: You can only pass in an offset that was returned to you via a previously paginated request.'
diff --git a/lib/asana/resources/gen/tasks_base.rb b/lib/asana/resources/gen/tasks_base.rb
index 3877646..125c3da 100644
--- a/lib/asana/resources/gen/tasks_base.rb
+++ b/lib/asana/resources/gen/tasks_base.rb
@@ -35,7 +35,7 @@ module Asana
         def add_dependents_for_task(client, task_gid: required("task_gid"), options: {}, **data)
           path = "/tasks/{task_gid}/addDependents"
           path["{task_gid}"] = task_gid
-          Collection.new(parse(client.post(path, body: data, options: options)), type: Task, client: client)
+          parse(client.post(path, body: data, options: options)).first
         end
 
         # Add followers to a task
@@ -48,7 +48,7 @@ module Asana
         def add_followers_for_task(client, task_gid: required("task_gid"), options: {}, **data)
           path = "/tasks/{task_gid}/addFollowers"
           path["{task_gid}"] = task_gid
-          parse(client.post(path, body: data, options: options)).first
+          Task.new(parse(client.post(path, body: data, options: options)).first, client: client)
         end
 
         # Add a project to a task
@@ -184,9 +184,9 @@ module Asana
         # Get multiple tasks
         #
 
-        # assignee - [str]  The assignee to filter tasks on. *Note: If you specify `assignee`, you must also specify the `workspace` to filter on.*
+        # assignee - [str]  The assignee to filter tasks on. If searching for unassigned tasks, assignee.any = null can be specified. *Note: If you specify `assignee`, you must also specify the `workspace` to filter on.*
         # project - [str]  The project to filter tasks on.
-        # section - [str]  The section to filter tasks on. *Note: Currently, this is only supported in board views.*
+        # section - [str]  The section to filter tasks on.
         # workspace - [str]  The workspace to filter tasks on. *Note: If you specify `workspace`, you must also specify the `assignee` to filter on.*
         # completed_since - [datetime]  Only return tasks that are either incomplete or that have been completed since this time.
         # modified_since - [datetime]  Only return tasks that have been modified since the given time.  *Note: A task is considered “modified” if any of its properties change, or associations between it and other objects are modified (e.g.  a task being added to a project). A task is not considered modified just because another object it is associated with (e.g. a subtask) is modified. Actions that count as modifying the task include assigning, renaming, completing, and adding stories.*
@@ -204,15 +204,17 @@ module Asana
         # Get tasks from a project
         #
         # project_gid - [str]  (required) Globally unique identifier for the project.
+        # completed_since - [str]  Only return tasks that are either incomplete or that have been completed since this time. Accepts a date-time string or the keyword *now*. 
         # options - [Hash] the request I/O options
         # > offset - [str]  Offset token. An offset to the next page returned by the API. A pagination request will return an offset token, which can be used as an input parameter to the next request. If an offset is not passed in, the API will return the first page of results. 'Note: You can only pass in an offset that was returned to you via a previously paginated request.'
         # > limit - [int]  Results per page. The number of objects to return per page. The value must be between 1 and 100.
         # > opt_fields - [list[str]]  Defines fields to return. Some requests return *compact* representations of objects in order to conserve resources and complete the request more efficiently. Other times requests return more information than you may need. This option allows you to list the exact set of fields that the API should be sure to return for the objects. The field names should be provided as paths, described below. The id of included objects will always be returned, regardless of the field options.
         # > opt_pretty - [bool]  Provides “pretty” output. Provides the response in a “pretty” format. In the case of JSON this means doing proper line breaking and indentation to make it readable. This will take extra time and increase the response size so it is advisable only to use this during debugging.
-        def get_tasks_for_project(client, project_gid: required("project_gid"), options: {})
+        def get_tasks_for_project(client, project_gid: required("project_gid"), completed_since: nil, options: {})
           path = "/projects/{project_gid}/tasks"
           path["{project_gid}"] = project_gid
-          Collection.new(parse(client.get(path, options: options)), type: Task, client: client)
+          params = { completed_since: completed_since }.reject { |_,v| v.nil? || Array(v).empty? }
+          Collection.new(parse(client.get(path, params: params, options: options)), type: Task, client: client)
         end
 
         # Get tasks from a section
@@ -269,7 +271,7 @@ module Asana
         def remove_dependencies_for_task(client, task_gid: required("task_gid"), options: {}, **data)
           path = "/tasks/{task_gid}/removeDependencies"
           path["{task_gid}"] = task_gid
-          Collection.new(parse(client.post(path, body: data, options: options)), type: Resource, client: client)
+          parse(client.post(path, body: data, options: options)).first
         end
 
         # Unlink dependents from a task
@@ -282,7 +284,7 @@ module Asana
         def remove_dependents_for_task(client, task_gid: required("task_gid"), options: {}, **data)
           path = "/tasks/{task_gid}/removeDependents"
           path["{task_gid}"] = task_gid
-          Collection.new(parse(client.post(path, body: data, options: options)), type: Resource, client: client)
+          parse(client.post(path, body: data, options: options)).first
         end
 
         # Remove followers from a task
@@ -295,7 +297,7 @@ module Asana
         def remove_follower_for_task(client, task_gid: required("task_gid"), options: {}, **data)
           path = "/tasks/{task_gid}/removeFollowers"
           path["{task_gid}"] = task_gid
-          parse(client.post(path, body: data, options: options)).first
+          Task.new(parse(client.post(path, body: data, options: options)).first, client: client)
         end
 
         # Remove a project from a task
diff --git a/lib/asana/resources/gen/teams_base.rb b/lib/asana/resources/gen/teams_base.rb
index 80bec6a..0a47312 100644
--- a/lib/asana/resources/gen/teams_base.rb
+++ b/lib/asana/resources/gen/teams_base.rb
@@ -53,20 +53,6 @@ module Asana
           Team.new(parse(client.get(path, options: options)).first, client: client)
         end
 
-        # Get teams in an organization
-        #
-        # workspace_gid - [str]  (required) Globally unique identifier for the workspace or organization.
-        # options - [Hash] the request I/O options
-        # > offset - [str]  Offset token. An offset to the next page returned by the API. A pagination request will return an offset token, which can be used as an input parameter to the next request. If an offset is not passed in, the API will return the first page of results. 'Note: You can only pass in an offset that was returned to you via a previously paginated request.'
-        # > limit - [int]  Results per page. The number of objects to return per page. The value must be between 1 and 100.
-        # > opt_fields - [list[str]]  Defines fields to return. Some requests return *compact* representations of objects in order to conserve resources and complete the request more efficiently. Other times requests return more information than you may need. This option allows you to list the exact set of fields that the API should be sure to return for the objects. The field names should be provided as paths, described below. The id of included objects will always be returned, regardless of the field options.
-        # > opt_pretty - [bool]  Provides “pretty” output. Provides the response in a “pretty” format. In the case of JSON this means doing proper line breaking and indentation to make it readable. This will take extra time and increase the response size so it is advisable only to use this during debugging.
-        def get_teams_for_organization(client, workspace_gid: required("workspace_gid"), options: {})
-          path = "/organizations/{workspace_gid}/teams"
-          path["{workspace_gid}"] = workspace_gid
-          Collection.new(parse(client.get(path, options: options)), type: Team, client: client)
-        end
-
         # Get teams for a user
         #
         # user_gid - [str]  (required) A string identifying a user. This can either be the string \"me\", an email, or the gid of a user.
@@ -83,6 +69,20 @@ module Asana
           Collection.new(parse(client.get(path, params: params, options: options)), type: Team, client: client)
         end
 
+        # Get teams in a workspace
+        #
+        # workspace_gid - [str]  (required) Globally unique identifier for the workspace or organization.
+        # options - [Hash] the request I/O options
+        # > offset - [str]  Offset token. An offset to the next page returned by the API. A pagination request will return an offset token, which can be used as an input parameter to the next request. If an offset is not passed in, the API will return the first page of results. 'Note: You can only pass in an offset that was returned to you via a previously paginated request.'
+        # > limit - [int]  Results per page. The number of objects to return per page. The value must be between 1 and 100.
+        # > opt_fields - [list[str]]  Defines fields to return. Some requests return *compact* representations of objects in order to conserve resources and complete the request more efficiently. Other times requests return more information than you may need. This option allows you to list the exact set of fields that the API should be sure to return for the objects. The field names should be provided as paths, described below. The id of included objects will always be returned, regardless of the field options.
+        # > opt_pretty - [bool]  Provides “pretty” output. Provides the response in a “pretty” format. In the case of JSON this means doing proper line breaking and indentation to make it readable. This will take extra time and increase the response size so it is advisable only to use this during debugging.
+        def get_teams_for_workspace(client, workspace_gid: required("workspace_gid"), options: {})
+          path = "/workspaces/{workspace_gid}/teams"
+          path["{workspace_gid}"] = workspace_gid
+          Collection.new(parse(client.get(path, options: options)), type: Team, client: client)
+        end
+
         # Remove a user from a team
         #
         # team_gid - [str]  (required) Globally unique identifier for the team.
@@ -96,6 +96,20 @@ module Asana
           parse(client.post(path, body: data, options: options)).first
         end
 
+        # Update a team
+        #
+
+        # options - [Hash] the request I/O options
+        # > offset - [str]  Offset token. An offset to the next page returned by the API. A pagination request will return an offset token, which can be used as an input parameter to the next request. If an offset is not passed in, the API will return the first page of results. 'Note: You can only pass in an offset that was returned to you via a previously paginated request.'
+        # > limit - [int]  Results per page. The number of objects to return per page. The value must be between 1 and 100.
+        # > opt_fields - [list[str]]  Defines fields to return. Some requests return *compact* representations of objects in order to conserve resources and complete the request more efficiently. Other times requests return more information than you may need. This option allows you to list the exact set of fields that the API should be sure to return for the objects. The field names should be provided as paths, described below. The id of included objects will always be returned, regardless of the field options.
+        # > opt_pretty - [bool]  Provides “pretty” output. Provides the response in a “pretty” format. In the case of JSON this means doing proper line breaking and indentation to make it readable. This will take extra time and increase the response size so it is advisable only to use this during debugging.
+        # data - [Hash] the attributes to PUT
+        def update_team(client, options: {}, **data)
+          path = "/teams"
+          Team.new(parse(client.put(path, body: data, options: options)).first, client: client)
+        end
+
       end
     end
   end
diff --git a/lib/asana/resources/gen/typeahead_base.rb b/lib/asana/resources/gen/typeahead_base.rb
index 86ea2a4..83a3685 100644
--- a/lib/asana/resources/gen/typeahead_base.rb
+++ b/lib/asana/resources/gen/typeahead_base.rb
@@ -17,7 +17,7 @@ module Asana
         # workspace_gid - [str]  (required) Globally unique identifier for the workspace or organization.
         # resource_type - [str]  (required) The type of values the typeahead should return. You can choose from one of the following: `custom_field`, `project`, `project_template`, `portfolio`, `tag`, `task`, and `user`. Note that unlike in the names of endpoints, the types listed here are in singular form (e.g. `task`). Using multiple types is not yet supported.
         # type - [str]  *Deprecated: new integrations should prefer the resource_type field.*
-        # query - [str]  The string that will be used to search for relevant objects. If an empty string is passed in, the API will currently return an empty result set.
+        # query - [str]  The string that will be used to search for relevant objects. If an empty string is passed in, the API will return results.
         # count - [int]  The number of results to return. The default is 20 if this parameter is omitted, with a minimum of 1 and a maximum of 100. If there are fewer results found than requested, all will be returned.
         # options - [Hash] the request I/O options
         # > opt_fields - [list[str]]  Defines fields to return. Some requests return *compact* representations of objects in order to conserve resources and complete the request more efficiently. Other times requests return more information than you may need. This option allows you to list the exact set of fields that the API should be sure to return for the objects. The field names should be provided as paths, described below. The id of included objects will always be returned, regardless of the field options.
diff --git a/lib/asana/resources/gen/workspaces_base.rb b/lib/asana/resources/gen/workspaces_base.rb
index 30f7c59..d4a4558 100644
--- a/lib/asana/resources/gen/workspaces_base.rb
+++ b/lib/asana/resources/gen/workspaces_base.rb
@@ -22,7 +22,7 @@ module Asana
         def add_user_for_workspace(client, workspace_gid: required("workspace_gid"), options: {}, **data)
           path = "/workspaces/{workspace_gid}/addUser"
           path["{workspace_gid}"] = workspace_gid
-          User.new(parse(client.post(path, body: data, options: options)).first, client: client)
+          parse(client.post(path, body: data, options: options)).first
         end
 
         # Get a workspace
diff --git a/lib/asana/resources/goal.rb b/lib/asana/resources/goal.rb
new file mode 100644
index 0000000..f5dd3e4
--- /dev/null
+++ b/lib/asana/resources/goal.rb
@@ -0,0 +1,54 @@
+require_relative 'gen/goals_base'
+
+module Asana
+  module Resources
+    class Goal < GoalsBase
+
+
+      attr_reader :gid
+
+      attr_reader :resource_type
+
+      attr_reader :due_on
+      
+      attr_reader :html_notes
+
+      attr_reader :is_workspace_level
+
+      attr_reader :liked
+
+      attr_reader :name
+
+      attr_reader :notes
+
+      attr_reader :start_on
+
+      attr_reader :status
+
+      attr_reader :current_status_update
+
+      attr_reader :followers
+
+      attr_reader :likes
+      
+      attr_reader :metric
+      
+      attr_reader :num_likes
+
+      attr_reader :owner
+
+      attr_reader :team
+
+      attr_reader :time_period
+
+      attr_reader :workspace
+
+      class << self
+        # Returns the plural name of the resource.
+        def plural_name
+          'goals'
+        end
+      end
+    end
+  end
+end
diff --git a/lib/asana/resources/goal_relationship.rb b/lib/asana/resources/goal_relationship.rb
new file mode 100644
index 0000000..9fc50c9
--- /dev/null
+++ b/lib/asana/resources/goal_relationship.rb
@@ -0,0 +1,32 @@
+require_relative 'gen/goal_relationships_base'
+
+module Asana
+  module Resources
+    class GoalRelationship < GoalRelationshipsBase
+
+
+      attr_reader :gid
+
+      attr_reader :resource_type
+
+      attr_reader :contribution_weight
+      
+      attr_reader :resource_subtype
+
+      attr_reader :supported_goal
+
+      attr_reader :owner
+
+      attr_reader :supporting_resource
+
+      attr_reader :supporting_resource
+
+      class << self
+        # Returns the plural name of the resource.
+        def plural_name
+          'goal_relationships'
+        end
+      end
+    end
+  end
+end
diff --git a/lib/asana/resources/membership.rb b/lib/asana/resources/membership.rb
new file mode 100644
index 0000000..328af0a
--- /dev/null
+++ b/lib/asana/resources/membership.rb
@@ -0,0 +1,20 @@
+require_relative 'gen/memberships_base'
+
+module Asana
+  module Resources
+    class Membership < MembershipsBase
+
+
+      attr_reader :gid
+
+      attr_reader :resource_type
+
+      class << self
+        # Returns the plural name of the resource.
+        def plural_name
+          'memberships'
+        end
+      end
+    end
+  end
+end
diff --git a/lib/asana/resources/project_brief.rb b/lib/asana/resources/project_brief.rb
new file mode 100644
index 0000000..9b7c535
--- /dev/null
+++ b/lib/asana/resources/project_brief.rb
@@ -0,0 +1,30 @@
+require_relative 'gen/project_briefs_base'
+
+module Asana
+  module Resources
+    class ProjectBrief < ProjectBriefsBase
+
+
+      attr_reader :gid
+
+      attr_reader :resource_type
+
+      attr_reader :html_text
+      
+      attr_reader :title
+
+      attr_reader :permalink_url
+
+      attr_reader :project
+
+      attr_reader :text
+
+      class << self
+        # Returns the plural name of the resource.
+        def plural_name
+          'project_briefs'
+        end
+      end
+    end
+  end
+end
diff --git a/lib/asana/resources/project_template.rb b/lib/asana/resources/project_template.rb
new file mode 100644
index 0000000..986651d
--- /dev/null
+++ b/lib/asana/resources/project_template.rb
@@ -0,0 +1,36 @@
+require_relative 'gen/project_templates_base'
+
+module Asana
+  module Resources
+    class ProjectTemplate < ProjectTemplatesBase
+
+
+      attr_reader :gid
+
+      attr_reader :resource_type
+
+      attr_reader :color
+      
+      attr_reader :description
+
+      attr_reader :html_description
+
+      attr_reader :name
+
+      attr_reader :owner
+
+      attr_reader :public
+
+      attr_reader :requested_dates
+
+      attr_reader :team
+
+      class << self
+        # Returns the plural name of the resource.
+        def plural_name
+          'project_templates'
+        end
+      end
+    end
+  end
+end
diff --git a/lib/asana/resources/status_update.rb b/lib/asana/resources/status_update.rb
new file mode 100644
index 0000000..8a8fac4
--- /dev/null
+++ b/lib/asana/resources/status_update.rb
@@ -0,0 +1,54 @@
+require_relative 'gen/status_updates_base'
+
+module Asana
+  module Resources
+    class StatusUpdate < StatusUpdatesBase
+
+
+      attr_reader :gid
+
+      attr_reader :resource_type
+
+      attr_reader :html_text
+      
+      attr_reader :resource_subtype
+
+      attr_reader :status_type
+
+      attr_reader :text
+
+      attr_reader :title
+
+      attr_reader :author
+
+      attr_reader :created_at
+
+      attr_reader :created_by
+
+      attr_reader :hearted
+
+      attr_reader :hearts
+
+      attr_reader :liked
+
+      attr_reader :likes
+
+      attr_reader :created_at
+
+      attr_reader :modified_at
+
+      attr_reader :num_hearts
+
+      attr_reader :num_likes
+
+      attr_reader :parent
+
+      class << self
+        # Returns the plural name of the resource.
+        def plural_name
+          'status_updates'
+        end
+      end
+    end
+  end
+end
diff --git a/lib/asana/resources/time_period.rb b/lib/asana/resources/time_period.rb
new file mode 100644
index 0000000..4800df7
--- /dev/null
+++ b/lib/asana/resources/time_period.rb
@@ -0,0 +1,30 @@
+require_relative 'gen/time_periods_base'
+
+module Asana
+  module Resources
+    class TimePeriod < TimePeriodsBase
+
+
+      attr_reader :gid
+
+      attr_reader :resource_type
+
+      attr_reader :display_name
+      
+      attr_reader :end_on
+
+      attr_reader :parent
+
+      attr_reader :period
+
+      attr_reader :start_on
+
+      class << self
+        # Returns the plural name of the resource.
+        def plural_name
+          'time_periods'
+        end
+      end
+    end
+  end
+end
diff --git a/lib/asana/resources/typeahead.rb b/lib/asana/resources/typeahead.rb
index 59fbf6b..e891b7a 100644
--- a/lib/asana/resources/typeahead.rb
+++ b/lib/asana/resources/typeahead.rb
@@ -14,7 +14,7 @@ module Asana
       class << self
         # Returns the plural name of the resource.
         def plural_name
-          'typeaheads'
+          'typeahead'
         end
       end
     end
diff --git a/lib/asana/ruby2_0_0_compatibility.rb b/lib/asana/ruby2_0_0_compatibility.rb
index 0e0ce01..c9c1078 100644
--- a/lib/asana/ruby2_0_0_compatibility.rb
+++ b/lib/asana/ruby2_0_0_compatibility.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 def required(name)
   raise(ArgumentError, "#{name} is a required keyword argument")
 end
diff --git a/lib/asana/version.rb b/lib/asana/version.rb
index 2b2101f..77dc2c2 100644
--- a/lib/asana/version.rb
+++ b/lib/asana/version.rb
@@ -1,5 +1,5 @@
 #:nodoc:
 module Asana
   # Public: Version of the gem.
-  VERSION = '0.10.13'
+  VERSION = '2.0.3'
 end
diff --git a/samples/attachments_sample.yaml b/samples/attachments_sample.yaml
index f6a4811..79e31b4 100644
--- a/samples/attachments_sample.yaml
+++ b/samples/attachments_sample.yaml
@@ -1,5 +1,5 @@
 attachments: 
-    create_attachment_for_task: >-
+    create_attachment_for_object: >-
         require 'asana'
 
 
@@ -8,7 +8,7 @@ attachments:
         end
 
 
-        result = client.attachments.create_attachment_for_task(task_gid: 'task_gid', field: "value", field: "value", options: {pretty: true})
+        result = client.attachments.create_attachment_for_object(field: "value", field: "value", options: {pretty: true})
     delete_attachment: >-
         require 'asana'
 
@@ -29,7 +29,7 @@ attachments:
 
 
         result = client.attachments.get_attachment(attachment_gid: 'attachment_gid', param: "value", param: "value", options: {pretty: true})
-    get_attachments_for_task: >-
+    get_attachments_for_object: >-
         require 'asana'
 
 
@@ -38,4 +38,4 @@ attachments:
         end
 
 
-        result = client.attachments.get_attachments_for_task(task_gid: 'task_gid', param: "value", param: "value", options: {pretty: true})
+        result = client.attachments.get_attachments_for_object(parent: '&#x27;parent_example&#x27;', param: "value", param: "value", options: {pretty: true})
diff --git a/samples/goal_relationships_sample.yaml b/samples/goal_relationships_sample.yaml
new file mode 100644
index 0000000..07163b7
--- /dev/null
+++ b/samples/goal_relationships_sample.yaml
@@ -0,0 +1,51 @@
+goalrelationships: 
+    add_supporting_relationship: >-
+        require 'asana'
+
+
+        client = Asana::Client.new do |c|
+            c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN'
+        end
+
+
+        result = client.goal_relationships.add_supporting_relationship(goal_gid: 'goal_gid', field: "value", field: "value", options: {pretty: true})
+    get_goal_relationship: >-
+        require 'asana'
+
+
+        client = Asana::Client.new do |c|
+            c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN'
+        end
+
+
+        result = client.goal_relationships.get_goal_relationship(goal_relationship_gid: 'goal_relationship_gid', param: "value", param: "value", options: {pretty: true})
+    get_goal_relationships: >-
+        require 'asana'
+
+
+        client = Asana::Client.new do |c|
+            c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN'
+        end
+
+
+        result = client.goal_relationships.get_goal_relationships(supported_goal: '&#x27;supported_goal_example&#x27;', param: "value", param: "value", options: {pretty: true})
+    remove_supporting_relationship: >-
+        require 'asana'
+
+
+        client = Asana::Client.new do |c|
+            c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN'
+        end
+
+
+        result = client.goal_relationships.remove_supporting_relationship(goal_gid: 'goal_gid', field: "value", field: "value", options: {pretty: true})
+    update_goal_relationship: >-
+        require 'asana'
+
+
+        client = Asana::Client.new do |c|
+            c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN'
+        end
+
+
+        result = client.goal_relationships.update_goal_relationship(goal_relationship_gid: 'goal_relationship_gid', field: "value", field: "value", options: {pretty: true})
diff --git a/samples/goals_sample.yaml b/samples/goals_sample.yaml
index 5b3ce40..827ecf0 100644
--- a/samples/goals_sample.yaml
+++ b/samples/goals_sample.yaml
@@ -8,27 +8,7 @@ goals:
         end
 
 
-        result = client.goals.add_followers(field: "value", field: "value", options: {pretty: true})
-    add_subgoal: >-
-        require 'asana'
-
-
-        client = Asana::Client.new do |c|
-            c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN'
-        end
-
-
-        result = client.goals.add_subgoal(field: "value", field: "value", options: {pretty: true})
-    add_supporting_work_for_goal: >-
-        require 'asana'
-
-
-        client = Asana::Client.new do |c|
-            c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN'
-        end
-
-
-        result = client.goals.add_supporting_work_for_goal(field: "value", field: "value", options: {pretty: true})
+        result = client.goals.add_followers(goal_gid: 'goal_gid', field: "value", field: "value", options: {pretty: true})
     create_goal: >-
         require 'asana'
 
@@ -48,7 +28,7 @@ goals:
         end
 
 
-        result = client.goals.create_goal_metric(field: "value", field: "value", options: {pretty: true})
+        result = client.goals.create_goal_metric(goal_gid: 'goal_gid', field: "value", field: "value", options: {pretty: true})
     delete_goal: >-
         require 'asana'
 
@@ -88,17 +68,7 @@ goals:
         end
 
 
-        result = client.goals.get_parent_goals_for_goal(param: "value", param: "value", options: {pretty: true})
-    get_subgoals_for_goal: >-
-        require 'asana'
-
-
-        client = Asana::Client.new do |c|
-            c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN'
-        end
-
-
-        result = client.goals.get_subgoals_for_goal(param: "value", param: "value", options: {pretty: true})
+        result = client.goals.get_parent_goals_for_goal(goal_gid: 'goal_gid', param: "value", param: "value", options: {pretty: true})
     remove_followers: >-
         require 'asana'
 
@@ -108,37 +78,7 @@ goals:
         end
 
 
-        result = client.goals.remove_followers(field: "value", field: "value", options: {pretty: true})
-    remove_subgoal: >-
-        require 'asana'
-
-
-        client = Asana::Client.new do |c|
-            c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN'
-        end
-
-
-        result = client.goals.remove_subgoal(field: "value", field: "value", options: {pretty: true})
-    remove_supporting_work_for_goal: >-
-        require 'asana'
-
-
-        client = Asana::Client.new do |c|
-            c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN'
-        end
-
-
-        result = client.goals.remove_supporting_work_for_goal(field: "value", field: "value", options: {pretty: true})
-    supporting_work: >-
-        require 'asana'
-
-
-        client = Asana::Client.new do |c|
-            c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN'
-        end
-
-
-        result = client.goals.supporting_work(param: "value", param: "value", options: {pretty: true})
+        result = client.goals.remove_followers(goal_gid: 'goal_gid', field: "value", field: "value", options: {pretty: true})
     update_goal: >-
         require 'asana'
 
@@ -158,4 +98,4 @@ goals:
         end
 
 
-        result = client.goals.update_goal_metric(field: "value", field: "value", options: {pretty: true})
+        result = client.goals.update_goal_metric(goal_gid: 'goal_gid', field: "value", field: "value", options: {pretty: true})
diff --git a/samples/memberships_sample.yaml b/samples/memberships_sample.yaml
new file mode 100644
index 0000000..03bd600
--- /dev/null
+++ b/samples/memberships_sample.yaml
@@ -0,0 +1,41 @@
+memberships: 
+    create_membership: >-
+        require 'asana'
+
+
+        client = Asana::Client.new do |c|
+            c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN'
+        end
+
+
+        result = client.memberships.create_membership(field: "value", field: "value", options: {pretty: true})
+    delete_membership: >-
+        require 'asana'
+
+
+        client = Asana::Client.new do |c|
+            c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN'
+        end
+
+
+        result = client.memberships.delete_membership(membership_gid: 'membership_gid', options: {pretty: true})
+    get_memberships: >-
+        require 'asana'
+
+
+        client = Asana::Client.new do |c|
+            c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN'
+        end
+
+
+        result = client.memberships.get_memberships(parent: '&#x27;parent_example&#x27;', param: "value", param: "value", options: {pretty: true})
+    update_membership: >-
+        require 'asana'
+
+
+        client = Asana::Client.new do |c|
+            c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN'
+        end
+
+
+        result = client.memberships.update_membership(membership_gid: 'membership_gid', field: "value", field: "value", options: {pretty: true})
diff --git a/samples/teams_sample.yaml b/samples/teams_sample.yaml
index 75ae2e8..7bc90b9 100644
--- a/samples/teams_sample.yaml
+++ b/samples/teams_sample.yaml
@@ -29,7 +29,7 @@ teams:
 
 
         result = client.teams.get_team(team_gid: 'team_gid', param: "value", param: "value", options: {pretty: true})
-    get_teams_for_organization: >-
+    get_teams_for_user: >-
         require 'asana'
 
 
@@ -38,8 +38,8 @@ teams:
         end
 
 
-        result = client.teams.get_teams_for_organization(workspace_gid: 'workspace_gid', param: "value", param: "value", options: {pretty: true})
-    get_teams_for_user: >-
+        result = client.teams.get_teams_for_user(user_gid: 'user_gid', organization: '&#x27;organization_example&#x27;', param: "value", param: "value", options: {pretty: true})
+    get_teams_for_workspace: >-
         require 'asana'
 
 
@@ -48,7 +48,7 @@ teams:
         end
 
 
-        result = client.teams.get_teams_for_user(user_gid: 'user_gid', organization: '&#x27;organization_example&#x27;', param: "value", param: "value", options: {pretty: true})
+        result = client.teams.get_teams_for_workspace(workspace_gid: 'workspace_gid', param: "value", param: "value", options: {pretty: true})
     remove_user_for_team: >-
         require 'asana'
 
@@ -59,3 +59,13 @@ teams:
 
 
         result = client.teams.remove_user_for_team(team_gid: 'team_gid', field: "value", field: "value", options: {pretty: true})
+    update_team: >-
+        require 'asana'
+
+
+        client = Asana::Client.new do |c|
+            c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN'
+        end
+
+
+        result = client.teams.update_team(field: "value", field: "value", options: {pretty: true})

More details

Full run details

Historical runs