auto_preload 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: []