Check if record was just destroyed in rails - ruby-on-rails

So there is
record.new_record?
To check if something is new
I need to check if something is on it's way out.
record = some_magic
record.destroy
record.is_destroyed? # => true
Something like that. I know destroying freezes the object, so frozen? sort of works, but is there something explicitly for this task?

Just do it:
record.destroyed?
Details are here ActiveRecord::Persistence

You can do this.
Record.exists?(record.id)
However that will do a hit on the database which isn't always necessary. The only other solution I know is to do a callback as theIV mentioned.
attr_accessor :destroyed
after_destroy :mark_as_destroyed
def mark_as_destroyed
self.destroyed = true
end
And then check record.destroyed.

This is coming very soon. In the latest Riding Rails post, it says this:
And finally, it's not necessarily
BugMash-related, but José Valim -
among dozens of other commits - added
model.destroyed?. This nifty method
will return true only if the instance
you're currently looking at has been
successfully destroyed.
So there you go. Coming soon!

destroying an object doesn't return anything other than a call to freeze (as far as I know) so I think frozen? is your best bet. Your other option is to rescue from ActiveRecord::RecordNotFound if you did something like record.reload.
I think Mike's tactic above could be best, or you could write a wrapper for these cases mentioned if you want to start 'making assumptions'.
Cheers.

While record.destroyed? works fine, and does return true or false, you can also DRY this up a little bit and create the if condition on the line you call destroy on in your controller.
record = Object.find(params[:id])
if record.destroy
... happy path
else
... sad path
end
Realize this post is a bit late in the game. But should anyone want to discuss this more, i'm game!
Side note: I also had an after_destroy validation on my model and while it worked, a separate method for something like this seems like overkill ;)

Without knowing more of the logic of your app, I think that frozen? is your best bet.
Failing that, you could certainly add a "destroyed" attribute to your models that you trigger in the callbacks and that could be checked against if you want a more precise solution.

The short answer is:
record.destroyed?
# or...
Record.exists?(record) # also very fast!
You'd think that record.destroyed? is better because it doesn't send an extra database request but actually Record.exists? is so extremely fast, that this typically isn't a reason to prefer one over the other.
Don't use the return value of record.destroy, which will always return a frozen instance of the record, whether it's deleted or not, from right before you tried to deleted it. See here: https://apidock.com/rails/v5.2.3/ActiveRecord/Persistence/destroy
# Assuming no issues when destroying the record...
x = record_one.destroy
x.destroyed? # Would return false! (even though the record no longer exists in the db)
Record.exists?(x) # Would correctly return false
# vs.
record_two.destroy
record_two.destroyed? # Would correctly return true
Record.exists?(record_two) # Would correctly return false
If you're in a controller, destroy! will throw a ActiveRecord::RecordNotDestroyed, which you can catch, based on before_destroy callbacks.
def destroy
render json: #record.destroy!
rescue ActiveRecord::RecordNotDestroyed
# #record will work fine down here, it still exists.
render json: { errors: ["Record not destroyed"] }
end

Related

Destruction of records in around_update

Before updating the record, I need to do some checks. As a result of one of the checks, I need to destroy the record that is being updated.
I have two questions.
How good is this solution? (I need to delete a record on update because a similar record was found among the old ones)
What is the correct way to implement cancellation of an update after a record has been destroyed?
I wrote this simple code:
return yield unless title_changed?
tmp_destroyed = false
# some code
tmp_destroyed = true if destroy!
# some code
return if tmp_destroyed
yield
But I'm not sure if this is the right decision.
Can you please tell me if I'm doing everything right? And did I choose the right way for the solution?
I would suggest, before updating the record, check your conditions in controller and if your conditions is true then delete the record instead of checking in model callback methods.

ActiveRecord callback on when object has updated virtual attribute NOT in database

CODE
# Item Model
class Item < ActiveRecord::Base
attr_accessor :paid_amount
after_save :amount_processed?
def amount_processed?
if self.try(:paid_amount)
return true
else
return false
end
end
end
# Controller snippet
...
# params = {"paid_amount" => 10}
#item.assign_attributes(params)
if #item.valid?
#item.save
end
...
Currently the callback is not running, i.e., the code never checks amount_processed?. The reason this is happening is because paid_amount isn't a db attribute for Item. But that is by design. The question is ASSUMING this design were to stay, would there be a way for me to run a callback to check amount_processed? simply based on the fact that the attribute was passed? (i.e., if you run #item.paid_amount you'd get "10" after the #item.assign_attributes).
Note that the following callbacks will not work:
after_save or after_touch because as above, the paid_amount is never saved so the #item is never updated
after_find because this runs, by definition, before the attribute assignment. So with this validation, even though amount_processed? is checked, when it is checked, #item.paid_amount = nil
Would love to combine the two...
Since the question asks how to do this GIVEN current design, a perfectly acceptable answer is to say in the current design, it's not possible. The callback will only work if the attribute is actually updated. In that case, I already have 2 strategies to tackle this, the easiest of which being moving amount_processed? to the controller level so I can check the paid_amount after the assign_attributes. The other strategy is to have a Child of Item, but this is dependent on other info about the code that, for simplicity's sake, I have withheld.
Thanks!
Ook I think I have the answer here, thanks for the comments. Willem is right, in the current design, I can ensure amount_processed? is run by using a custom validation, changing the callback to:
validate :amount_processed?
However, doing so then makes the code a bit hacky, since I'm co-opting a validation to do the work of a callback. In other words, I would have to ensure amount_processed? always returned true (at end of the if statement; obviously other work would be done with paid_amount). There are some other considerations as well looking holistically at my code.
Given that, may change the design... but this was still a very helpful exercise

How to save a model without running callbacks in Rails

I need to calculate values when saving a model in Rails. So I call calculate_averages as a callback for a Survey class:
before_save :calculate_averages
However, occasionally (and initially I have 10k records that need this operation) I need to manually update all the averages for every record. No problem, I have code like the following:
Survey.all.each do |survey|
survey.some_average = (survey.some_value + survey.some_other_value) / 2.to_f
#and some more averages...
survey.save!
end
Before even running this code, I'm worried the calculate_averages is going to get called and duplicate this and probably even cause some problems with the way I'm doing things. Ok, so then I think, well I'll just do nothing and let calculate_averages get called and do its thing. Problem there is, first, is there a way to force callbacks to get called even if you made no changes to the record?
Secondly, the way averages are calculated it's far more efficient to simply not let the callbacks get called at all and do the averages for everything all at once. Is this possible to not let callbacks get called?
I believe what you are asking for can be achieved with ActiveSupport::Callbacks. Have a look at set_callback and skip_callback.
In order to "force callbacks to get called even if you made no changes to the record", you need to register the callback to some event e.g. save, validate etc..
set_callback :save, :before, :my_before_save_callback
To skip the before_save callback, you would do:
Survey.skip_callback(:save, :before, :calculate_average).
Please reference the linked ActiveSupport::Callbacks on other supported options such as conditions and blocks to set_callback and skip_callback.
To disable en-mass callbacks use...
Survey.skip_callback(:save, :before, :calculate_averages)
Then to enable them...
Survey.set_callback(:save, :before, :calculate_average)
This skips/sets for all instances.
update_column is an ActiveRecord function which does not run any callbacks, and it also does not run validation.
Doesn't work for Rails 5
Survey.skip_callback(:save, :before, :calculate_average)
Works for Rails 5
Survey.skip_callback(:save, :before, :calculate_average, raise: false)
https://github.com/thoughtbot/factory_bot/issues/931
If you want to conditionally skip callbacks after checking for each survey you can write your custom method.
For ex.
Modified callback
before_save :calculate_averages, if: Proc.new{ |survey| !survey.skip_callback }
New instance method
def skip_callback(value = false)
#skip_callback = #skip_callback ? #skip_callback : value
end
Script to update surveys
Survey.all.each do |survey|
survey.some_average = (survey.some_value + survey.some_other_value) / 2.to_f
#and some more averages...
survey.skip_callback(true)
survey.save!
end
Its kinda hack but hope will work for you.
Rails 5.2.3 requiring an after party script to NOT trigger model events, update_column(column_name, value) did the trick:
task.update_column(task_status, ReferenceDatum::KEY_COMPLETED)
https://apidock.com/rails/ActiveRecord/Persistence/update_column
hopefully this is what you're looking for.
https://stackoverflow.com/a/6587546/2238259
For your second issue, I suspect it would be better to inspect when this calculation needs to happen, it would be best if it could be handled in batch at a specified time where network traffic is at its trough.
EDIT: Woops. I actually found 2 links but lost the first one, apparently. Hopefully you have it fixed.
For Rails 3 ActiveSupport::Callbacks gives you the necessary control. You can reset_callbacks en-masse, or use skip_callback to disable judiciously like this:
Vote.skip_callback(:save, :after, :add_points_to_user)
…after which you can operate on Vote instances with :add_points_to_user inhibited

Rails 4: Create object only in a factory method?

Going to simplify a bit here, but assume an app that has Users and UserRecords. A User must have one or more UserRecords. I want to limit the creation of UserRecords to a method in User, namely #create_new_user_record.
In other words, I don't want to allow UserRecord.new or UserRecords.create anywhere else in the application. I need to control the creation of these records, and perform some logic around them (for example, setting the new one current and any others to not current), and I don't want any orphaned UserRecords in the database.
I tried the after_initialize callback and checking if the object is new and raising an error there, but of course I do need to call UserRecord.new in User#create_new_user_record. If I could somehow flag in #create_new_user_record that I am calling new from that method, and pick that up in after_intialize, that would work, but how?
I might be over thinking it. I can certainly create a that method on User, and just 'know' to always call it. But others will eventually work on this app, and I will go away and come back to it as some point.
I suppose I could raise the error and just rescue from it in #create_new_user_record. Then at least, if another develop tries it elsewhere they will find out why I did it when they pursue the error.
Anyway, wondering what the Rails gurus here had to say about it.
super method is what you are looking for. Though you'll need some workaround (maybe simple check for value of option only you know about) to fit your needs
class User < ActiveRecord:Base
def .new(attributes = nil, options = {})
do_your_fancy_stuff
if option[:my_secret_new_method]
super # call AR's .new method and automatically pass all the arguments
end
end
Ok, here's what I did. Feel free to tell me if this is bad idea or, if it's an ok idea, if there's a better way. For what it's worth, this does accomplish my goal.
In the factory method in the User model, I send a custom parameter in the optional options hash defined on the new method in the API. Then I in the UserRecord#new override, I check for this parameter. If it's true, I create and return the object, otherwise I raise in custom error.
In my way of thinking, creating a UserRecord object any other way is an error. And a developer who innocently attempts it would be lead to explanatory comments in the two methods.
One thing that's not clear to me is why I need to leave off the options hash when I call super. Calling super with it causes the ArgumentError I posted in my earlier comment. Calling super without it seems to work fine.
class User < ActiveRecord::Base
...
def create_new_user_record
# do fancy stuff here
user_record = UserRecord.new( { owner_id: self.id, is_current: true }, called_from_factory: true )
user_record.save
end
...
end
class UserRecord < ActiveRecord::Base
...
def UserRecord.new(attributes = nil, options = {})
if options[:called_from_factory] == true
super(attributes)
else
raise UseFactoryError, "You must use factory method (User#create_new_user_record) to create UserRecords"
end
end
...
end

Rails 3 - How to deal with complicated Switch Statements / If Statements

I'm building a method that ingests incoming email, and processes the email. Along the way there are a lot of things that could prevent the email from being processes successfully. The wrong reply-to address, the wrong from address, an empty message body etc..
The code is full of Switch Statements (case/when/end) and If statements. I'd like to learn a smarter, cleaner way of doing this. Additionally, a way to can track an error and at the end have one location where it emails back the user with an error. Is something like this possible with rails?
#error = []
Case XXX
when xxxx
if XXXXX
else
#error = 'You don't have permission to reply to the xxxxx'
end
else
#error = 'Unfamilar XXXX'
end
Then something at the end like...
If #errors.count > 0
Send the user an email letting them know what went wrong
else
do nothing
end
Thanks for the help here. If you know of any other tutorials that would teach me how to write logic like the above smarter, that'd be great. Right now I have case/if statements going 3 levels deeps, it's hard to keep it straight.
Thanks
First, I would just assign a symbol to each error message as a simple hash:
ErrorsDescription = {
:first => "First error",
:second => "Second error",
...
}
And use symbols instead of strings.
Then, your if and switch statements. Basicaly I can't really help you, because I don't see what kind of condition statements you have. What are you checking? Why do you have 3 level deep conditions? Probably you can write it simpler using if and switch - so this is my first answer to this issue. Another solution may be writing simple methods to improve readability, so you can write like this:
if #email.has_wrong_reply_to_address?
#errors << :wrong_reply_to_address
else
...
end
Also, as #mpapis suggested, you can use Rails build in validation system, but not as ActiveRecord but as ActiveModel. Here you have some examples how to do it and how it works (also take a look here). Of course you may need to write custom validations, but they are just simple methods. Once you do all above job, you can just use:
#email.valid?
And if it is not, you have all errors in hash:
#email.errors
Just as in ordinary ActiveRecord object.
Then you may extend your Emial class with send_error_email method which sends an email if there was an error.
EDIT:
This is about new information you attached in comment.
You don't have to use nested ifs and switch here. You can have it looking like this:
def is_this_email_valid?
if !email_from_user_in_system?
#errors << :user_not_in_system
return false
end
if comment_not_exists?
#errors << :comment_not_exists
return false
end
if user_cannot_comment_here?
#errors << :permision_error
return false
end
...
true
end
Then you can use it:
if !#email.is_this_email_valid?
#email.send_error_mail
end
I suggest using Exceptions. Start with this tutorial, then use Google, trial and error to go from there.
Edit: In more complex cases, exceptions may not be the right tool. You might want to use validator functions instead, for example (see other answers), or you could just return early instead of nesting ifs, e.g.:
unless sender_valid?
#error = "Sender invalid"
return
end
unless subject_valid?
#error = "Invalid command"
return
end
# normal no-errors flow continues here...
You could throw an error when something is not right. Then catch it at the end of your method.
http://phrogz.net/programmingruby/tut_exceptions.html
To make your code more readable and not have a lot of switch and if/then statements, you could create separate methods that validate certain aspects and call them from your main error-checking method.
Is it possible to map your message to a model ? then all the if/switch logic would be validations and automatically handled by rails. Good starting point is active record validations guide
Also worth reading is action mailer guide

Resources