-
Notifications
You must be signed in to change notification settings - Fork 64
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
Conditional callbacks #483
Comments
Found where those were removed b684ad3#diff-4b25d19af16746ff3a5cdb80fbd5f3a228d0385a7a197fd42d3a6bb4e0da6622R117 So we had before_create and before_update. There has been some talk on making a SaveOperation only run for a create or an update. I think that might cover this portion. |
I think for the For example:
For skipping in a type-safe wayI think we could do something similar to what was done for Actions https://github.com/luckyframework/lucky/blob/dd06b41926da1a162732c4c0d3ff83c22018543e/src/lucky/action_pipes.cr#L11-L23 It goes through an array of callbacks and raises if you try to skip a callback that doesn't exist. This means we may need to create an array to store a list of callbacks. I don't think we have that yet I don't mind that the method is simply redefined to "skip" a callback. I think if you do your examples that's just something you shouldn't do haha. I'd also bet most people won't do it. But we can always change it if people run into it. IMO though it's not worth the work since I think most people will not do that. We should document the edge case in the guides and say "if you do this it will redefine the callback, just FYI" Nervous about skipping callbacksThis could be a way to spaghetti things up if people aren't careful. For example, people might add a bunch of callbacks to a base class and then skip them in operations, when really it'd be clearer to leave them out of the base class and add them just to operations that need them. I think skipping callbacks is fine when used sparingly, but we just need to make sure we document this when documenting "skipping callbacks". Something like, "prefer putting callbacks in the operations that need them, rather than in a base class. Reserve callbacks in base classes for things that should almost always run for all operations. Otherwise it can lead to a lot of confusion about what callbacks are actually running when you save an operation" |
Yeah, I wasn't really a fan of this syntax class SaveUser < User::SaveOperation
before_save :set_thing
private def set_thing
unless responds_to?(:user) && user.admin?
# skips this callback
end
end
end And I guess you could just override the method in a subclass if you need. My original plan with the skipping was based on if you wanted to have class SaveUser < User::SaveOperation
end
class CreateUser < SaveUser
permit_columns email, username
end
class UpdateUser < SaveUser
# Assuming this is something SaveUser had set
skip_before_save :validate_required
end Maybe I should remove those skips for now, and then we can work out some specs on how to handle a skip in this sort of manner where we just override some method where needed? |
I actually really like being able to define short guard clauses with callbacks. It helps me to avoid jumping around in a file for a long time just to figure out where my object is ending up. Say I have an order processing operation: class ProcessOrder < Order::SaveOperation
before_save :normalize_card_number, if: :paid_with_card?
before_save :determine_tax_strategy, if: :user_is_outside_united_states?
before_save :validate_shipping_address, if: :order_requires_fulfillment?
private def paid_with_card?
order.payment_method == "card"
end
private def user_is_outside_united_states?
order.payee.country_code == "US"
end
private def order_requires_fulfillment?
order.contains_physical_goods?
end
end I prefer being able to essentially ignore all of those private methods, since I'm able to determine exactly what will prevent each of those callbacks from firing based on the method name. If I'm debugging an issue with a PayPal order that contains digital goods, I can safely skip thinking about I remember finding this concept really intuitive when I ran into it in Rails. I also think that using a symbol will make it a bit easier to read through than using a Proc. From Paul in Discord, it also sounds like adding an |
Ok, doing that might actually be better than having class SaveUser < User::SaveOperation
before_save :validate_required_for_new_people, if: :new_user?
def new_user?
record.try &.new_record?
end
end
class CreateUser < SaveUser
permit_columns email, username
def new_user?
true
end
end
class UpdateUser < SaveUser
def new_user?
false
end
end This allows you to skip the before save where needed without adding in a new macro. If y'all are cool with it, then I'll remove the skips and add in this instead. |
I think that's a good idea. We can always add it back later if we need it and as we understand use cases better. For now the |
With the new addition of #481 we can now skip some callbacks. However, this implementation does have a drawback where it redefines the callback method to do nothing which means that if the skip callback is defined before the actual callback, then it won't actually be skipped.
Now, I don't think anyone would actually write code like either of these examples, but if we had conditional callbacks they might. Imagine something like this:
In this case, we may need to think about a system to register callbacks at the instance level. I kinda like what @matthewmcgarvey suggested here.
Now, I could have sworn we had some sort of conditional on the
before_save
before like where you could saybefore_save :whatever, on: :create
... but I couldn't find it. I wanted to see if there was a reason we removed it so if anyone remembers if we had that before, let me know.Another idea I just had, which may be a bit a crazy, is to change the callbacks to be segmented in to a more type-safe way... Just thinking out loud here, but what about this?
Obviously a "rough" sketch, and would be another major refactor, but worth a shot possibly...
The text was updated successfully, but these errors were encountered: