blueprinter-activerecord 1.0.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: 65c4c6d27e0bea2ed2d73f35949587cf6f2197cd540fe2a1ceef1a49d2d135d8
4
+ data.tar.gz: 850d61584f87c743478d496ce1117cdd2bda7b6db7d92a582cb875f7d63c4f70
5
+ SHA512:
6
+ metadata.gz: 40c750e1e67710a3e695a495be6702393f6f72f145ebf47cc21d78587c34cb14ae899edb4cb76a31c599bdb075b5f93c490ff7eb7f5ddc9d16bf15e22314f544
7
+ data.tar.gz: 531d150f3cc8aa474d8c149ae35663395cf0893e406953cf0174cd06fc77ff92f8c7c1cd4ccbaa63a054b7ba930fcb92efd4bf501c6323e1f6033d8a40787053
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BlueprinterActiveRecord
4
+ #
5
+ # A Blueprinter extension to log what preloads were found and added by the BlueprinterActiveRecord::Preloader extension.
6
+ #
7
+ # This extension may safely be used alongside the BlueprinterActiveRecord::MissingPreloadsLogger extension. Each query will
8
+ # only be processed by one.
9
+ #
10
+ # NOTE Only queries that pass through a Blueprint's "render" method will be found.
11
+ #
12
+ # Blueprinter.configure do |config|
13
+ # # The Preloader extension MUST be added first!
14
+ # config.extensions << BlueprinterActiveRecord::Preloader.new
15
+ #
16
+ # config.extensions << BlueprinterActiveRecord::AddedPreloadsLogger.new do |info|
17
+ # next unless info.found.any?
18
+ #
19
+ # Rails.logger.info({
20
+ # event: "added_preloads",
21
+ # root_model: info.query.model.name,
22
+ # sql: info.query.to_sql,
23
+ # added: info.found.map { |x| x.join " > " },
24
+ # percent_added: info.percent_found,
25
+ # trace: info.trace,
26
+ # }.to_json)
27
+ # end
28
+ # end
29
+ #
30
+ class AddedPreloadsLogger < Blueprinter::Extension
31
+ include Helpers
32
+
33
+ #
34
+ # Initialize and configure the extension.
35
+ #
36
+ # @yield [BlueprinterActiveRecord::PreloadInfo] Your logging action
37
+ #
38
+ def initialize(&log_proc)
39
+ @log_proc = log_proc
40
+ end
41
+
42
+ def pre_render(object, blueprint, view, options)
43
+ if object.is_a?(ActiveRecord::Relation) && object.before_preload_blueprint
44
+ from_code = object.before_preload_blueprint
45
+ from_blueprint = Preloader.preloads(blueprint, view, object.model)
46
+ info = PreloadInfo.new(object, from_code, from_blueprint, caller)
47
+ @log_proc&.call(info)
48
+ end
49
+ object
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BlueprinterActiveRecord
4
+ module Helpers
5
+ extend self
6
+
7
+ #
8
+ # Combines all types of preloads (preload, includes, eager_load) into a single nested hash
9
+ #
10
+ # @param q [ActiveRecord::Relation]
11
+ # @return [Hash] Symbol keys with Hash values of arbitrary depth
12
+ #
13
+ def extract_preloads(q)
14
+ merge_values [*q.values[:preload], *q.values[:includes], *q.values[:eager_load]]
15
+ end
16
+
17
+ #
18
+ # Count the number of preloads in a nested Hash.
19
+ #
20
+ # @param preloads [Hash] Nested Hash of preloads
21
+ # @return [Integer] The number of associations in the Hash
22
+ #
23
+ def count_preloads(preloads)
24
+ preloads.reduce(0) { |acc, (_key, val)|
25
+ acc + 1 + count_preloads(val)
26
+ }
27
+ end
28
+
29
+ #
30
+ # Finds preloads from 'after' that are missing in 'before'.
31
+ #
32
+ # @param before [Hash] The extracted preloads from before Preloader ran
33
+ # @param after [Hash] The extracted preloads from after Preloader ran
34
+ # @param diff [Array<BlueprinterActiveRecord::MissingPreload>] internal use
35
+ # @param path [Array<Symbol>] internal use
36
+ # @return [Array<Array<Symbol>>] the preloads missing from 'before' . They're in a "path" structure, with the last element of each sub-array being the missing preload, e.g. `[[:widget], [:project, :company]]`
37
+ #
38
+ def diff_preloads(before, after, diff = [], path = [])
39
+ after.each_with_object(diff) do |(key, after_val), obj|
40
+ sub_path = path + [key]
41
+ before_val = before[key]
42
+ obj << sub_path if before_val.nil?
43
+ diff_preloads(before_val || {}, after_val, diff, sub_path)
44
+ end
45
+ end
46
+
47
+ #
48
+ # Merges 'values', which may be any nested structure of arrays, hashes, strings, and symbols into a nested hash.
49
+ #
50
+ # @param value [Array|Hash|String|Symbol]
51
+ # @param result [Hash]
52
+ # @return [Hash] Symbol keys with Hash values of arbitrary depth
53
+ #
54
+ def merge_values(value, result = {})
55
+ case value
56
+ when Array
57
+ value.each { |val| merge_values(val, result) }
58
+ when Hash
59
+ value.each { |key, val|
60
+ key = key.to_sym
61
+ result[key] ||= {}
62
+ merge_values(val, result[key])
63
+ }
64
+ when Symbol
65
+ result[value] ||= {}
66
+ when String
67
+ result[value.to_sym] ||= {}
68
+ else
69
+ raise ArgumentError, "Unexpected value of type '#{value.class.name}' (#{value.inspect})"
70
+ end
71
+ result
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BlueprinterActiveRecord
4
+ #
5
+ # A Blueprinter extension to log what COULD have been preloaded with the BlueprinterActiveRecord::Preloader extension.
6
+ #
7
+ # This extension may safely be used alongside the BlueprinterActiveRecord::Preloader and BlueprinterActiveRecord::AddedPreloadsLogger
8
+ # extensions. Any queries processed by those extensions will be ignored by this one.
9
+ #
10
+ # NOTE Only queries that pass through a Blueprint's "render" method will be found.
11
+ #
12
+ # Blueprinter.configure do |config|
13
+ # config.extensions << BlueprinterActiveRecord::MissingPreloadsLogger.new do |info|
14
+ # next unless info.found.any?
15
+ #
16
+ # Rails.logger.info({
17
+ # event: "missing_preloads",
18
+ # root_model: info.query.model.name,
19
+ # sql: info.query.to_sql,
20
+ # missing: info.found.map { |x| x.join " > " },
21
+ # percent_missing: info.percent_found,
22
+ # trace: info.trace,
23
+ # }.to_json)
24
+ # end
25
+ # end
26
+ #
27
+ class MissingPreloadsLogger < Blueprinter::Extension
28
+ include Helpers
29
+
30
+ #
31
+ # Initialize and configure the extension.
32
+ #
33
+ # @yield [BlueprinterActiveRecord::PreloadInfo] Your logging action
34
+ #
35
+ def initialize(&log_proc)
36
+ @log_proc = log_proc
37
+ end
38
+
39
+ def pre_render(object, blueprint, view, options)
40
+ if object.is_a?(ActiveRecord::Relation) && !object.before_preload_blueprint
41
+ from_code = extract_preloads object
42
+ from_blueprint = Preloader.preloads(blueprint, view, object.model)
43
+ info = PreloadInfo.new(object, from_code, from_blueprint, caller)
44
+ @log_proc&.call(info)
45
+ end
46
+ object
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BlueprinterActiveRecord
4
+ #
5
+ # Info about preloads from a query that was run through a Blueprinter's render method.
6
+ #
7
+ # Used for logging by the BlueprinterActiveRecord::MissingPreloadsLogger and BlueprinterActiveRecord::AddedPreloadsLogger extensions.
8
+ #
9
+ class PreloadInfo
10
+ include Helpers
11
+
12
+ # @return [ActiveRecord::Relation] The base query
13
+ attr_reader :query
14
+
15
+ # @return [Array<String>] Stack trace to the query
16
+ attr_reader :trace
17
+
18
+ #
19
+ # @param query [ActiveRecord::Relation] The query passed to "render"
20
+ # @param from_code [Hash] Nested Hash of preloads, includes, and eager_loads that were present in query when passed to "render"
21
+ # @param from_blueprint [Hash] Nested Hash of associations pulled from the Blueprint view
22
+ # @param trace [Array<String>] Stack trace to query
23
+ #
24
+ def initialize(query, from_code, from_blueprint, trace)
25
+ @query = query
26
+ @from_code = from_code
27
+ @from_blueprint = from_blueprint
28
+ @trace = trace
29
+ end
30
+
31
+ # @return [Integer] The percent of total preloads found by BlueprinterActiveRecord
32
+ def percent_found
33
+ total = num_existing + found.size
34
+ ((found.size / num_existing.to_f) * 100).round
35
+ end
36
+
37
+ # @return [Integer] The number of preloads, includes, and eager_loads that existed before BlueprinterActiveRecord was involved
38
+ def num_existing
39
+ @num_existing ||= count_preloads(hash)
40
+ end
41
+
42
+ # @return [Array<Array<Symbol>>] Array of "preload paths" (e.g. [[:project, :company]]) to missing preloads that could have been found & added by BlueprinterActiveRecord::Preloader
43
+ def found
44
+ @found ||= diff_preloads(@from_code, hash)
45
+ end
46
+
47
+ # @return [Array<Array<Symbol>>] Array of "preload paths" (e.g. [[:project, :company]]) from the blueprint that were visible to the preloader
48
+ def visible
49
+ @visible ||= diff_preloads({}, @from_blueprint)
50
+ end
51
+
52
+ # @return [Hash] Nested hash of all preloads, both manually added and auto found
53
+ def hash
54
+ @hash ||= merge_values [@from_code, @from_blueprint]
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BlueprinterActiveRecord
4
+ # A Blueprinter extension to automatically preload a Blueprint view's ActiveRecord associations during render
5
+ class Preloader < Blueprinter::Extension
6
+ include Helpers
7
+
8
+ attr_reader :use, :auto, :auto_proc
9
+
10
+ #
11
+ # Initialize and configure the extension.
12
+ #
13
+ # @param auto [true|false] When true, preload for EVERY ActiveRecord::Relation passed to a Blueprint
14
+ # @param use [:preload|:includes] When `auto` is true, use this method (e.g. :preload) for preloading
15
+ # @yield [Object, Class, Symbol, Hash] Instead of passing `auto` as a boolean, you may define a block that accepts the object to render, the blueprint class, the view, and options. If the block returns true, auto preloading will take place.
16
+ #
17
+ def initialize(auto: false, use: :preload, &auto_proc)
18
+ @auto = auto
19
+ @auto_proc = auto_proc
20
+ @use =
21
+ case use
22
+ when :preload, :includes
23
+ use
24
+ else
25
+ raise ArgumentError, "Unknown value '#{use.inspect}' for `BlueprinterActiveRecord::Preloader` argument 'use'. Valid values are :preload, :includes."
26
+ end
27
+ end
28
+
29
+ #
30
+ # Implements the "pre_render" Blueprinter Extension to preload associations from a view.
31
+ # If auto is true, all ActiveRecord::Relation objects will be preloaded. If auto is false,
32
+ # only queries that have called `.preload_blueprint` will be preloaded.
33
+ #
34
+ # NOTE: If auto is on, *don't* be concerned that you'll end up with duplicate preloads. Even if
35
+ # the query ends up with overlapping members in 'preload' and 'includes', ActiveRecord
36
+ # intelligently handles them. There are several unit tests which confirm this behavior.
37
+ #
38
+ def pre_render(object, blueprint, view, options)
39
+ case object
40
+ when ActiveRecord::Relation
41
+ if object.preload_blueprint_method || auto || auto_proc&.call(object, blueprint, view, options) == true
42
+ object.before_preload_blueprint = extract_preloads object
43
+ blueprint_preloads = self.class.preloads(blueprint, view, object.model)
44
+ loader = object.preload_blueprint_method || use
45
+ object.public_send(loader, blueprint_preloads)
46
+ else
47
+ object
48
+ end
49
+ else
50
+ object
51
+ end
52
+ end
53
+
54
+ private
55
+
56
+ #
57
+ # Returns an ActiveRecord preload plan extracted from the Blueprint and view (recursive).
58
+ #
59
+ # Example:
60
+ #
61
+ # preloads = BlueprinterActiveRecord::Preloader.preloads(WidgetBlueprint, :extended, Widget)
62
+ # q = Widget.where(...).order(...).preload(preloads)
63
+ #
64
+ # @param blueprint [Class] The Blueprint class
65
+ # @param view_name [Symbol] Name of the view in blueprint
66
+ # @param model [Class] The ActiveRecord model class that blueprint represents
67
+ # @return [Hash] A Hash containing preload/eager_load/etc info for ActiveRecord
68
+ #
69
+ def self.preloads(blueprint, view_name, model=nil)
70
+ view = blueprint.reflections.fetch(view_name)
71
+ view.associations.each_with_object({}) { |(_name, assoc), acc|
72
+ ref = model ? model.reflections[assoc.name.to_s] : nil
73
+ if (ref || model.nil?) && !assoc.blueprint.is_a?(Proc)
74
+ ref_model = ref && !(ref.belongs_to? && ref.polymorphic?) ? ref.klass : nil
75
+ acc[assoc.name] = preloads(assoc.blueprint, assoc.view, ref_model)
76
+ end
77
+ }
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BlueprinterActiveRecord
4
+ module QueryMethods
5
+ module Delegates
6
+ def preload_blueprint(blueprint = nil, view = :default, use: :preload)
7
+ all.preload_blueprint(blueprint, view, use: use)
8
+ end
9
+ end
10
+
11
+ ACTIONS = %i(preload eager_load includes).freeze
12
+
13
+ #
14
+ # Automatically preload (or `eager_load` or `includes`) the associations in the given
15
+ # blueprint and view (recursively).
16
+ #
17
+ # You can have the Blueprint and view autodetected on render:
18
+ #
19
+ # q = Widget.where(...).preload_blueprint
20
+ # WidgetBlueprint.render(q, view: :extended)
21
+ #
22
+ # Or you can pass them up front:
23
+ #
24
+ # widgets = Widget.where(...).preload_blueprint(WidgetBlueprint, :extended).to_a
25
+ # # do something with widgets, then render
26
+ # WidgetBlueprint.render(widgets, view: :extended)
27
+ #
28
+ # @param blueprint [Class] The Blueprinter class to use (ignore to autodetect on render)
29
+ # @param view [Symbol] The Blueprinter view name to use (ignore to autodetect on render)
30
+ # @param use [Symbol] The eager loading strategy to use (:preload, :includes, :eager_load)
31
+ # @return [ActiveRecord::Relation]
32
+ #
33
+ def preload_blueprint(blueprint = nil, view = :default, use: :preload)
34
+ spawn.preload_blueprint!(blueprint, view, use: use)
35
+ end
36
+
37
+ # See preload_blueprint
38
+ def preload_blueprint!(blueprint = nil, view = :default, use: :preload)
39
+ unless ACTIONS.include? use
40
+ valid = ACTIONS.map(&:inspect).join(", ")
41
+ raise BlueprinterError, "Unknown `preload_blueprint` method :#{use}. Valid methods are #{valid}."
42
+ end
43
+
44
+ if blueprint and view
45
+ # preload right now
46
+ preloads = Preloader.preloads(blueprint, view, model)
47
+ public_send(use, preloads)
48
+ else
49
+ # preload during render
50
+ @values[:preload_blueprint_method] = use
51
+ self
52
+ end
53
+ end
54
+
55
+ def preload_blueprint_method
56
+ @values[:preload_blueprint_method]
57
+ end
58
+
59
+ # Get the preloads present before the Preloader extension ran (internal, for PreloadLogger)
60
+ def before_preload_blueprint
61
+ @values[:before_preload_blueprint]
62
+ end
63
+
64
+ # Set the preloads present before the Preloader extension ran (internal, for PreloadLogger)
65
+ def before_preload_blueprint=(val)
66
+ @values[:before_preload_blueprint] = val
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,7 @@
1
+ module BlueprinterActiveRecord
2
+ class Railtie < Rails::Railtie
3
+ rake_tasks do
4
+ Dir[File.join(File.dirname(__FILE__), "..", "tasks/*.rake")].each { |f| load f }
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BlueprinterActiveRecord
4
+ VERSION = "1.0.0"
5
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'blueprinter'
4
+ require 'active_record'
5
+
6
+ module BlueprinterActiveRecord
7
+ autoload :QueryMethods, 'blueprinter-activerecord/query_methods'
8
+ autoload :Preloader, 'blueprinter-activerecord/preloader'
9
+ autoload :AddedPreloadsLogger, 'blueprinter-activerecord/added_preloads_logger'
10
+ autoload :MissingPreloadsLogger, 'blueprinter-activerecord/missing_preloads_logger'
11
+ autoload :PreloadInfo, 'blueprinter-activerecord/preload_info'
12
+ autoload :Helpers, 'blueprinter-activerecord/helpers'
13
+ autoload :Version, 'blueprinter-activerecord/version'
14
+ end
15
+
16
+ ActiveRecord::Relation.send(:include, BlueprinterActiveRecord::QueryMethods)
17
+ ActiveRecord::Base.extend(BlueprinterActiveRecord::QueryMethods::Delegates)
18
+
19
+ require 'blueprinter-activerecord/railtie' if defined? Rails::Railtie
@@ -0,0 +1,21 @@
1
+ namespace :blueprinter do
2
+ namespace :activerecord do
3
+ desc "Prints the preload plan"
4
+ task :preloads, [:blueprint, :view, :model] => :environment do |_, args|
5
+ def pretty(hash, indent = 0)
6
+ s = " " * indent
7
+ buff = "{"
8
+ hash.each { |key, val|
9
+ buff << "\n#{s} :#{key} => #{pretty val, indent + 2},"
10
+ }
11
+ buff << "\n#{s}" if hash.any?
12
+ buff << "}"
13
+ end
14
+
15
+ model = args[:model].constantize
16
+ blueprint = args[:blueprint].constantize
17
+ preloads = BlueprinterActiveRecord::Preloader.preloads(blueprint, args[:view].to_sym, model)
18
+ puts pretty preloads
19
+ end
20
+ end
21
+ end
metadata ADDED
@@ -0,0 +1,162 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: blueprinter-activerecord
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Procore Technologies, Inc.
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-02-09 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: '6.0'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '7.2'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: '6.0'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '7.2'
33
+ - !ruby/object:Gem::Dependency
34
+ name: blueprinter
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '1.0'
40
+ type: :runtime
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '1.0'
47
+ - !ruby/object:Gem::Dependency
48
+ name: appraisal
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '2.5'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '2.5'
61
+ - !ruby/object:Gem::Dependency
62
+ name: database_cleaner
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '2.0'
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '2.0'
75
+ - !ruby/object:Gem::Dependency
76
+ name: minitest
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '5.0'
82
+ type: :development
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '5.0'
89
+ - !ruby/object:Gem::Dependency
90
+ name: rake
91
+ requirement: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: '13.0'
96
+ type: :development
97
+ prerelease: false
98
+ version_requirements: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - "~>"
101
+ - !ruby/object:Gem::Version
102
+ version: '13.0'
103
+ - !ruby/object:Gem::Dependency
104
+ name: sqlite3
105
+ requirement: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: '1.4'
110
+ type: :development
111
+ prerelease: false
112
+ version_requirements: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - "~>"
115
+ - !ruby/object:Gem::Version
116
+ version: '1.4'
117
+ description: Eager loading and other ActiveRecord helpers for Blueprinter
118
+ email:
119
+ - opensource@procore.com
120
+ executables: []
121
+ extensions: []
122
+ extra_rdoc_files: []
123
+ files:
124
+ - lib/blueprinter-activerecord.rb
125
+ - lib/blueprinter-activerecord/added_preloads_logger.rb
126
+ - lib/blueprinter-activerecord/helpers.rb
127
+ - lib/blueprinter-activerecord/missing_preloads_logger.rb
128
+ - lib/blueprinter-activerecord/preload_info.rb
129
+ - lib/blueprinter-activerecord/preloader.rb
130
+ - lib/blueprinter-activerecord/query_methods.rb
131
+ - lib/blueprinter-activerecord/railtie.rb
132
+ - lib/blueprinter-activerecord/version.rb
133
+ - lib/tasks/blueprinter_activerecord.rake
134
+ homepage: https://github.com/procore-oss/blueprinter-activerecord
135
+ licenses:
136
+ - MIT
137
+ metadata:
138
+ homepage_uri: https://github.com/procore-oss/blueprinter-activerecord
139
+ source_code_uri: https://github.com/procore-oss/blueprinter-activerecord
140
+ changelog_uri: https://github.com/procore-oss/blueprinter-activerecord/CHANGELOG.md
141
+ allowed_push_host: https://rubygems.org
142
+ rubygems_mfa_required: 'true'
143
+ post_install_message:
144
+ rdoc_options: []
145
+ require_paths:
146
+ - lib
147
+ required_ruby_version: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - ">="
150
+ - !ruby/object:Gem::Version
151
+ version: '2.7'
152
+ required_rubygems_version: !ruby/object:Gem::Requirement
153
+ requirements:
154
+ - - ">="
155
+ - !ruby/object:Gem::Version
156
+ version: '0'
157
+ requirements: []
158
+ rubygems_version: 3.4.19
159
+ signing_key:
160
+ specification_version: 4
161
+ summary: Extensions for using ActiveRecord with ActiveRecord
162
+ test_files: []