Pass newly created object to after_create callback in Rails - ruby-on-rails

Everytime an object has been created i want to enqueue it in a Redis queue to check for certain properties. How can i add the created object directly as a parameter to the callback? So my redis job would do something like this:
class FurtherProcessCarJob
#....
def self.perform(order)
puts order.id
end
end
whereas in the model
after_create Resque.enqueue FurtherProcessCar, #self
It is possible to hook a method to the callback and there look for the car again and the enqueue the object, but is it possible to do it directly?

As I understand your question, something like this should work
class YourModel < ActiveRecord::Base
#....
after_create :enqueue_to_redis
private
def enque_to_redis
Resque.enqueue self, other_args
end
end

Add in your model
class YourModel < ApplicationRecord
after_commit do
YourModelJob.perform_later(self.id)
end
end

Related

Callback for Active Storage file upload

Is there a callback for active storage files on a model
after_update or after_save is getting called when a field on the model is changed. However when you update (or rather upload a new file) no callback seems to be called?
context:
class Person < ApplicationRecord
#name :string
has_one_attached :id_document
after_update :call_some_service
def call_some_service
#do something
end
end
When a new id_document is uploaded after_update is not called however when the name of the person is changed the after_update callback is executed
For now, it seems like there is no callback for this case.
What you could do is create a model to handle the creation of an active storage attachment which is what is created when you attach a file to your person model.
So create a new model
class ActiveStorageAttachment < ActiveRecord::Base
after_update :after_update
private
def after_update
if record_type == 'Person'
record.do_something
end
end
end
You normally have created the model table already in your database so no need for a migration, just create this model
Erm i would just comment but since this is not possible without rep..
Uelb's answer works but you need to fix the error in comments and add it as an initializer instead of model. Eg:
require 'active_storage/attachment'
class ActiveStorage::Attachment
before_save :do_something
def do_something
puts 'yeah!'
end
end
In my case tracking attachment timestamp worked
class Person < ApplicationRecord
has_one_attached :id_document
after_save do
if id_document.attached? && (Time.now - id_document.attachment.created_at)<5
Rails.logger.info "id_document change detected"
end
end
end
The answer from #Uleb got me 90% of the way, but for completion sake I will post my final solution.
The issue I had was that I was not able to monkey patch the class (not sure why, even requiring the class as per #user10692737 did not help)
So I copied the source code (https://github.com/rails/rails/blob/fc5dd0b85189811062c85520fd70de8389b55aeb/activestorage/app/models/active_storage/attachment.rb#L20)
and modified it to include the callback
require "active_support/core_ext/module/delegation"
# Attachments associate records with blobs. Usually that's a one record-many blobs relationship,
# but it is possible to associate many different records with the same blob. If you're doing that,
# you'll want to declare with <tt>has_one/many_attached :thingy, dependent: false</tt>, so that destroying
# any one record won't destroy the blob as well. (Then you'll need to do your own garbage collecting, though).
class ActiveStorage::Attachment < ActiveRecord::Base
self.table_name = "active_storage_attachments"
belongs_to :record, polymorphic: true, touch: true
belongs_to :blob, class_name: "ActiveStorage::Blob"
delegate_missing_to :blob
#CUSTOMIZED AT THE END:
after_create_commit :analyze_blob_later, :identify_blob, :do_something
# Synchronously purges the blob (deletes it from the configured service) and destroys the attachment.
def purge
blob.purge
destroy
end
# Destroys the attachment and asynchronously purges the blob (deletes it from the configured service).
def purge_later
blob.purge_later
destroy
end
private
def identify_blob
blob.identify
end
def analyze_blob_later
blob.analyze_later unless blob.analyzed?
end
#CUSTOMIZED:
def do_something
end
end
Not sure its the best method, and will update if I find a better solution
None of these really hit the nail on the head, but you can achieve what you were looking for by following this blog post https://redgreen.no/2021/01/25/active-storage-callbacks.html
I was able to modify the code there to work on attachments instead of blobs like this
Rails.configuration.to_prepare do
module ActiveStorage::Attachment::Callbacks
# Gives us some convenient shortcuts, like `prepended`
extend ActiveSupport::Concern
# When prepended into a class, define our callback
prepended do
after_commit :attachment_changed, on: %i[create update]
end
# callback method
def attachment_changed
record.after_attachment_update(self) if record.respond_to? :after_attachment_update
end
end
# After defining the module, call on ActiveStorage::Blob to prepend it in.
ActiveStorage::Attachment.prepend ActiveStorage::Attachment::Callbacks
end
What I do is add a callback on my record:
after_touch :check_after_touch_data
This gets called if an ActiveStorage object is added, edited or deleted. I use this callback to check if something changed.

how to call a class, after_create method from either a spec or rails console

I have the following
class Item < ActiveRecord::Base
after_create TellMe
end
class TellMe
def self.after_create(model)
end
and would like to be able to do something analogous to this:
i=Item.new :name => 'my test name'
i.send(:TellMe.after_create)
similar to how I could call send on a public method? It looks like I can do
i.instance_eval 'TellMe.after_create(self)'
but feels a little ugly (amongst other things)
The only way to trigger a callback is to do a qualifying event, in this case, creating an item.
As a workaround to what you want, you could just create another method that will do exactly what the callback would do and you would be able to access it like normal
Class Item < ActiveRecord::Base
def tellme(params)
TellMe.function(params)
end
end

Attribute that indicates is activerecord model in saving state?

I need to know if model is in saving state (between before_validate and after_save).
class ModelA < ActiveRecord::Base
before_save: do_before
def do_before
modelb.create(:attr => 123, :ref => self)
end
end
class ModelB < ActiveRecord::Base
before_create: do_before
def do_before
self.ref.my_attr = 321
self.ref.save! unless self.ref.is_saving?
end
end
I need "is_saving?" attribute for every model instance in my project. What is the best way to implement that?
Can't you rather set an instance variable in between :before_save and :after_save by using ActiveRecord's :around_save callback and then yield your save? Anyway, the question is not too clear to me. What is the purpose of is_saving? method?

before_update syntax

post.rb Model
after_update :assign_owner
def assign_owner
self.owner = "test"
end
The above method works in terminal but does not change the value of Post.new.owner in Rails. What am I missing?
This is an after update (object needs to be saved) so
post = Post.new.save
Then
post.owner # will be test
If you wanna do this you may want to use after_initialize
for e.g in post.rb
class Post < ActiveRecord::Base
protected
def after_initialize
self.owner = "test
end
end
after_update only fires when you update your object. after_update will not call when you create.
You can use after_create callback when you want to call method on creating new object.
after_create :assign_owner
after_update :assign_owner
def assign_owner
self.owner = "test"
end
after_update and after_create are called after the object is saved. You do set the value of the owner but you don't save it.
Two possible options: use before_update instead --> your object is not yet saved and your change will be saved correctly.
Or use after_update and write it as follows:
def assign_owner
self.update_attribute :owner, "test"
end
Note: any callback will only be called right before or right after saving, so Post.new.owner will still be wrong. But Post.create(:context => 'blabla') should trigger it correctly (or Post.new.save).
Hope this helps.

Test if Has One Relationship has Changed in Rails

Is it possible to have a 'before_save' callback that detects if a 'has_one' relationship has changed (the relationship not the model at the end of the relationship)? For example, something that would act like this:
#person.picture = #picture
#person.picture_changed? # true
#person.save
#person.picture_changed? # false
Maybe it works with
#person.picture_id_changed?
Try relation.changed?... You may also want to look into observers, depending on what you are trying to accomblish.
Example:
class Model < ActiveRecord::Base
has_one :relation
before_save :check_relation_changed
private def check_relation_changed
do_something if relation.changed?
end
end
Ref: http://ryandaigle.com/articles/2008/3/31/what-s-new-in-edge-rails-dirty-objects

Resources