blueprinter-activerecord 1.1.0 → 1.2.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: d4e873acea4991a8ea2761ca16f7564c8dd7ce10b1d0237a7f7fcc7068a64cac
4
+ data.tar.gz: 6398394fe88e9324ae285a9144cb07f1354a9195df196c48c99c011111f113ea
5
5
  SHA512:
6
- metadata.gz: f5ca1480aeaae70cef964408594dd4b72b12101925daf12d8233c0c454652c451c427a0e91c430ad18612d1bb47002496289caffdb0b8cdd48d8a537056fcb36
7
- data.tar.gz: ae53e10d27f7c93cab9920151bee1b3a10ed3d1b46b807b5420a297ee3cdfa2bae05ebae5f26ca1e5809616d05609771b5a9368537e9c980d43b6031e1f0622f
6
+ metadata.gz: c5b87d18310bcd54a677aeec768ca5697f427f267059c20f700def7bc7b386a185577a1f87bd15f87342e76bbe86ece3e472bace0626ad3d869678e64b614a9f
7
+ data.tar.gz: 511f52f12693ce69a8337a74fe7c31cf894d04427a4fa1538ca7570689c0961e8259d559435216c0be1e62417fc93d62f1944e9920c6fcdf4ad56a867f5ae3a2
@@ -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.2.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.2.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-06-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord