-
Notifications
You must be signed in to change notification settings - Fork 21.4k
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
Add ActiveRecord::Base.suppressor #18847
Comments
Were you going to handle this one? (I'm willing to have a look-see if it's up for grabs, given the provided implementation.) |
@perceptec Have at it. |
Should we have an |
How would that one be used, Aaron?
|
In case something inside your
Where updating the comments on the destination needs to send a notification. I suppose you could just do this: module Copyable
def copy_to(destination)
comment = Notification.suppress { Comment.create }
destination.comments << comment
end
end
end But now this |
For that case, IMO, you'd do something like: module Copyable
def copy_to(destination)
Notification.suppress do
destination.comments << Comment.create
end
Notification.create # tell the admin
end
end Seems like it'd be a bit of a battle between the suppress/unsuppress otherwise. But maybe we just need to find a more compelling use case. |
Just a word of caution here - let's not replay the issues we've had over the years with scopes and modifying state on the model class. The implementation needs to be threadsafe and not subject to strange side effects of lazy evaluation falling outside of the suppressed block. |
👍 to that AW. Use Thread.current in the real implementation.
|
@dhh in your example it seems to me more appropriate to disable the after_create callbacks than to completely disable |
@cristianbica I'm not sure I follow. When I do a copy, I don't want Notifications to be created. These notifications are created by the Comment in an after_save, yes, but it may be doing lots of other stuff in callbacks that does need to happen. So you wouldn't want to do something like Comment.suppress(:callbacks) in this example. What's the use case you're imagining for having a difference between create and save? Suppress is the same name used elsewhere, like logging, to prevent something from being saved. So seems to fit from that angle. |
@dhh |
The dictionary definition seems to fit what we're doing to a tee: http://dictionary.reference.com/browse/suppress. I think it works with all sorts of classes. In my code base, I'm currently using three different versions of this is different places: Event.suppress, Mention.suppress, Notification.suppress. All those work for me. |
I'd appreciate some grown-up eyes on my last commit--I've tried to account for the threading discussion above (and wonder if it couldn't be more idiomatic in its current form, though that's a lesser consideration). |
What if, we use contexts to implement this feature instead of adding a new method and a new concept? class Comment < ActiveRecord::Base
belongs_to :commentable, polymorphic: true
after_create -> { Notification.create! comment: self, recipients: commendable.recipients },
except_on: :copying
end module Copyable
def copy_to(destination)
comment = Comment.new
comment.save(context: :copying)
end
end |
Then the comment has to know about all the concerns that might do stuff to it. That seems highly coupled.
|
Right, make sense. I have a bad feeling about this feature and I believe it will lead to a lot of bad usages but it is just a feeling at this point. I don't have any more arguments besides the one already presented by you in the issue description about the factories (or whatever people want to call that pattern). I already tried both approaches (the factory and the suppressing using contexts) in my applications and both went fine. I don't have a strong argument against suppressing so I'll stand back with my feelings and try this implementation. 👍 |
|
I’m using this pattern at the moment in Basecamp and like it. As a general rule, I don’t find “people might cut themselves with a sharp knife if they wield it wrong” as a good direction for design. Rails should be full of both spoons, forks, and also some sharp knifes.
|
👍 I was just trying to give a better shape to our Ginsu knife and not hurt ourselves leaving it without a handhold. |
Haha, it sounded like "they can't handle the Ginsu, give them a fisher price" :). If you don't mind tying your Comment implementation to a concern like Copyable, then context is a fine approach imo. I don't think comment should know about Copyable, unless it's Copyable itself (which in my case it is not). |
@dhh, if I want to suppress more than one model? And I think if we could do something like this, wouldn't it be simpler?
|
Sharvy, I prefer the suppression to be explcitly from the perspective of the model being suppressed. So: Notification.suppressed do
Unreads.suppressed do
copy
end
end |
@dhh got it. Thanks a lot. And yes your implementation makes sense, |
@rafaelfranca I was reviewing this issue and I would like to know if why you suggest of using context, is it already possible or should be implemented inside active_record/active_model? |
Common case vs. uncommon case is one factor. Another is the consequence of triggering the side effect: if you write a migration and accidentally notify all your users, is that something you can shrug off as inconsequential, or an embarrassing mistake that you could and should have avoided with different habits? To rephrase, you should weigh the inconvenience of having to write something like If the cost is relatively low as compared to what dangers you avoid, I think being safer by default is the sensible thing. |
@henrik I think the underlying thing is that you have a different view on people than Rails does. We trust them to handle that possibility of danger. But you want a fisher price knife, so to speak, and that's perfectly fine. That's just not what Rails wants. Thanks for writing. Your point has been heard, but we're going in a different direction ❤️ |
@kaspth I can definitely accept the general idea - this is of course just my opinion and not the only valid one. I don't share the view that Rails has a preference for trusting people. We're now HTML safe by default instead of trusting the person to always get it right. IIRC sanitize was changed to whitelist, not blacklist. I see this as related to that line of thinking. Rails DOES have a preference for callbacks in some cases where I do not – this change is certainly in line with that. Thanks for the thoughtful reply! ❤️ |
I agree with @rafaelfranca. This feature will be mis-used in quite a lot of Rails applications. Suppressing callbacks in this way should be something that the framework actively discourages, rather than encourages. I have worked with enough live Rails applications (that aren't just simple CRUD apps) to know that this feature is going to cause issues in the long run. From the examples given in the PR, this is what I understand it to be doing:
Notification.suppress do
# code that calls something like Comment.create
end This problem could just as easily be solved by not having the
def create
comment = Comment.new(comment_params)
if comment.save
Notification.create(thing: comment)
# etc.
end
end Both of these solutions are more preferable (to me, at least) than adding more code to the Rails framework which encourages "magical" behaviour. Both of the solutions I've provided make it explicit that when the comment is created, the notification is created. If Consider reverting this feature. It is going to cause headaches because it will be misused in Rails applications. |
You're a year late to the discussion and you didn't bring any new arguments to the party. Feel free to write your own code without callbacks, but this chef's knife is staying in the drawer. |
Honestly @radar has a fantastic point, and one I didn't see anywhere else in this thread. I would also strongly disagree with there being a time limit on discussing the merits of a public API feature. |
Locking this thread along with the other outlets. If you want to start a discussion on the mailing list to take this further, be welcome. All the same considerations apply. Arguments already considered aren't going sway things by being restated. But you're welcome to provide novel arguments. "This might get misused" was addressed today in the ninth pillar added to the doctrine: http://rubyonrails.org/doctrine/#provide-sharp-knives. So consider that line of argument addressed there. |
Here's a pattern of creating notifications when new comments are posted. (The notification may in turn trigger an email, a push notification, or just appear in the UI somewhere):
So that's what you want the bulk of the time. New comment creates a new Notification. But there may well be off cases, like copying a commentable and its comments, where you don't want that. So you'd have a concern something like this:
Here's a naive implementation of ActiveRecord::Base.suppress:
May well need something more sophisticated, but that's a starting point for this feature.
(Yes, you could also accomplish this by having a separate factory for CreateCommentWithNotificationsFactory and not use that for copy. But I don't think that's at all an improvement to burden the common case with the work, rather than ask the uncommon case to make arrangements)
The text was updated successfully, but these errors were encountered: