-
Notifications
You must be signed in to change notification settings - Fork 11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Customize loading behavior #14
Comments
Haha, that was exactly my main reason for writing this gem 💃
Yeah, it'd be perfect, but I'm afraid it might be difficult to achieve due to how AMS serializes data. You could however achieve a similar result by writing custom lazy loader class. So your serializer and loader would look somewhat like this: class UserSerializer < BaseSerializer
lazy_has_many :friends, loader: UserFriendsLoader.new
end
class UserFriendsLoader
def load(user, &block)
BatchLoader.for(user.id) do |user_ids, loader|
all_friends = load_all_friends(user_ids)
# Yield all loaded records so that the
# gem can prepare deeper lazy relationships for each of them
block&.call(all_friends)
resolve(user_ids, all_friends, loader)
end
end
end Btw, maybe it'd be possible to somehow extract a base loader class to help others, because I clearly see a following repeating pattern.
If you'd have any ideas based on your use cases let me know :) Also make sure to check the available loader classes, because it sounds like the gem might already have a loader that'd fit your needs. |
Ah, ok. That makes sense, I was missing why the block was necessary, but I think I'm following now. I think the main thing that is currently preventing us from being able to just use a loader class is that we need access to the scope, in addition to the object, for many of our loaders. Our use of scope is wrapped in a couple of methods added to our base serializer class, so it would actually be most beneficial to have access to an instance context. I wonder if it would be possible to pass ams_lazy_relationships/lib/ams_lazy_relationships/core.rb Lines 37 to 43 in 8adede8
If that is too heavy handed, then we would be able to make do with just also passing in |
I see your point. The latter idea sounds cleaner to me. Would you be able to provide one or two real-life use cases for batch loading with serialization context? Maybe we'd be able to invent something here :) |
Sorry for my radio silence. I was on a deadline to get this work done and my sense was that what would work best for us was not something that would necessarily be what you wanted for this gem. So we ultimately went with using a fork of this gem. That being said, there were a few changes that we made that might be able to be useful to you. You can see the changes we made here: master...willcosgrove:instance-access-patch I'll briefly walk through each commit's story:
Now I'll show you how we're using these changes in our app. We wrote our own loader definition that looks like this: class CustomLoader
def initialize(&loader_definition)
@loader_definition = loader_definition
end
def load(instance, _load_for, &preload)
instance.instance_exec(preload, &@loader_definition)
end
end So this allows us to write something like this in the serializer class PersonSerializer < BaseSerializer
lazy_has_many :received_challenges, loader: CustomLoader.new { |prefetch|
BatchLoader.for(person.id).batch(key: user_scoper, default_value: []) do |person_ids, loader|
challenges = V4::AcceptedChallenge.where(
receiver_id: person_ids,
owner_id: user_scoper.person.id
)
prefetch.call(challenges)
challenges.each do |challenge|
loader.call(challenge.receiver_id) do |received_challenges|
received_challenges << challenge
end
end
end
}
end In the code above In our actual use, we never use the class PersonSerializer < BaseSerializer
lazy_has_many :received_challenges do |prefetch|
# ...
end
end Additionally, we don't write the call to class PersonSerializer < BaseSerializer
lazy_has_many :received_challenges do |prefetch|
BatchLoaders::AcceptedChallengeLoader
.load_received_challenges_using_person_and_user_scoper(object, user_scoper, &prefetch)
end
end We would love to give back some of our changes to this project if they could be helpful. Let me know what you think about these changes, and if I could help by opening a PR implementing any or all of these changes. If you don't want any of them, that's no problem either. We're content with using our fork 😄 Thanks so much for making this gem, it's been very helpful! |
Awesome work 👏👏👏 I quickly read your ideas and they look interesting. I'll take a deeper look during the weekend and try to figure out the details. Thanks for sharing your code 🙇 |
Hey, I've read all your stuff and I have a few comments/questions:
In this case even if you don't explicitly send From: /Users/bajena/Code/priv/ams_lazy_relationships/lib/ams_lazy_relationships/core.rb @ line 41 AmsLazyRelationships::Core::Initializer#initialize:
38: def initialize(object, options = {})
39: super
40:
=> 41: binding.pry
42:
43: prefetch = options.fetch(:prefetch) do
44: self.class.include_directive_from_options(@instance_options)
45: end
46:
47: self.class.send(:load_all_lazy_relationships, object)
48: end
[1] pry(#<Level1Serializer0>)> self.class.include_directive_from_options(@instance_options)
=> #<JSONAPI::IncludeDirective:0x00007fda146a2d40 @hash={:*=>#<JSONAPI::IncludeDirective:0x00007fda146a2a48 @hash={}, @options={:allow_wildcard=>true}>}, @options={:allow_wildcard=>true}>
If you have time let me know what you think about those points I love how creative people can get when solving their problems. I'm happy that somebody managed to understand the 🍝 I wrote ;) Btw, I really like the way you solve problems and how quickly catch the concepts. Are you working on any other open source projects? Maybe you'd like to work on something together? Jan |
Hey Jan! Thanks for taking a thorough look at this! I'll respond in order:
😂 This gem is great! Definitely not 🍝 Thank you! No I haven't been. I opened sourced a few things several years back when I was first getting started developing. So they probably aren't very good 😄
Definitely! I'm always interested in solving a good problem! |
I'm closing this issue - I'll reopen if I decide to change the way how the gem works. Thanks for all your help @willcosgrove! |
We just recently started using ams_lazy_relationships on a project I've been working on, and it's been great!
We have lots of relationships though then end up being modified in the serializer for one reason or another, and that prevented us from being able to use the lazy association helpers for them. I started writing custom loaders (unrelated to this gem's concept of a Loader) that use BatchLoader to fetch these records like we need. Unfortunately I discovered that just using BatchLoader was not enough because AMS was trying to serialize the related object before it had called the loader for each nested record.
I would love to be able to hook into the breadth-first loading that you've got in this gem, but I haven't been able to work out what the best way to do that would be.
If I was dreaming about what ams_lazy_relationships could do to make this situation better, it would be great if I could just define my own method that returns a BatchLoader object. Something like this:
And ams_lazy_relationships would handle calling that method at the right time.
What do you think?
The text was updated successfully, but these errors were encountered: