blueprinter-activerecord 1.1.0 → 1.3.0

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