blueprinter-activerecord 1.0.2 → 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: 7bec64f8859b0d2cea3346675d4b2a3279245bcb5f7060b0ba0bfcf2f3bb4d83
4
- data.tar.gz: 4fe808fc8be1d043bc18d53dbe3e6a02c40904408ed51f6a8d0fbe0fc1349b2f
3
+ metadata.gz: d4e873acea4991a8ea2761ca16f7564c8dd7ce10b1d0237a7f7fcc7068a64cac
4
+ data.tar.gz: 6398394fe88e9324ae285a9144cb07f1354a9195df196c48c99c011111f113ea
5
5
  SHA512:
6
- metadata.gz: 3f20ed5466369e302e0e8d73bae2bade4fa92cd7efa6ae2bba63eb49494af7990cbc74d371f6c7f4efe782a94ae4d001fbc2dfdbce7a2358771ce6fdc748442c
7
- data.tar.gz: 86b01a869b55e52ac6cfbac55dacb149112a01409ec004d7c55e4ef79451d5716327aa4aa04b8777aef2d68e000498d66f04c813e25d098ad985948bc103ad77
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
@@ -57,25 +57,74 @@ module BlueprinterActiveRecord
57
57
  #
58
58
  # Returns an ActiveRecord preload plan extracted from the Blueprint and view (recursive).
59
59
  #
60
+ # Preloads are found when one of the model's associations matches:
61
+ # 1. A Blueprint association name.
62
+ # 2. A :preload option on a field or association.
63
+ #
60
64
  # Example:
61
65
  #
62
- # preloads = BlueprinterActiveRecord::Preloader.preloads(WidgetBlueprint, :extended, Widget)
66
+ # preloads = BlueprinterActiveRecord::Preloader.preloads(WidgetBlueprint, :extended, model: Widget)
63
67
  # q = Widget.where(...).order(...).preload(preloads)
64
68
  #
65
69
  # @param blueprint [Class] The Blueprint class
66
70
  # @param view_name [Symbol] Name of the view in blueprint
67
- # @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
68
73
  # @return [Hash] A Hash containing preload/eager_load/etc info for ActiveRecord
69
74
  #
70
- def self.preloads(blueprint, view_name, model=nil)
75
+ def self.preloads(blueprint, view_name, model:, cycles: {})
71
76
  view = blueprint.reflections.fetch(view_name)
72
- view.associations.each_with_object({}) { |(_name, assoc), acc|
73
- ref = model ? model.reflections[assoc.name.to_s] : nil
74
- if (ref || model.nil?) && !assoc.blueprint.is_a?(Proc)
75
- ref_model = ref && !(ref.belongs_to? && ref.polymorphic?) ? ref.klass : nil
76
- acc[assoc.name] = preloads(assoc.blueprint, assoc.view, ref_model)
77
+ preload_vals = view.associations.each_with_object({}) { |(_name, assoc), acc|
78
+ # look for a matching association on the model
79
+ if (preload = association_preloads(assoc, model, cycles))
80
+ acc[assoc.name] = preload
81
+ end
82
+
83
+ # look for a :preload option on the association
84
+ if (custom = assoc.options[:preload])
85
+ Helpers.merge_values custom, acc
77
86
  end
78
87
  }
88
+
89
+ # look for a :preload options on fields
90
+ view.fields.each_with_object(preload_vals) { |(_name, field), acc|
91
+ if (custom = field.options[:preload])
92
+ Helpers.merge_values custom, acc
93
+ end
94
+ }
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]
79
128
  end
80
129
  end
81
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.0.2"
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.0.2
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-05-30 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