Skip to content

Commit

Permalink
Pass serialization scope to lazy loaders
Browse files Browse the repository at this point in the history
closes #41
  • Loading branch information
Bajena committed Dec 29, 2019
1 parent 26a2961 commit f1c4f9a
Show file tree
Hide file tree
Showing 9 changed files with 106 additions and 16 deletions.
2 changes: 1 addition & 1 deletion lib/ams_lazy_relationships/core.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ module Initializer
def initialize(*)
super

self.class.send(:load_all_lazy_relationships, object)
self.class.send(:load_all_lazy_relationships, object, scope)
end
end
end
20 changes: 12 additions & 8 deletions lib/ams_lazy_relationships/core/evaluation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,51 +13,55 @@ module Evaluation
#
# @param object [Object] Lazy relationships will be loaded for this record.
# @param level [Integer] Current nesting level
def load_all_lazy_relationships(object, level = NESTING_START_LEVEL)
def load_all_lazy_relationships(object, scope, level = NESTING_START_LEVEL)
return if level >= LAZY_NESTING_LEVELS
return unless object

return unless lazy_relationships

lazy_relationships.each_value do |lrm|
load_lazy_relationship(lrm, object, level)
load_lazy_relationship(lrm, object, scope, level)
end
end

# @param lrm [LazyRelationshipMeta] relationship data
# @param object [Object] Object to load the relationship for
# @param level [Integer] Current nesting level
def load_lazy_relationship(lrm, object, level = NESTING_START_LEVEL)
def load_lazy_relationship(lrm, object, scope, level = NESTING_START_LEVEL)
load_for_object = if lrm.load_for.present?
object.public_send(lrm.load_for)
else
object
end

lrm.loader.load(load_for_object) do |batch_records|
# Make sure that old custom loaders are still supported
loader_args = lrm.loader.method(:load).arity == 1 ? [load_for_object] : [load_for_object, scope]

lrm.loader.load(*loader_args) do |batch_records|
deep_load_for_yielded_records(
batch_records,
scope,
lrm,
level
)
end
end

def deep_load_for_yielded_records(batch_records, lrm, level)
def deep_load_for_yielded_records(batch_records, scope, lrm, level)
# There'll be no more nesting if there's no
# reflection for this relationship. We can skip deeper lazy loading.
return unless lrm.reflection

Array.wrap(batch_records).each do |r|
deep_load_for_yielded_record(r, lrm, level)
deep_load_for_yielded_record(r, scope, lrm, level)
end
end

def deep_load_for_yielded_record(batch_record, lrm, level)
def deep_load_for_yielded_record(batch_record, scope, lrm, level)
serializer = serializer_for(batch_record, lrm.reflection.options)
return unless serializer

serializer.send(:load_all_lazy_relationships, batch_record, level + 1)
serializer.send(:load_all_lazy_relationships, batch_record, scope, level + 1)
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def lazy_relationship(name, loader: nil, load_for: nil)
@lazy_relationships[name] = lrm

define_method :"lazy_#{name}" do
self.class.send(:load_lazy_relationship, lrm, object)
self.class.send(:load_lazy_relationship, lrm, object, scope)
end
end

Expand Down
2 changes: 1 addition & 1 deletion lib/ams_lazy_relationships/loaders/association.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def initialize(model_class_name, association_name)

attr_reader :model_class_name, :association_name

def load_data(records, loader)
def load_data(records, loader, scope)
::ActiveRecord::Associations::Preloader.new.preload(
records_to_preload(records), association_name
)
Expand Down
5 changes: 3 additions & 2 deletions lib/ams_lazy_relationships/loaders/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ module Loaders
class Base
# Lazy loads and yields the data when evaluating
# @param record [Object] an object for which we're loading the data
# @param scope [Object] serialization scope object.
# @param block [Proc] a block to execute when data is evaluated.
# Loaded data is yielded as a block argument.
def load(record, &block)
def load(record, scope = nil, &block)
BatchLoader.for(record).batch(
key: batch_key(record),
# Replacing methods can be costly, especially on objects with lots
Expand All @@ -18,7 +19,7 @@ def load(record, &block)
# https://github.com/exAspArk/batch-loader/tree/v1.4.1#replacing-methods
replace_methods: false
) do |records, loader|
data = load_data(records, loader)
data = load_data(records, loader, scope)

block&.call(data)
end
Expand Down
2 changes: 1 addition & 1 deletion lib/ams_lazy_relationships/loaders/direct.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def initialize(relationship_name, &load_block)

attr_reader :relationship_name, :load_block

def load_data(records, loader)
def load_data(records, loader, scope)
data = []
records.each do |r|
value = calculate_value(r)
Expand Down
2 changes: 1 addition & 1 deletion lib/ams_lazy_relationships/loaders/simple_belongs_to.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def initialize(

attr_reader :association_class_name, :foreign_key

def load_data(records, loader)
def load_data(records, loader, scope)
data_ids = records.map(&foreign_key).compact.uniq
data = if data_ids.present?
association_class_name.constantize.where(id: data_ids)
Expand Down
2 changes: 1 addition & 1 deletion lib/ams_lazy_relationships/loaders/simple_has_many.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def initialize(association_class_name, foreign_key:)

attr_reader :association_class_name, :foreign_key

def load_data(records, loader)
def load_data(records, loader, scope)
# Some records use UUID class as id - it's safer to cast them to strings
record_ids = records.map { |r| r.id.to_s }
association_class_name.constantize.where(
Expand Down
85 changes: 85 additions & 0 deletions spec/core_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -582,4 +582,89 @@ class BlogPostSerializer < BaseTestSerializer
expect(category_attributes).to match_array(%w[id created_at updated_at])
end
end

describe "using serialization scope" do
class BlogPostsLoader < AmsLazyRelationships::Loaders::Base
def load_data(records, loader, scope)
data = []

records.each do |r|
d = r.blog_posts
d = d.where(title: scope[:title]) if scope

loader.call(r, d)
data << d
end

data.flatten.compact.uniq
end

def batch_key(_)
"Key"
end
end

let(:level0_serializer_class) do
class Level1Serializer11 < BaseTestSerializer
end

class Level0Serializer11 < BaseTestSerializer
has_many :level1, serializer: Level1Serializer11 do |s|
s.lazy_level1
end
lazy_relationship :level1, loader: BlogPostsLoader.new
end

Level0Serializer11
end
let(:includes) { ["level1"] }

before do
level1_records[0].update!(title: "BP1")
end

context "when scope is present" do
let(:serializer) { level0_serializer_class.new(level0_record, scope: { title: "BP1" }) }

it "filters the data based on scope" do
expect(json.dig(:user, :level1).length).to eq(1)
expect(json.dig(:user, :level1, 0, :id)).to eq(level1_records[0].id)
end
end

context "when scope is blank" do
it "doesn't filter the data based on scope" do
expect(json.dig(:user, :level1).length).to eq(3)
end
end

context "when using deprecated loaders" do
let(:serializer) { level0_serializer_class.new(level0_record, scope: { title: "BP1" }) }

class DeprecatedBlogPostsLoader < BlogPostsLoader
def load(records, &block)
super(records, nil, &block)
end
end

let(:level0_serializer_class) do
class Level1Serializer12 < BaseTestSerializer
end

class Level0Serializer12 < BaseTestSerializer
has_many :level1, serializer: Level1Serializer12 do |s|
s.lazy_level1
end
lazy_relationship :level1, loader: DeprecatedBlogPostsLoader.new
end

Level0Serializer12
end


it "works correctly" do
expect(json.dig(:user, :level1).length).to eq(3)
end
end
end
end

0 comments on commit f1c4f9a

Please sign in to comment.