auto_preload 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 27b495a7c7242a5cf7b8adbd58d4bd5b94b517f6c4328d0cd25ce9dd33108438
4
+ data.tar.gz: dfa50abfe824c5a79ebfff08549e56e3fd9e84269a6f2bf4d7ac55885457ac99
5
+ SHA512:
6
+ metadata.gz: 1e499cdcc1ec269098a702eb824aa3f3a36d730b64867f74b359f5595d80e57f617942ad0decabf717baf1de01df2649b1a644ee4b521ecf22f9b4664a069692
7
+ data.tar.gz: b13655e3b6423d98a2b6f26d6f91e7096be16c890a4a6449fda57e986959733d11d1f1a8cd5119dd4fcd9bff93f46d387a7bc16e4dd4b603754ab5162dbe2d8c
data/.editorconfig ADDED
@@ -0,0 +1,12 @@
1
+ # EditorConfig is awesome: https://EditorConfig.org
2
+
3
+ # top-most EditorConfig file
4
+ root = true
5
+
6
+ [*]
7
+ indent_style = space
8
+ indent_size = 2
9
+ end_of_line = lf
10
+ charset = utf-8
11
+ trim_trailing_whitespace = true
12
+ insert_final_newline = true
@@ -0,0 +1,72 @@
1
+ # For most projects, this workflow file will not need changing; you simply need
2
+ # to commit it to your repository.
3
+ #
4
+ # You may wish to alter this file to override the set of languages analyzed,
5
+ # or to provide custom queries or build logic.
6
+ #
7
+ # ******** NOTE ********
8
+ # We have attempted to detect the languages in your repository. Please check
9
+ # the `language` matrix defined below to confirm you have the correct set of
10
+ # supported CodeQL languages.
11
+ #
12
+ name: "CodeQL"
13
+
14
+ on:
15
+ push:
16
+ branches: [ master ]
17
+ pull_request:
18
+ # The branches below must be a subset of the branches above
19
+ branches: [ master ]
20
+ schedule:
21
+ - cron: '19 13 * * 1'
22
+
23
+ jobs:
24
+ analyze:
25
+ name: Analyze
26
+ runs-on: ubuntu-latest
27
+ permissions:
28
+ actions: read
29
+ contents: read
30
+ security-events: write
31
+
32
+ strategy:
33
+ fail-fast: false
34
+ matrix:
35
+ language: [ 'ruby' ]
36
+ # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
37
+ # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
38
+
39
+ steps:
40
+ - name: Checkout repository
41
+ uses: actions/checkout@v3
42
+
43
+ # Initializes the CodeQL tools for scanning.
44
+ - name: Initialize CodeQL
45
+ uses: github/codeql-action/init@v2
46
+ with:
47
+ languages: ${{ matrix.language }}
48
+ # If you wish to specify custom queries, you can do so here or in a config file.
49
+ # By default, queries listed here will override any specified in a config file.
50
+ # Prefix the list here with "+" to use these queries and those in the config file.
51
+
52
+ # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
53
+ # queries: security-extended,security-and-quality
54
+
55
+
56
+ # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
57
+ # If this step fails, then you should remove it and run the build manually (see below)
58
+ - name: Autobuild
59
+ uses: github/codeql-action/autobuild@v2
60
+
61
+ # ℹ️ Command-line programs to run using the OS shell.
62
+ # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
63
+
64
+ # If the Autobuild fails above, remove it and uncomment the following three lines.
65
+ # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
66
+
67
+ # - run: |
68
+ # echo "Run, Build Application using script"
69
+ # ./location_of_script_within_repo/buildscript.sh
70
+
71
+ - name: Perform CodeQL Analysis
72
+ uses: github/codeql-action/analyze@v2
@@ -0,0 +1,44 @@
1
+ name: Ruby Gem
2
+
3
+ on:
4
+ push:
5
+ tags: [ '*' ]
6
+
7
+ jobs:
8
+ build:
9
+ name: Build + Publish
10
+ runs-on: ubuntu-latest
11
+ environment: production
12
+ permissions:
13
+ contents: read
14
+ packages: write
15
+
16
+ steps:
17
+ - uses: actions/checkout@v3
18
+ - name: Set up Ruby 3.0
19
+ uses: actions/setup-ruby@v1
20
+ with:
21
+ ruby-version: 3.0.x
22
+
23
+ - name: Publish to GPR
24
+ run: |
25
+ mkdir -p $HOME/.gem
26
+ touch $HOME/.gem/credentials
27
+ chmod 0600 $HOME/.gem/credentials
28
+ printf -- "---\n:github: ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials
29
+ gem build *.gemspec
30
+ gem push --KEY github --host https://rubygems.pkg.github.com/${OWNER} *.gem
31
+ env:
32
+ GEM_HOST_API_KEY: "Bearer ${{secrets.GITHUB_TOKEN}}"
33
+ OWNER: ${{ github.repository_owner }}
34
+
35
+ - name: Publish to RubyGems
36
+ run: |
37
+ mkdir -p $HOME/.gem
38
+ touch $HOME/.gem/credentials
39
+ chmod 0600 $HOME/.gem/credentials
40
+ printf -- "---\n:rubygems_api_key: ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials
41
+ gem build *.gemspec
42
+ gem push *.gem
43
+ env:
44
+ GEM_HOST_API_KEY: "${{secrets.RUBYGEMS_AUTH_TOKEN}}"
@@ -0,0 +1,18 @@
1
+ name: Tests
2
+ on: [push, pull_request]
3
+ jobs:
4
+ test:
5
+ strategy:
6
+ fail-fast: false
7
+ matrix:
8
+ os: [ubuntu-latest, macos-latest]
9
+ # Due to https://github.com/actions/runner/issues/849, we have to use quotes for '3.0'
10
+ ruby: [2.7, '3.0', 3.1, head, truffleruby]
11
+ runs-on: ${{ matrix.os }}
12
+ steps:
13
+ - uses: actions/checkout@v2
14
+ - uses: ruby/setup-ruby@v1
15
+ with:
16
+ ruby-version: ${{ matrix.ruby }}
17
+ bundler-cache: true # runs 'bundle install' and caches installed gems automatically
18
+ - run: bundle exec rspec
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ .rspec_status
2
+ /doc
3
+ /Gemfile.lock
4
+ /.yardoc
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,16 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.7
3
+
4
+ Style/StringLiterals:
5
+ Enabled: true
6
+ EnforcedStyle: double_quotes
7
+
8
+ Style/StringLiteralsInInterpolation:
9
+ Enabled: true
10
+ EnforcedStyle: double_quotes
11
+
12
+ Layout/LineLength:
13
+ Max: 120
14
+
15
+ Metrics/BlockLength:
16
+ Enabled: false
data/Gemfile ADDED
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in rspec_match_structure.gemspec
6
+ gemspec
7
+
8
+ gem "rake", "~> 13.0"
9
+ gem "sqlite3"
10
+
11
+ gem "rspec", "~> 3.0"
12
+
13
+ gem "rubocop", "~> 1.7"
14
+
15
+ gem "active_model_serializers", "~> 0.10"
16
+ gem "simplecov"
17
+ gem "yard"
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2022 Mònade
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,116 @@
1
+ ![Tests](https://github.com/monade/auto_preload/actions/workflows/test.yml/badge.svg)
2
+ [![Gem Version](https://badge.fury.io/rb/auto_preload.svg)](https://badge.fury.io/rb/auto_preload)
3
+
4
+ # Auto Preload
5
+
6
+ A gem to parse and run `preload`/`includes`/`eager_load` on your model from a JSON::API include string.
7
+
8
+ ## Installation
9
+
10
+ Add the gem to your Gemfile
11
+
12
+ ```ruby
13
+ gem 'auto_preload'
14
+ ```
15
+
16
+ and run the `bundle install` command.
17
+
18
+ ## The problem
19
+ JSON::API allows API consumers to pass a query parameter, called `include`, to manually select which model associations should be resolved and returned in the output JSON.
20
+
21
+ This means that in your controller, you may have a dilemma:
22
+ * If the consumer requests an association that is not preloaded, Rails will run [N+1 queries](https://guides.rubyonrails.org/active_record_querying.html#eager-loading-associations), slowing down the response
23
+ * You can't know, beforehand, which association may be requested by the consumer, since it's parametric
24
+ * You can just preload every possible association, but you'll end up making a lot of extra (redundant) queries in most cases.
25
+
26
+ This gem tries to fix this by parsing the `include` parameter and transforming it to a `preload`, `includes` or `eager_load` call in the model.
27
+
28
+ ## Usage
29
+ This gem adds to ActiveRecord classes a couple of utility methods that will help to preload associations.
30
+
31
+ To start using it, simply pass a [JSON::API include string](https://jsonapi.org/format/#fetching-includes) to the `auto_preload` class method of a model, and it will resolve it.
32
+
33
+ Here's an example:
34
+ ```ruby
35
+ # Models declaration
36
+ class User < ApplicationRecord
37
+ has_many :articles
38
+ has_many :comments
39
+ end
40
+
41
+ class Comment < ApplicationRecord
42
+ belongs_to :user
43
+ end
44
+
45
+ class Article < ApplicationRecord
46
+ belongs_to :user
47
+ has_many :comments
48
+ end
49
+
50
+ # Now calling auto_preload on User
51
+ User.auto_preload('*') # Equivalent to preload(:articles, :comments)
52
+ User.auto_preload('articles.*') # Equivalent to preload(articles: [:user, :comments])
53
+ ```
54
+
55
+ The same works also with `eager_load` and `includes`:
56
+ ```ruby
57
+ User.auto_eager_load('*') # Equivalent to eager_load(:articles, :comments)
58
+ User.auto_includes('*') # Equivalent to includes(:articles, :comments)
59
+ ```
60
+
61
+ ### Caveats: the `**` resolver
62
+ You can also use the keyword `**`, however it may take you to a loop.
63
+
64
+ For instance in this case, it would raise an error:
65
+ ```ruby
66
+ User.auto_preload('**') # Raises "Too many iterations reached (101 of 100)"
67
+ ```
68
+ Since `User` resolves `:articles`, but `Article` declares `belongs_to :user`.
69
+
70
+ To solve this you can whitelist the associations you want to preload:
71
+ ```ruby
72
+ class Article < ApplicationRecord
73
+ self.auto_preloadable = [:comments]
74
+ belongs_to :user
75
+ has_many :comments
76
+ end
77
+
78
+ class Comment < ApplicationRecord
79
+ self.auto_preloadable = []
80
+ belongs_to :user
81
+ end
82
+ ```
83
+
84
+ Now you can safely use auto_preload:
85
+ ```ruby
86
+ User.auto_preload('**') # Equivalent to preload(:comments, articles: :comments)
87
+ ```
88
+
89
+ ### Adapters
90
+ By default, the resolution of the expressions passed to `auto_preload` methods is resolved by the [ActiveRecord Adapter](https://github.com/monade/auto_preload/blob/master/lib/auto_preload/adapters/active_record.rb).
91
+
92
+ An Adapter is simply a class that, given a model, returns the list of the associations that can be preloaded.
93
+
94
+ The ActiveRecord Adapter uses `reflect_on_all_associations` to get this list.
95
+
96
+ In many circumstances, you don't want this. For instance, if you use `ActiveModelSerializers` gem, you want to resolve only associations that are declared in the serializer.
97
+
98
+ To do so, just change the default adapter using an initializer, in `config/initializers/auto_preload.rb`:
99
+ ```ruby
100
+ AutoPreload.config.adapter = AutoPreload::Adapters::Serializer.new
101
+ ```
102
+
103
+ Of course, you can also declare your custom Adapters, simply creating a class that implements the method `resolve_preloadables(model, options = {})` and returns a list of associations.
104
+
105
+ ## License
106
+
107
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
108
+
109
+ About Monade
110
+ ----------------
111
+
112
+ ![monade](https://monade.io/wp-content/uploads/2021/06/monadelogo.png)
113
+
114
+ auto_preload is maintained by [mònade srl](https://monade.io/en/home-en/).
115
+
116
+ We <3 open source software. [Contact us](https://monade.io/en/contact-us/) for your next project!
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require "rubocop/rake_task"
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[spec rubocop]
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ $LOAD_PATH.push File.expand_path("lib", __dir__)
4
+
5
+ require_relative "lib/auto_preload/version"
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = "auto_preload"
9
+ spec.version = AutoPreload::VERSION
10
+ spec.authors = ["Mònade"]
11
+ spec.email = ["team@monade.io"]
12
+
13
+ spec.summary = "A gem to run nested preloads/includes from string."
14
+ spec.description = "A gem to run nested preloads/includes from string."
15
+ spec.homepage = "https://github.com/monade/auto_preload"
16
+ spec.license = "MIT"
17
+ spec.required_ruby_version = ">= 2.7"
18
+
19
+ spec.metadata["homepage_uri"] = spec.homepage
20
+ spec.metadata["source_code_uri"] = spec.homepage
21
+ spec.metadata["changelog_uri"] = "https://github.com/monade/auto_preload/CHANGELOG.md"
22
+
23
+ # Specify which files should be added to the gem when it is released.
24
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
25
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
26
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
27
+ end
28
+ spec.bindir = "exe"
29
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
30
+ spec.require_paths = ["lib"]
31
+
32
+ # Uncomment to register a new dependency of your gem
33
+ spec.add_dependency "activerecord", [">= 5", "< 8"]
34
+ spec.add_dependency "activesupport", [">= 5", "< 8"]
35
+
36
+ # For more information and examples about making a new gem, checkout our
37
+ # guide at: https://bundler.io/guides/creating_gem.html
38
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AutoPreload
4
+ module ActiveRecord
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ scope :auto_includes, lambda { |inclusions, options = {}|
9
+ if inclusions.present?
10
+ includes(*Resolver.new(options).resolve(self, inclusions))
11
+ else
12
+ self
13
+ end
14
+ }
15
+ scope :auto_preload, lambda { |inclusions, options = {}|
16
+ if inclusions.present?
17
+ preload(*Resolver.new(options).resolve(self, inclusions))
18
+ else
19
+ self
20
+ end
21
+ }
22
+ scope :auto_eager_load, lambda { |inclusions, options = {}|
23
+ if inclusions.present?
24
+ eager_load(*Resolver.new(options).resolve(self, inclusions))
25
+ else
26
+ self
27
+ end
28
+ }
29
+
30
+ class_attribute :auto_preloadable
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AutoPreload
4
+ module Adapters
5
+ # This class takes a model and finds all the preloadable associations.
6
+ class ActiveRecord
7
+ def resolve_preloadables(model, _options = {})
8
+ if model.auto_preloadable
9
+ model.auto_preloadable.map { |w| model.reflect_on_association(w) }.compact
10
+ else
11
+ model.reflect_on_all_associations
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_model_serializers"
4
+
5
+ module AutoPreload
6
+ module Adapters
7
+ # This class takes a model and finds all the preloadable associations.
8
+ class Serializer
9
+ def initialize
10
+ @fallback = ActiveRecord.new
11
+ end
12
+
13
+ # @param model [ActiveRecord::Base] The model to find preloadable associations for.
14
+ # @return [Array<ActiveRecord::Reflection>] The preloadable associations.
15
+ def resolve_preloadables(model, options = {})
16
+ serializer = resolve_serializer(model, options)
17
+ preloadables = @fallback.resolve_preloadables(model)
18
+ return preloadables unless serializer
19
+
20
+ preloadables_map = preloadables.index_by(&:name)
21
+
22
+ serializer._reflections.map do |key, _|
23
+ preloadables_map[key]
24
+ end.compact
25
+ end
26
+
27
+ def resolve_serializer(model, options = {})
28
+ if options[:root]
29
+ options[:serializer] || ActiveModel::Serializer.serializer_for(model)
30
+ else
31
+ ActiveModel::Serializer.serializer_for(model)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AutoPreload
4
+ module Adapters
5
+ extend ActiveSupport::Autoload
6
+
7
+ autoload :ActiveRecord
8
+ autoload :Serializer
9
+ end
10
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AutoPreload
4
+ # This class handles the gem configurations.
5
+ class Config
6
+ # @attr_writr [AutoPreload::Adapters::ActiveRecord, AutoPreload::Adapters::Serializer] adapter The adapter to use.
7
+ attr_writer :adapter
8
+
9
+ # @return [AutoPreload::Adapters::ActiveRecord, AutoPreload::Adapters::Serializer] The adapter to use.
10
+ def adapter
11
+ @adapter ||= AutoPreload::Adapters::ActiveRecord.new
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AutoPreload
4
+ # This class parses a string in the format "articles,comments" and returns an array of symbols.
5
+ class Resolver
6
+ MAX_ITERATIONS = 100
7
+
8
+ def initialize(options = {})
9
+ @iterations = 0
10
+ @options = options
11
+ @max_iterations = options[:max_iterations] || MAX_ITERATIONS
12
+ @adapter = AutoPreload.config.adapter
13
+ end
14
+
15
+ # Resolves a string as an array of symbols or hashes.
16
+ #
17
+ # @param query [ActiveRecord::Base, ActiveRecord::Relation]
18
+ # @param inclusions [String, Array<String>]
19
+ # @return [Array<Symbol, Hash>]
20
+ def resolve(query, inclusions)
21
+ model = query.respond_to?(:klass) ? query.klass : query
22
+
23
+ format_output(run_resolve(model, inclusions, root: true))
24
+ end
25
+
26
+ protected
27
+
28
+ # @param model [ActiveRecord::Base]
29
+ # @param inclusions [String, Array<String>]
30
+ # @return [Array<Symbol, Hash>]
31
+ def run_resolve(model, inclusions, root: false)
32
+ inclusions = inclusions.split(",") if inclusions.is_a? String
33
+ inclusions.flat_map { |item| parse_association(model, item, root: root) }
34
+ end
35
+
36
+ # @param list [Array<Symbol, Hash>]
37
+ # @return [Array<Symbol, Hash>]
38
+ def format_output(list)
39
+ list = list.compact.uniq # .sort { |a, _b| a.is_a?(Hash) ? 1 : -1 }
40
+ symbols = list.select { |item| item.is_a? Symbol }
41
+ objects = merge(list.select { |item| item.is_a? Hash })
42
+
43
+ objects.present? ? (symbols << objects) : symbols
44
+ end
45
+
46
+ # @param list [Array<Hash>]
47
+ # @return [Hash]
48
+ def merge(objects)
49
+ objects.reduce({}) do |result, object|
50
+ result.merge(object) do |_, old_value, new_value|
51
+ (old_value + new_value).uniq
52
+ end
53
+ end
54
+ end
55
+
56
+ # @param model [ActiveRecord::Base]
57
+ # @param item [String, Symbol]
58
+ # @return [Symbol, Hash, Array<Hash>]
59
+ def parse_association(model, item, root: false)
60
+ if item == "*"
61
+ resolve_preloadables(model, root: root).map(&:name)
62
+ elsif item == "**"
63
+ recurse_associations(model, root: root)
64
+ elsif item.include?(".")
65
+ split_inclusions(model, item, root: root)
66
+ else
67
+ item = item.strip.underscore.to_sym
68
+ find_association(model, item, root: root) ? item : nil
69
+ end
70
+ end
71
+
72
+ # @param model [ActiveRecord::Base]
73
+ # @return [Array<Hash>]
74
+ def recurse_associations(model, root: false)
75
+ increase_iterations_count!
76
+
77
+ associations = resolve_preloadables(model, root: root)
78
+
79
+ associations.map do |association|
80
+ resolved = resolve(association.klass, "**")
81
+ resolved.present? ? { association.name.to_sym => resolved } : association.name.to_sym
82
+ end
83
+ end
84
+
85
+ # @param model [ActiveRecord::Base]
86
+ # @param inclusions [String]
87
+ # @return [Array<Hash>]
88
+ def split_inclusions(model, inclusions, root: false)
89
+ increase_iterations_count!
90
+
91
+ head, *tail = inclusions.split(".", 2)
92
+ head = head.strip.underscore.to_sym
93
+ child_model = find_association(model, head, root: root).klass
94
+ [{ head => resolve(child_model, tail[0]) }]
95
+ end
96
+
97
+ # @param model [ActiveRecord::Base]
98
+ # @return [Array<ActiveRecord::Reflection::AssociationReflection>]
99
+ def resolve_preloadables(model, root: false)
100
+ @adapter.resolve_preloadables(model, @options.merge(root: root))
101
+ end
102
+
103
+ # @param model [ActiveRecord::Base]
104
+ # @param name [Symbol]
105
+ # @return [nil, ActiveRecord::Reflection::AssociationReflection]
106
+ def find_association(model, name, root: false)
107
+ resolve_preloadables(model, root: root).find { |association| association.name == name.to_sym }
108
+ end
109
+
110
+ # @raise [RuntimeError]
111
+ def increase_iterations_count!
112
+ @iterations += 1
113
+ raise "Iterations limit reached (#{@iterations} of #{@max_iterations})" if @iterations > @max_iterations
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AutoPreload
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support"
4
+ require "active_record"
5
+
6
+ module AutoPreload
7
+ extend ActiveSupport::Autoload
8
+
9
+ autoload :ActiveRecord
10
+ autoload :Adapters
11
+ autoload :Resolver
12
+ autoload :Config
13
+
14
+ def self.configure
15
+ yield(config)
16
+ end
17
+
18
+ def self.config
19
+ @config ||= Config.new
20
+ end
21
+ end
22
+
23
+ ActiveRecord::Base.include AutoPreload::ActiveRecord
metadata ADDED
@@ -0,0 +1,106 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: auto_preload
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Mònade
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2022-11-30 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activerecord
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '5'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '8'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: '5'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '8'
33
+ - !ruby/object:Gem::Dependency
34
+ name: activesupport
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '5'
40
+ - - "<"
41
+ - !ruby/object:Gem::Version
42
+ version: '8'
43
+ type: :runtime
44
+ prerelease: false
45
+ version_requirements: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: '5'
50
+ - - "<"
51
+ - !ruby/object:Gem::Version
52
+ version: '8'
53
+ description: A gem to run nested preloads/includes from string.
54
+ email:
55
+ - team@monade.io
56
+ executables: []
57
+ extensions: []
58
+ extra_rdoc_files: []
59
+ files:
60
+ - ".editorconfig"
61
+ - ".github/workflows/codeql-analysis.yml"
62
+ - ".github/workflows/gem-push.yml"
63
+ - ".github/workflows/test.yml"
64
+ - ".gitignore"
65
+ - ".rspec"
66
+ - ".rubocop.yml"
67
+ - Gemfile
68
+ - LICENSE
69
+ - README.md
70
+ - Rakefile
71
+ - auto_preload.gemspec
72
+ - lib/auto_preload.rb
73
+ - lib/auto_preload/active_record.rb
74
+ - lib/auto_preload/adapters.rb
75
+ - lib/auto_preload/adapters/active_record.rb
76
+ - lib/auto_preload/adapters/serializer.rb
77
+ - lib/auto_preload/config.rb
78
+ - lib/auto_preload/resolver.rb
79
+ - lib/auto_preload/version.rb
80
+ homepage: https://github.com/monade/auto_preload
81
+ licenses:
82
+ - MIT
83
+ metadata:
84
+ homepage_uri: https://github.com/monade/auto_preload
85
+ source_code_uri: https://github.com/monade/auto_preload
86
+ changelog_uri: https://github.com/monade/auto_preload/CHANGELOG.md
87
+ post_install_message:
88
+ rdoc_options: []
89
+ require_paths:
90
+ - lib
91
+ required_ruby_version: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '2.7'
96
+ required_rubygems_version: !ruby/object:Gem::Requirement
97
+ requirements:
98
+ - - ">="
99
+ - !ruby/object:Gem::Version
100
+ version: '0'
101
+ requirements: []
102
+ rubygems_version: 3.2.33
103
+ signing_key:
104
+ specification_version: 4
105
+ summary: A gem to run nested preloads/includes from string.
106
+ test_files: []