Skip to content

Commit

Permalink
[Ruby] add useAutoload option to use autoload instead of require (O…
Browse files Browse the repository at this point in the history
…penAPITools#13153)

* [Ruby] Use Ruby autoload to lower memory usage and load times

Fixes OpenAPITools#12648

Requiring all models up front can be very expensive in both time and
memory if there are many models. In an example client with 6000 models,
this would consume nearly 400MB of memory and take about 7 seconds to
load. This is mostly unnecessary as most users of the client library
will only actually use a small percentage of the library.

The changes in this commit use Ruby's autoload capability to defer the
loading until the constant is actually used. In that same example client
with 6000 models, when initially requiring the library, the memory
usage dropped to ~20MB and loaded in 0.3 seconds. As the constants are
loaded on-demand, the memory would increase towards that 400MB ceiling,
but if only a few constants are actually used, then memory will never
actually hit that ceiling.

An additional side effect of using Ruby's autoload is that the order of
declaring the constants is not important, as Ruby will naturally load
them in the correct order when they are needed. Thus, this commit obviates
PR OpenAPITools#9103 and fixes OpenAPITools#4690.

* add option to use autoload in ruby client

* test ruby clients only

* add tests

* update samples

* Revert "test ruby clients only"

This reverts commit 0aaf71c.

* update doc

Co-authored-by: Jason Frey <[email protected]>
  • Loading branch information
wing328 and Fryguy authored Aug 11, 2022
1 parent 5662d61 commit c5a0374
Show file tree
Hide file tree
Showing 194 changed files with 22,735 additions and 5 deletions.
12 changes: 12 additions & 0 deletions bin/configs/ruby-autoload.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
generatorName: ruby
outputDir: samples/client/petstore/ruby-autoload
library: typhoeus
inputSpec: modules/openapi-generator/src/test/resources/3_0/petstore-with-fake-endpoints-models-for-testing.yaml
templateDir: modules/openapi-generator/src/main/resources/ruby-client
additionalProperties:
gemVersion: 1.0.0
moduleName: Petstore
gemName: petstore
skipFormModel: "true"
useAutoload: true
strictSpecBehavior: false
1 change: 1 addition & 0 deletions docs/generators/ruby.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|prependFormOrBodyParameters|Add form or body parameters to the beginning of the parameter list.| |false|
|sortModelPropertiesByRequiredFlag|Sort model properties to place required parameters before optional parameters.| |true|
|sortParamsByRequiredFlag|Sort method arguments to place required parameters before optional parameters.| |true|
|useAutoload|Use autoload instead of require to load modules.| |false|

## IMPORT MAPPING

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ public class RubyClientCodegen extends AbstractRubyCodegen {
public static final String GEM_AUTHOR_EMAIL = "gemAuthorEmail";
public static final String FARADAY = "faraday";
public static final String TYPHOEUS = "typhoeus";
public static final String USE_AUTOLOAD = "useAutoload";
private final Logger LOGGER = LoggerFactory.getLogger(RubyClientCodegen.class);
private static final String NUMERIC_ENUM_PREFIX = "N";
protected static int emptyMethodNameCounter = 0;
Expand All @@ -62,6 +63,7 @@ public class RubyClientCodegen extends AbstractRubyCodegen {
protected String gemAuthorEmail = "";
protected String apiDocPath = "docs/";
protected String modelDocPath = "docs/";
protected boolean useAutoload = false;

private Map<String, String> schemaKeyToModelNameCache = new HashMap<>();

Expand Down Expand Up @@ -165,6 +167,9 @@ public RubyClientCodegen() {
cliOptions.add(new CliOption(CodegenConstants.HIDE_GENERATION_TIMESTAMP, CodegenConstants.HIDE_GENERATION_TIMESTAMP_DESC).
defaultValue(Boolean.TRUE.toString()));

cliOptions.add(CliOption.newBoolean(USE_AUTOLOAD, "Use autoload instead of require to load modules.").
defaultValue(Boolean.FALSE.toString()));

supportedLibraries.put(FARADAY, "Faraday (https://github.com/lostisland/faraday) (Beta support)");
supportedLibraries.put(TYPHOEUS, "Typhoeus >= 1.0.1 (https://github.com/typhoeus/typhoeus)");

Expand Down Expand Up @@ -234,6 +239,10 @@ public void processOpts() {
setGemAuthorEmail((String) additionalProperties.get(GEM_AUTHOR_EMAIL));
}

if (additionalProperties.containsKey(USE_AUTOLOAD)) {
setUseAutoload(convertPropertyToBooleanAndWriteBack(USE_AUTOLOAD));
}

// make api and model doc path available in mustache template
additionalProperties.put("apiDocPath", apiDocPath);
additionalProperties.put("modelDocPath", modelDocPath);
Expand Down Expand Up @@ -297,7 +306,6 @@ public String getHelp() {
* Generate Ruby module name from the gem name, e.g. use "OpenAPIClient" for "openapi_client".
*
* @param gemName Ruby gem name
*
* @return Ruby module name
*/
@SuppressWarnings("static-method")
Expand All @@ -309,7 +317,6 @@ public String generateModuleName(String gemName) {
* Generate Ruby gem name from the module name, e.g. use "openapi_client" for "OpenAPIClient".
*
* @param moduleName Ruby module name
*
* @return Ruby gem name
*/
@SuppressWarnings("static-method")
Expand Down Expand Up @@ -583,6 +590,10 @@ public void setGemAuthorEmail(String gemAuthorEmail) {
this.gemAuthorEmail = gemAuthorEmail;
}

public void setUseAutoload(boolean useAutoload) {
this.useAutoload = useAutoload;
}

@Override
protected void addAdditionPropertiesToCodeGenModel(CodegenModel codegenModel, Schema schema) {
final Schema additionalProperties = getAdditionalProperties(schema);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ require '{{gemName}}/version'
require '{{gemName}}/configuration'

# Models
{{^useAutoload}}
{{#models}}
{{#model}}
{{^parent}}
Expand All @@ -23,11 +24,24 @@ require '{{gemName}}/{{modelPackage}}/{{classFilename}}'
{{/parent}}
{{/model}}
{{/models}}
{{/useAutoload}}
{{#useAutoload}}
{{#models}}
{{#model}}
{{moduleName}}.autoload :{{classname}}, '{{gemName}}/{{modelPackage}}/{{classFilename}}'
{{/model}}
{{/models}}
{{/useAutoload}}

# APIs
{{#apiInfo}}
{{#apis}}
{{^useAutoload}}
require '{{importPath}}'
{{/useAutoload}}
{{#useAutoload}}
{{moduleName}}.autoload :{{classname}}, '{{importPath}}'
{{/useAutoload}}
{{/apis}}
{{/apiInfo}}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@
import java.util.Map;

public class RubyClientOptionsProvider implements OptionsProvider {
public static final String GEM_NAME_VALUE = "swagger_client_ruby";
public static final String MODULE_NAME_VALUE = "SwaggerClientRuby";
public static final String GEM_NAME_VALUE = "openapi_client_ruby";
public static final String MODULE_NAME_VALUE = "OpenApiClientRuby";
public static final String GEM_VERSION_VALUE = "1.0.0-SNAPSHOT";
public static final String SORT_PARAMS_VALUE = "false";
public static final String SORT_MODEL_PROPERTIES_VALUE = "false";
Expand All @@ -35,12 +35,13 @@ public class RubyClientOptionsProvider implements OptionsProvider {
public static final String GEM_HOMEPAGE_VALUE = "homepage";
public static final String GEM_SUMMARY_VALUE = "summary";
public static final String GEM_DESCRIPTION_VALUE = "description";
public static final String GEM_AUTHOR_VALUE = "foo";
public static final String GEM_AUTHOR_VALUE = "foo";
public static final String GEM_AUTHOR_EMAIL_VALUE = "foo";
public static final String ALLOW_UNICODE_IDENTIFIERS_VALUE = "false";
public static final String PREPEND_FORM_OR_BODY_PARAMETERS_VALUE = "true";
public static final String LIBRARY = "typhoeus";
public static final String ENUM_UNKNOWN_DEFAULT_CASE_VALUE = "false";
public static final String USE_AUTOLOAD_VALUE = "true";

@Override
public String getLanguage() {
Expand Down Expand Up @@ -70,6 +71,7 @@ public Map<String, String> createOptions() {
.put(CodegenConstants.LEGACY_DISCRIMINATOR_BEHAVIOR, "true")
.put(CodegenConstants.DISALLOW_ADDITIONAL_PROPERTIES_IF_NOT_PRESENT, "true")
.put(CodegenConstants.ENUM_UNKNOWN_DEFAULT_CASE, ENUM_UNKNOWN_DEFAULT_CASE_VALUE)
.put(RubyClientCodegen.USE_AUTOLOAD, USE_AUTOLOAD_VALUE)
.build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,6 @@ protected void verifyOptions() {
verify(clientCodegen).setGemAuthor(RubyClientOptionsProvider.GEM_AUTHOR_VALUE);
verify(clientCodegen).setGemAuthorEmail(RubyClientOptionsProvider.GEM_AUTHOR_EMAIL_VALUE);
verify(clientCodegen).setEnumUnknownDefaultCase(Boolean.parseBoolean(RubyClientOptionsProvider.ENUM_UNKNOWN_DEFAULT_CASE_VALUE));
verify(clientCodegen).setUseAutoload(Boolean.parseBoolean(RubyClientOptionsProvider.USE_AUTOLOAD_VALUE));
}
}
1 change: 1 addition & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1146,6 +1146,7 @@
<!--<module>samples/client/petstore/bash</module>-->
<module>samples/client/petstore/ruby-faraday</module>
<module>samples/client/petstore/ruby</module>
<module>samples/client/petstore/ruby-autoload</module>
<!-- comment out as it's not working as the moment
<module>samples/client/petstore/c</module>
-->
Expand Down
39 changes: 39 additions & 0 deletions samples/client/petstore/ruby-autoload/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Generated by: https://openapi-generator.tech
#

*.gem
*.rbc
/.config
/coverage/
/InstalledFiles
/pkg/
/spec/reports/
/spec/examples.txt
/test/tmp/
/test/version_tmp/
/tmp/

## Specific to RubyMotion:
.dat*
.repl_history
build/

## Documentation cache and generated files:
/.yardoc/
/_yardoc/
/doc/
/rdoc/

## Environment normalization:
/.bundle/
/vendor/bundle
/lib/bundler/man/

# for a library or gem, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# Gemfile.lock
# .ruby-version
# .ruby-gemset

# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
.rvmrc
26 changes: 26 additions & 0 deletions samples/client/petstore/ruby-autoload/.gitlab-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
.ruby: &ruby
variables:
LANG: "C.UTF-8"
before_script:
- ruby -v
- bundle config set --local deployment true
- bundle install -j $(nproc)
parallel:
matrix:
- RUBY_VERSION: ['2.3', '2.4', '2.5', '2.6', '2.7', '3.0']
image: "ruby:$RUBY_VERSION"
cache:
paths:
- vendor/ruby
key: 'ruby-$RUBY_VERSION'

gem:
extends: .ruby
script:
- bundle exec rspec
- bundle exec rake build
- bundle exec rake install
artifacts:
paths:
- pkg/*.gem

23 changes: 23 additions & 0 deletions samples/client/petstore/ruby-autoload/.openapi-generator-ignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# OpenAPI Generator Ignore
# Generated by openapi-generator https://github.com/openapitools/openapi-generator

# Use this file to prevent files from being overwritten by the generator.
# The patterns follow closely to .gitignore or .dockerignore.

# As an example, the C# client generator defines ApiClient.cs.
# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line:
#ApiClient.cs

# You can match any string of characters against a directory, file or extension with a single asterisk (*):
#foo/*/qux
# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux

# You can recursively match patterns against a directory, file or extension with a double asterisk (**):
#foo/**/qux
# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux

# You can also negate patterns with an exclamation (!).
# For example, you can ignore all files in a docs folder with the file extension .md:
#docs/*.md
# Then explicitly reverse the ignore rule for a single file:
#!docs/README.md
128 changes: 128 additions & 0 deletions samples/client/petstore/ruby-autoload/.openapi-generator/FILES
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
.gitignore
.gitlab-ci.yml
.rspec
.rubocop.yml
.travis.yml
Gemfile
README.md
Rakefile
docs/AdditionalPropertiesClass.md
docs/AllOfWithSingleRef.md
docs/Animal.md
docs/AnotherFakeApi.md
docs/ApiResponse.md
docs/ArrayOfArrayOfNumberOnly.md
docs/ArrayOfNumberOnly.md
docs/ArrayTest.md
docs/Capitalization.md
docs/Cat.md
docs/CatAllOf.md
docs/Category.md
docs/ClassModel.md
docs/Client.md
docs/DefaultApi.md
docs/DeprecatedObject.md
docs/Dog.md
docs/DogAllOf.md
docs/EnumArrays.md
docs/EnumClass.md
docs/EnumTest.md
docs/FakeApi.md
docs/FakeClassnameTags123Api.md
docs/File.md
docs/FileSchemaTestClass.md
docs/Foo.md
docs/FooGetDefaultResponse.md
docs/FormatTest.md
docs/HasOnlyReadOnly.md
docs/HealthCheckResult.md
docs/List.md
docs/MapTest.md
docs/MixedPropertiesAndAdditionalPropertiesClass.md
docs/Model200Response.md
docs/ModelReturn.md
docs/Name.md
docs/NullableClass.md
docs/NumberOnly.md
docs/ObjectWithDeprecatedFields.md
docs/Order.md
docs/OuterComposite.md
docs/OuterEnum.md
docs/OuterEnumDefaultValue.md
docs/OuterEnumInteger.md
docs/OuterEnumIntegerDefaultValue.md
docs/OuterObjectWithEnumProperty.md
docs/Pet.md
docs/PetApi.md
docs/ReadOnlyFirst.md
docs/SingleRefType.md
docs/SpecialModelName.md
docs/StoreApi.md
docs/Tag.md
docs/User.md
docs/UserApi.md
git_push.sh
lib/petstore.rb
lib/petstore/api/another_fake_api.rb
lib/petstore/api/default_api.rb
lib/petstore/api/fake_api.rb
lib/petstore/api/fake_classname_tags123_api.rb
lib/petstore/api/pet_api.rb
lib/petstore/api/store_api.rb
lib/petstore/api/user_api.rb
lib/petstore/api_client.rb
lib/petstore/api_error.rb
lib/petstore/configuration.rb
lib/petstore/models/additional_properties_class.rb
lib/petstore/models/all_of_with_single_ref.rb
lib/petstore/models/animal.rb
lib/petstore/models/api_response.rb
lib/petstore/models/array_of_array_of_number_only.rb
lib/petstore/models/array_of_number_only.rb
lib/petstore/models/array_test.rb
lib/petstore/models/capitalization.rb
lib/petstore/models/cat.rb
lib/petstore/models/cat_all_of.rb
lib/petstore/models/category.rb
lib/petstore/models/class_model.rb
lib/petstore/models/client.rb
lib/petstore/models/deprecated_object.rb
lib/petstore/models/dog.rb
lib/petstore/models/dog_all_of.rb
lib/petstore/models/enum_arrays.rb
lib/petstore/models/enum_class.rb
lib/petstore/models/enum_test.rb
lib/petstore/models/file.rb
lib/petstore/models/file_schema_test_class.rb
lib/petstore/models/foo.rb
lib/petstore/models/foo_get_default_response.rb
lib/petstore/models/format_test.rb
lib/petstore/models/has_only_read_only.rb
lib/petstore/models/health_check_result.rb
lib/petstore/models/list.rb
lib/petstore/models/map_test.rb
lib/petstore/models/mixed_properties_and_additional_properties_class.rb
lib/petstore/models/model200_response.rb
lib/petstore/models/model_return.rb
lib/petstore/models/name.rb
lib/petstore/models/nullable_class.rb
lib/petstore/models/number_only.rb
lib/petstore/models/object_with_deprecated_fields.rb
lib/petstore/models/order.rb
lib/petstore/models/outer_composite.rb
lib/petstore/models/outer_enum.rb
lib/petstore/models/outer_enum_default_value.rb
lib/petstore/models/outer_enum_integer.rb
lib/petstore/models/outer_enum_integer_default_value.rb
lib/petstore/models/outer_object_with_enum_property.rb
lib/petstore/models/pet.rb
lib/petstore/models/read_only_first.rb
lib/petstore/models/single_ref_type.rb
lib/petstore/models/special_model_name.rb
lib/petstore/models/tag.rb
lib/petstore/models/user.rb
lib/petstore/version.rb
petstore.gemspec
spec/api_client_spec.rb
spec/configuration_spec.rb
spec/spec_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
6.1.0-SNAPSHOT
2 changes: 2 additions & 0 deletions samples/client/petstore/ruby-autoload/.rspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
--color
--require spec_helper
Loading

0 comments on commit c5a0374

Please sign in to comment.