diff --git a/.github/workflows/octokit.yml b/.github/workflows/octokit.yml index c931902ea..d81a79938 100644 --- a/.github/workflows/octokit.yml +++ b/.github/workflows/octokit.yml @@ -56,3 +56,9 @@ jobs: GITHUB_CI: 1 RUBYOPT: --enable-frozen-string-literal run: bundle exec rspec -w + - name: Test with RSpec in lazy-loaded mode + env: + GITHUB_CI: 1 + RUBYOPT: --enable-frozen-string-literal + OCTOKIT_REQUIRE: octokit/lazy + run: bundle exec rspec -w diff --git a/Gemfile b/Gemfile index da391776d..ed944d75e 100644 --- a/Gemfile +++ b/Gemfile @@ -42,4 +42,4 @@ group :test, :development do gem 'rubocop' end -gemspec +gemspec require: false diff --git a/README.md b/README.md index d4570878f..3e2a48d8d 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ client.readme 'al3x/sovereign', :accept => 'application/vnd.github.html' ## Installation -Install via Rubygems +Install via RubyGems gem install octokit @@ -82,6 +82,10 @@ Access the library in Ruby: require 'octokit' +To speed up the boot of your application, you can `require 'octokit/lazy'` which will lazy-load required code as you use it, rather than requiring everything at once. You may also need to configure this in your `Gemfile` like this: + + gem "octokit", "~> 4.0", require: "octokit/lazy" + ## Making requests [API methods][] are available as client instance methods. diff --git a/lib/octokit.rb b/lib/octokit.rb index b27e3257b..5afbd51b7 100644 --- a/lib/octokit.rb +++ b/lib/octokit.rb @@ -3,56 +3,9 @@ require 'octokit/enterprise_admin_client' require 'octokit/enterprise_management_console_client' -# Ruby toolkit for the GitHub API -module Octokit - class << self - include Octokit::Configurable - - # API client based on configured options {Configurable} - # - # @return [Octokit::Client] API wrapper - def client - return @client if defined?(@client) && @client.same_options?(options) - @client = Octokit::Client.new(options) - end - - # EnterpriseAdminClient client based on configured options {Configurable} - # - # @return [Octokit::EnterpriseAdminClient] API wrapper - def enterprise_admin_client - return @enterprise_admin_client if defined?(@enterprise_admin_client) && @enterprise_admin_client.same_options?(options) - @enterprise_admin_client = Octokit::EnterpriseAdminClient.new(options) - end - - # EnterpriseManagementConsoleClient client based on configured options {Configurable} - # - # @return [Octokit::EnterpriseManagementConsoleClient] API wrapper - def enterprise_management_console_client - return @enterprise_management_console_client if defined?(@enterprise_management_console_client) && @enterprise_management_console_client.same_options?(options) - @enterprise_management_console_client = Octokit::EnterpriseManagementConsoleClient.new(options) - end - - private - - def respond_to_missing?(method_name, include_private=false) - client.respond_to?(method_name, include_private) || - enterprise_admin_client.respond_to?(method_name, include_private) || - enterprise_management_console_client.respond_to?(method_name, include_private) - end - - def method_missing(method_name, *args, &block) - if client.respond_to?(method_name) - return client.send(method_name, *args, &block) - elsif enterprise_admin_client.respond_to?(method_name) - return enterprise_admin_client.send(method_name, *args, &block) - elsif enterprise_management_console_client.respond_to?(method_name) - return enterprise_management_console_client.send(method_name, *args, &block) - end - - super - end - - end -end - -Octokit.setup +# We have already required `Client`, `EnterpriseAdminClient` and +# `EnterpriseManagementConsoleClient` above, so the `autoload` declaration in `lib/octokit/base.rb` +# won't do anything. This is the default for Octokit, and leads to a simpler and more reliable +# user experience, even if it makes booting your app a little slower compared to the lazy-loaded +# experience you get if you call `require 'octokit/lazy'`. +require 'octokit/base' diff --git a/lib/octokit/base.rb b/lib/octokit/base.rb new file mode 100644 index 000000000..aa993b8fa --- /dev/null +++ b/lib/octokit/base.rb @@ -0,0 +1,64 @@ +# Ruby toolkit for the GitHub API +# +# NOTE: The `Octokit` module is defined here, rather than in `lib/octokit.rb`, to allow +# the gem to include a lazy-loaded version where dependencies are required as needed, +# as well as a default where everything is required together. +module Octokit + # These autoload statements won't do anything if the module has already been loaded, as + # will happen if the gem is required in the normal, automatic way. They will only come + # into affect if the user requires `octokit/lazy`. + autoload(:Client, File.join(__dir__, 'client')) + autoload(:EnterpriseAdminClient, File.join(__dir__, 'enterprise_admin_client')) + autoload(:EnterpriseManagementConsoleClient, File.join(__dir__, 'enterprise_management_console_client')) + + class << self + include Octokit::Configurable + + # API client based on configured options {Configurable} + # + # @return [Octokit::Client] API wrapper + def client + return @client if defined?(@client) && @client.same_options?(options) + @client = Octokit::Client.new(options) + end + + # EnterpriseAdminClient client based on configured options {Configurable} + # + # @return [Octokit::EnterpriseAdminClient] API wrapper + def enterprise_admin_client + return @enterprise_admin_client if defined?(@enterprise_admin_client) && @enterprise_admin_client.same_options?(options) + @enterprise_admin_client = Octokit::EnterpriseAdminClient.new(options) + end + + # EnterpriseManagementConsoleClient client based on configured options {Configurable} + # + # @return [Octokit::EnterpriseManagementConsoleClient] API wrapper + def enterprise_management_console_client + return @enterprise_management_console_client if defined?(@enterprise_management_console_client) && @enterprise_management_console_client.same_options?(options) + @enterprise_management_console_client = Octokit::EnterpriseManagementConsoleClient.new(options) + end + + private + + def respond_to_missing?(method_name, include_private=false) + client.respond_to?(method_name, include_private) || + enterprise_admin_client.respond_to?(method_name, include_private) || + enterprise_management_console_client.respond_to?(method_name, include_private) + end + + def method_missing(method_name, *args, &block) + if client.respond_to?(method_name) + return client.send(method_name, *args, &block) + elsif enterprise_admin_client.respond_to?(method_name) + return enterprise_admin_client.send(method_name, *args, &block) + elsif enterprise_management_console_client.respond_to?(method_name) + return enterprise_management_console_client.send(method_name, *args, &block) + end + + super + end + + end +end + +Octokit.setup diff --git a/lib/octokit/lazy.rb b/lib/octokit/lazy.rb new file mode 100644 index 000000000..a7a7408fd --- /dev/null +++ b/lib/octokit/lazy.rb @@ -0,0 +1,3 @@ +require 'octokit/default' +require 'octokit/configurable' +require 'octokit/base' \ No newline at end of file diff --git a/spec/helper.rb b/spec/helper.rb index c41399e8f..7fa05b447 100644 --- a/spec/helper.rb +++ b/spec/helper.rb @@ -4,7 +4,14 @@ end require 'json' -require 'octokit' + +# This allows us to use the `OCTOKIT_REQUIRE` environment variable to switch in our +# tests between requiring all of Octokit (`require 'octokit'`) and using the lazy- +# loaded version (`require 'octokit/lazy'`). +octokit_require = ENV.fetch('OCTOKIT_REQUIRE', 'octokit') +require octokit_require + +require 'faraday' require 'rspec' require 'webmock/rspec' require 'base64'