blueprinter-activerecord 1.1.0 → 1.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1a433613eafed67560509b44a75f9d8468eb98addf5262865cef731b63c2175b
4
- data.tar.gz: 37ed9a65f70a441dfc23806b0c67bf7a1622170bb607f416e280a4739cc1c472
3
+ metadata.gz: dbbbb6e1ad95d2a01cfbea311786b853dbc9897b95e66aa7724f3c514d2c0c53
4
+ data.tar.gz: 1df369b58caf6f83b9c5779d3f111bb1484c5e1feb8e8738e494f20e6de25332
5
5
  SHA512:
6
- metadata.gz: f5ca1480aeaae70cef964408594dd4b72b12101925daf12d8233c0c454652c451c427a0e91c430ad18612d1bb47002496289caffdb0b8cdd48d8a537056fcb36
7
- data.tar.gz: ae53e10d27f7c93cab9920151bee1b3a10ed3d1b46b807b5420a297ee3cdfa2bae05ebae5f26ca1e5809616d05609771b5a9368537e9c980d43b6031e1f0622f
6
+ metadata.gz: f13e0c074ac8b7a3348aad0d80bdd98a37adc6505eb93bc2980992b93b6fc0f98c294c9800959a4820ed427e74b55b0ad991bc0ce33ef389ce68b763360e85c3
7
+ data.tar.gz: 9b3ebe8b99baba740f2fdfb369f9f8fe84d118506b72f9fc60253e2a61dce5ceb92078ddc490f9a59b3ef7e45b9969f62b1db6cd5f862d885c8985e46784f0c7
@@ -42,7 +42,7 @@ module BlueprinterActiveRecord
42
42
  def pre_render(object, blueprint, view, options)
43
43
  if object.is_a?(ActiveRecord::Relation) && object.before_preload_blueprint
44
44
  from_code = object.before_preload_blueprint
45
- from_blueprint = Preloader.preloads(blueprint, view, object.model)
45
+ from_blueprint = Preloader.preloads(blueprint, view, model: object.model)
46
46
  info = PreloadInfo.new(object, from_code, from_blueprint, caller)
47
47
  @log_proc&.call(info)
48
48
  end
@@ -39,7 +39,7 @@ module BlueprinterActiveRecord
39
39
  def pre_render(object, blueprint, view, options)
40
40
  if object.is_a?(ActiveRecord::Relation) && !object.before_preload_blueprint
41
41
  from_code = extract_preloads object
42
- from_blueprint = Preloader.preloads(blueprint, view, object.model)
42
+ from_blueprint = Preloader.preloads(blueprint, view, model: object.model)
43
43
  info = PreloadInfo.new(object, from_code, from_blueprint, caller)
44
44
  @log_proc&.call(info)
45
45
  end
@@ -4,6 +4,7 @@ module BlueprinterActiveRecord
4
4
  # A Blueprinter extension to automatically preload a Blueprint view's ActiveRecord associations during render
5
5
  class Preloader < Blueprinter::Extension
6
6
  include Helpers
7
+ DEFAULT_MAX_RECURSION = 10
7
8
 
8
9
  attr_reader :use, :auto, :auto_proc
9
10
 
@@ -37,11 +38,10 @@ module BlueprinterActiveRecord
37
38
  # intelligently handles them. There are several unit tests which confirm this behavior.
38
39
  #
39
40
  def pre_render(object, blueprint, view, options)
40
- case object.class.name
41
- when "ActiveRecord::Relation", "ActiveRecord::AssociationRelation"
41
+ if object.is_a?(ActiveRecord::Relation) && !object.loaded?
42
42
  if object.preload_blueprint_method || auto || auto_proc&.call(object, blueprint, view, options) == true
43
43
  object.before_preload_blueprint = extract_preloads object
44
- blueprint_preloads = self.class.preloads(blueprint, view, object.model)
44
+ blueprint_preloads = self.class.preloads(blueprint, view, model: object.model)
45
45
  loader = object.preload_blueprint_method || use
46
46
  object.public_send(loader, blueprint_preloads)
47
47
  else
@@ -63,22 +63,21 @@ module BlueprinterActiveRecord
63
63
  #
64
64
  # Example:
65
65
  #
66
- # preloads = BlueprinterActiveRecord::Preloader.preloads(WidgetBlueprint, :extended, Widget)
66
+ # preloads = BlueprinterActiveRecord::Preloader.preloads(WidgetBlueprint, :extended, model: Widget)
67
67
  # q = Widget.where(...).order(...).preload(preloads)
68
68
  #
69
69
  # @param blueprint [Class] The Blueprint class
70
70
  # @param view_name [Symbol] Name of the view in blueprint
71
- # @param model [Class] The ActiveRecord model class that blueprint represents
71
+ # @param model [Class|:polymorphic] The ActiveRecord model class that blueprint represents
72
+ # @param cycles [Hash<String, Integer>] (internal) Preloading will halt if recursion/cycles gets too high
72
73
  # @return [Hash] A Hash containing preload/eager_load/etc info for ActiveRecord
73
74
  #
74
- def self.preloads(blueprint, view_name, model=nil)
75
+ def self.preloads(blueprint, view_name, model:, cycles: {})
75
76
  view = blueprint.reflections.fetch(view_name)
76
77
  preload_vals = view.associations.each_with_object({}) { |(_name, assoc), acc|
77
78
  # look for a matching association on the model
78
- ref = model ? model.reflections[assoc.name.to_s] : nil
79
- if (ref || model.nil?) && !assoc.blueprint.is_a?(Proc)
80
- ref_model = ref && !(ref.belongs_to? && ref.polymorphic?) ? ref.klass : nil
81
- acc[assoc.name] = preloads(assoc.blueprint, assoc.view, ref_model)
79
+ if (preload = association_preloads(assoc, model, cycles))
80
+ acc[assoc.name] = preload
82
81
  end
83
82
 
84
83
  # look for a :preload option on the association
@@ -94,5 +93,38 @@ module BlueprinterActiveRecord
94
93
  end
95
94
  }
96
95
  end
96
+
97
+ def self.association_preloads(assoc, model, cycles)
98
+ max_cycles = assoc.options.fetch(:max_recursion, DEFAULT_MAX_RECURSION)
99
+ if model == :polymorphic
100
+ if assoc.blueprint.is_a? Proc
101
+ {}
102
+ else
103
+ cycles, count = count_cycles(assoc.blueprint, assoc.view, cycles)
104
+ count < max_cycles ? preloads(assoc.blueprint, assoc.view, model: model, cycles: cycles) : {}
105
+ end
106
+ elsif (ref = model.reflections[assoc.name.to_s])
107
+ if assoc.blueprint.is_a? Proc
108
+ {}
109
+ elsif ref.belongs_to? && ref.polymorphic?
110
+ cycles, count = count_cycles(assoc.blueprint, assoc.view, cycles)
111
+ count < max_cycles ? preloads(assoc.blueprint, assoc.view, model: :polymorphic, cycles: cycles) : {}
112
+ else
113
+ cycles, count = count_cycles(assoc.blueprint, assoc.view, cycles)
114
+ count < max_cycles ? preloads(assoc.blueprint, assoc.view, model: ref.klass, cycles: cycles) : {}
115
+ end
116
+ end
117
+ end
118
+
119
+ def self.count_cycles(blueprint, view, cycles)
120
+ id = "#{blueprint.name || blueprint.inspect}/#{view}"
121
+ cycles = cycles.dup
122
+ if cycles[id].nil?
123
+ cycles[id] = 0
124
+ else
125
+ cycles[id] += 1
126
+ end
127
+ return cycles, cycles[id]
128
+ end
97
129
  end
98
130
  end
@@ -43,7 +43,7 @@ module BlueprinterActiveRecord
43
43
 
44
44
  if blueprint and view
45
45
  # preload right now
46
- preloads = Preloader.preloads(blueprint, view, model)
46
+ preloads = Preloader.preloads(blueprint, view, model: model)
47
47
  public_send(use, preloads)
48
48
  else
49
49
  # preload during render
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module BlueprinterActiveRecord
4
- VERSION = "1.1.0"
4
+ VERSION = "1.3.0"
5
5
  end
@@ -14,7 +14,7 @@ namespace :blueprinter do
14
14
 
15
15
  model = args[:model].constantize
16
16
  blueprint = args[:blueprint].constantize
17
- preloads = BlueprinterActiveRecord::Preloader.preloads(blueprint, args[:view].to_sym, model)
17
+ preloads = BlueprinterActiveRecord::Preloader.preloads(blueprint, args[:view].to_sym, model: model)
18
18
  puts pretty preloads
19
19
  end
20
20
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: blueprinter-activerecord
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Procore Technologies, Inc.
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-06-11 00:00:00.000000000 Z
11
+ date: 2024-09-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -17,9 +17,6 @@ dependencies:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: '6.0'
20
- - - "<"
21
- - !ruby/object:Gem::Version
22
- version: '7.2'
23
20
  type: :runtime
24
21
  prerelease: false
25
22
  version_requirements: !ruby/object:Gem::Requirement
@@ -27,9 +24,6 @@ dependencies:
27
24
  - - ">="
28
25
  - !ruby/object:Gem::Version
29
26
  version: '6.0'
30
- - - "<"
31
- - !ruby/object:Gem::Version
32
- version: '7.2'
33
27
  - !ruby/object:Gem::Dependency
34
28
  name: blueprinter
35
29
  requirement: !ruby/object:Gem::Requirement