I'm having a weird issue for which I can't find a logical explanation.
I'm investigating a bug and put some logging in place (through Rollbar) so I can see the evolution some instances of one of my models.
Here is what I got:
class Connexion < ActiveRecord::Base
before_validation :save_info_in_rollbar
after_save :save_info_in_rollbar
def save_info_in_rollbar
Rollbar.log("debug", "Connexion save", :connexion_id => self.id, :connexion_details => self.attributes)
end
end
Now I am getting loads of data in rollbar (pretty much 2 rows for every time a connexion is created/updated). But the weird thing is the following: for some connexions (=> exactly the ones with faulty data which I am investigating), I am getting no data at all!
I don't get how it's possible for a connexion to be created and persisted to the DB, and not have any trace of the before_validation logging.
It looks like the callback is not called, but unless I'm mistaken, it's supposed to be the first one in the callback order => what could prevent it from being called?
EDIT >> Copy and paste from a reply, might be relevant:
There are 3 cases in which a connexion is created or updated, and thoses cases are :
.connexions.create()
connexion.attr = "value"; connexion.save!
connexion.update_attributes(attr: "value")
The only cases in which the callback won’t be run are:
Explicitly skipping validations (e.g. with save(validate: false))
Using an update method that skips Ruby-land (either partially or entirely, see each method’s linked docs) and just runs the SQL directly (e.g. update_columns, update_attribute, update_all).
But: I might be missing a case. Also, I’m assuming there isn’t a bug in ActiveRecord/ActiveModel causing this.
Sorry about the stupid question guys, the explanation was that we were having 2 apps working on the same database, and the modification was made by the other app (which of course was not sending the Rollbar updates).
Sometimes the toughest issues have the most simple answers haha
Firstly, you don't need self in instance methods, as the scope of the method is instance.
Secondly, you need to check, how are you saving the data to the database. You can skip callbacks in Rails: Rails 3 skip validations and callbacks
Thirdly, double check the data.
Related
I am not sure how Ruby uses ActiveRecord to save data directly in the model code. How can I save data into the DB in the model itself?
Basically my code run into a race condition for the following reason (Ruby/rails + ActiveRecord + Sidekiq):
- My model does something like the following:
def is_present?(data)
memory['properties'].include?(data)
def update_mem(data, size)
if size != 0 && memory['properties'].length == amount
memory['properties'].shift
memory['properties'].push(data)
It checks if a specific value is present (is_present), if it is not present in the memory['properties'] yet, it will add it.
Clearly if there is only one thread accessing "memory", it works just fine but since Sidekiq is pretty fast there might be multiple threads running and will end up in a race condition (one thread write something, the other thread read what was in memory before).
"memory" is actually a column in a table (MySQL) and as soon as I write something in it with "memory['properties'].push(123)" I would like to save them immediately.
My question is, how can I prevent this weird race condition?
What I would like to do is to save the data directly in the DB using the model. The problem is that it seems is not working.
So, to access the data in the model, I use the following code:
memory['property'].push(123)
or
self.memory['property'].push(123)
They both reference the memory column in the DB table.
But then I want to do something like self.save! but it is not working at all.
I tried to add the following code in the model iteself:
self.memory_will_change!
self.memory['properties'].push(property)
self.save!
Unfortunately, it is not working and I cannot save the data into the DB.
This model is actually called via perform() using Sidekiq as per below:
model = Receivers.find(id)
model.receive(data)
model.time = Time.now
model.save!
So the time is updated correctly but the memory (which is updated in the model when i call "receive") it does not get updated. Anyone knows how to overcome this problem? I need to save the data into the DB directly in the model.
Thanks and I look forward to hearing from you.
One solution would be to use a lock so that only one process can access the table at a time. Once you are done processing, you would release the lock.
Check out the following link:
https://api.rubyonrails.org/v5.1/classes/ActiveRecord/Locking/Pessimistic.html
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
I'm running into a weird bug on Heroku, which I believe may be a race condition, and I'm looking for any sort of advice for solving it.
My application has a model that calls an external API (Twilio, if you're curious) after it's created. In this call, it passes a url to be called back once the third party completes their work. Like this:
class TextMessage < ActiveRecord::Base
after_create :send_sms
def send_sms
call.external.third.party.api(
:callback => sent_text_message_path(self)
)
end
end
Then I have a controller to handle the callback:
class TextMessagesController < ActiveController::Base
def sent
#textmessage = TextMessage.find(params[:id])
#textmessage.sent = true
#textmessage.save
end
end
The problem is that the third party is reporting that they're getting a 404 error on the callback because the model hasn't been created yet. Our own logs confirm this:
2014-03-13T18:12:10.291730+00:00 app[web.1]: ActiveRecord::RecordNotFound (Couldn't find TextMessage with id=32)
We checked and the ID is correct. What's even crazier is that we threw in a puts to log when the model is created and this is what we get:
2014-03-13T18:15:22.192733+00:00 app[web.1]: TextMessage created with ID 35.
2014-03-13T18:15:22.192791+00:00 app[web.1]: ActiveRecord::RecordNotFound (Couldn't find TextMessage with id=35)
Notice the timestamps. These things seem to be happening 58 milliseconds apart, so I'm thinking it's a race condition. We're using Heroku (at least for staging) so I think it might be their virtual Postgres databases that are the problem.
Has anyone had this sort of problem before? If so, how did you fix it? Are there any recommendations?
after_create is processed within the database transaction saving the text message. Therefore the callback that hits another controller cannot read the text message. It is not a good idea to have an external call within a database transaction, because the transaction blocks parts of the database for the whole time the slow external request takes.
The simples solution is to replace after_save with after_commit (see: http://apidock.com/rails/ActiveRecord/Transactions/ClassMethods/after_commit)
Since callbacks tend to become hard to understand (and may lead to problems when testing), I would prefer to make the call explicit by calling another method. Perhaps something like this:
# use instead of .save
def save_and_sent_sms
save and sent_sms
end
Perhaps you want to sent the sms in the background, so it does not slow down the web request for the user. Search for the gems delayed_job or resque for more information.
Do you have master/slave database where you always write to master but read from slave? This sounds like the db replication lag.
We solved such problems by forcing a read being made from the master database in the specific controller action.
Another way would be to call send_sms after the replication has been finished.
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
I have a function within a model that performs some actions after_save
Within that function I have the following code
progression = Progression.find_by_id(newprogression)
if progression.participation_id.nil?
progression.participation_id = participation.id
progression.save
else
What I am seeing is that progression is not being updated. Even though the following
if i check progression.participation_id before the save it has been updated
if in debugger I manually run progression.save I get true returned.
any thoughts?
I'm still pretty new to Rails, but every time I save something, I have to use a bang method. Have you tried progression.save!
Ok, I had the same problem and I managed to deal with it.
The problem was with association name. I had a model named User and a child model named Update, which had number of new updates of each user. So, in user.rb I had this line:
has_one :update
After I deleted this line, everything started working again. Looks like this is a rails reserved name and it should not be used when creating models. Perhaps you have some kind of the same problem here. It's a pity that Rails doesn't indicate this problem.
It might be a caching problem. From the rails guide on AR Relations - Sec 3.1, you might want to reload the cache by passing true. You are likely to encounter this, if you are checking it via participation, after establishing the link through progression.
Assuming Progression belongs_to Participation,
Can you try: progression.participation = participation and then progression.save
Also, try: participation.progressions << progression and participation.save ?
Try
progression = Progression.find_by_id(newprogression)
if progression.participation_id.nil?
progression.update_attributes(
:participation_id => participation.id
)
else
save and after_save run in the same transaction. maybe it's a problem. try to use after_commit callback