I am on rails 4.2.10. I need to trigger a job using sidekiq in after_save method. But the job is triggered, before the object is committed into the database, so I get the error, object not found with id=xyz.
So, I need to use
after_commit :method_name, :on => [:create, :update]
But the changes that I made in object doesn't show up in above method. I have an attribute email. When I was calling above method after_save, email_changed? return true. But if I call the same method using after_commit, email_changed? returns `false.
Is it because I am using object.save method and not create method?
Below is the method, which I am calling to trigger the job:
def update_or_create_user
if email_changed?
ServiceUpdateDataJob.perform_later action: 'update', data: {type: 'user', user_id: self.id}
end
true
end
I recognize this isn't exactly an answer to your question as stated. However...
IMO, you're overloading your model's responsibilities. I suggest you create a service that triggers the job when your model is saved. It might look something like:
class FooService
attr_accessor :unsaved_record
class << self
def call(unsaved_record)
new(unsaved_record).call
end
end
def initialize(unsaved_record)
#unsaved_record = unsaved_record
end
def call
kick_off_job if unsaved_record.save
!unsaved_record.new_record?
end
private
def kick_off_job
# job logic
end
end
You might use the service in a controller something like:
class FooController < ApplicationController
def create
#new_record = ModelName.new(record_params)
if FooService.call(#new_record)
# do successful save stuff
else
# do unsuccessful save stuff
end
end
...
end
Related
I would like to create something similar to ActiveRecord validation: before_validate do ... end. I am not sure how could I reference attributes of class instance from the block given. Any idea?
class Something
attr_accessor :x
def self.before_validate(&block)
#before_validate_block = block
end
before_validate do
self.x.downcase
end
def validate!
# how should this method look like?
# I would like that block would be able to access instance attributes
end
end
#3limin4t0r's answer covers mimicing the behavior in plain ruby very well. But if your are working in Rails you don't need to reinvent the wheel just because you're not using ActiveRecord.
You can use ActiveModel::Callbacks to define callbacks in any plain old ruby object:
class Something
extend ActiveModel::Callbacks
define_model_callbacks :validate, scope: :name
before_validate do
self.x.downcase
end
def validate!
run_callbacks :validate do
# do validations here
end
end
end
Featurewise it blows the socks off any of the answers you'll get here. It lets define callbacks before, after and around the event and handles multiple callbacks per event.
If validations are what you really are after though you can just include ActiveModel::Validations which gives you all the validations except of course validates_uniqueness_of which is defined by ActiveRecord.
ActiveModel::Model includes all the modules that make up the rails models API and is a good choice if your are declaring a virtual model.
This can be achieved by using instance_eval or instance_exec.
class Something
attr_accessor :x
# You need a way to retrieve the block when working with the
# instance of the class. So I've changed the method so it
# returns the +#before_validate_block+ when no block is given.
# You could also add a new method to do this.
def self.before_validate(&block)
if block
#before_validate_block = block
else
#before_validate_block
end
end
before_validate do
self.x.downcase
end
def validate!
block = self.class.before_validate # retrieve the block
instance_eval(&block) # execute it in instance context
end
end
How about this?
class Something
attr_accessor :x
class << self
attr_reader :before_validate_blocks
def before_validate(&block)
#before_validate_blocks ||= []
#before_validate_blocks << block
end
end
def validate!
blocks = self.class.before_validate_blocks
blocks.each {|b| instance_eval(&b)}
end
end
Something.before_validate do
puts x.downcase
end
Something.before_validate do
puts x.size
end
something = Something.new
something.x = 'FOO'
something.validate! # => "foo\n3\n"
This version allows us to define multiple validations.
I've got a sidekiq job that needs to be run after the commit, but only in some situations and not all, in order to avoid a common race condition.
For example, the below after_commit will always fire but the code inside will only execute if the flag is true (previously set in the verify method).
class User < ActiveRecord::Base
...
after_commit do |user|
if #enqueue_some_job
SomeJob.new(user).enqueue
#enqueue_some_job = nil
end
end
def verify
#enqueue_some_job = ...
...
save!
end
end
The code is a bit ugly. I'd much rather be able to somehow wrap the callback inline like this:
class User < ActiveRecord::Base
def verify
if ...
run_after_commit do |user|
SomeJob.new(user).enqueue
end
end
...
save!
end
end
Does anything built into Rails exist to support a syntax like this (that doesn't rely on setting a temporary instance variable)? Or do any libraries exist that extend Rails to add a syntax like this?
Found a solution using a via a concern. The snippet gets reused enough that it is probably a better option to abstract the instance variable and form a reusable pattern. It doesn't handle returns (not sure which are supported via after_commit since no transaction is present to roll back.
app/models/concerns/callbackable.rb
module Callbackable
extend ActiveSupport::Concern
included do
after_commit do |resource|
if #_execute_after_commit
#_execute_after_commit.each do |callback|
callback.call(resource)
end
#_execute_after_commit = nil
end
end
end
def execute_after_commit(&callback)
if callback
#_execute_after_commit ||= []
#_execute_after_commit << callback
end
end
end
app/models/user.rb
class User < ActiveRecord::Base
include Callbackable
def verify
if ...
execute_after_commit do |user|
SomeJob.new(user).enqueue
end
end
...
save!
end
end
You can use a method name instead of a block when declaring callbacks:
class User < ActiveRecord::Base
after_commit :do_something!
def do_something!
end
end
To set a condition on the callback you can use the if and unless options. Note that these are just hash options - not keywords.
You can use a method name or a lambda:
class User < ActiveRecord::Base
after_commit :do_something!, if: -> { self.some_value > 2 }
after_commit :do_something!, unless: :something?
def do_something!
end
def something?
true || false
end
end
Assuming that you need to verify a user after create.
after_commit :run_sidekiq_job, on: :create
after_commit :run_sidekiq_job, on: [:create, :update] // if you want on update as well.
This will ensure that your job will run only after a commit to db.
Then define your job that has to be performed.
def run_sidekiq_job
---------------
---------------
end
Hope it helps you :)
In a Rails project because of some history reasons, a piece of codes in Tiger and Elephant are same.
I don't like the repetition, but if I create a new method in AnimalController class and move these codes into it, I can't return the walk or running method from the new method.
I think return from another method may not a good practice, but I really hate the duplications, can someone help me refactoring?
class AnimalController
# I want create a new method here
#def all_in
#end
end
class TigerController < AnimalController
def running # This is an Action
some_different_codes...
if arm.blank?
render_not_found
return # <- how can I return `running` from the new method?
end
if lag.nil?
invalid_id
return # <-
end
some_different_codes...
end
end
class ElephantController < AnimalController
def walk # This is an Action
some_different_codes...
if arm.blank?
render_not_found
return
end
if lag.nil?
invalid_id
return
end
some_different_codes...
end
end
A method can't make its caller return, if it doesn't want to. So this new method will perform checks (while rendering something) and it will return result of the checks. Caller method analyzes the return value and decides what to do. Something along these lines:
class AnimalController
def all_in
if invalid_id
render_not_found
return false
end
if lag.nil?
invalid_id
return false
end
true
end
end
class TigerController < AnimalController
def running # This is an Action
some_different_codes...
return unless all_in
some_different_codes...
end
end
Might be a good turn to look up about callbacks and superclassing:
Callbacks basically allow you to run login in code based on the response of another function.
Since we have them all over Rails, not many people actually appreciate what they do. If you've ever implemented them in JS, you'll know all about them!
-
Superclassing is where you inherit from an existing class - allowing you to use (& extend) the functions the class has. This is where the super command comes from.
I'd do this (looks like Sergio's answer actually, which is reassuring):
#app/controllers/animals_controller.rb
class AnimalsController < ApplicationController
private
def all_in?
if invalid_id
return false
end
if lag.nil?
invalid_id
return false
end
true #-> ruby automatically returns the last line
end
end
The above is what you'd call a callback -- you'll be able to call all_in (in an instance) and receive a response of either true or false.
This will give you the ability to call that method (if you're superclassing, the method will be available down the chain:
#app/controllers/elephants_controller.rb
class ElephantController < AnimalController
def walk # This is an Action
some_different_codes...
if all_in?
some_different_codes...
end
end
end
Now, there's something you must be aware of.
This type of behavior should not be put in the controller of your app - it should be in your models:
#app/models/animal.rb
class Animal < ActiveRecord::Base
def walk
end
end
#app/models/animals/elephant.rb
class Elephant < Animal
def walk
super ...
end
end
#app/models/animals/tiger.rb
class Tiger < Animal
end
The above is known as STI (Single Table Inheritance). It's basically a way to subclass against a main model with other "dependent" models & their methods.
Since ruby is object orientated, you should be calling object-specific methods on the objects themselves;
#config/routes.rb
resources :tigers, :elephants, controller: :animals
#url.com/tigers/
#url.com/elephants/
#app/controllers/animals_controller.rb
class AnimalsController < ApplicationController
def show
#tiger = Tiger.find params[:id]
#tiger.walk
end
end
This is more or less epitomized with a state machine:
class Vehicle
attr_accessor :seatbelt_on, :time_used, :auto_shop_busy
state_machine :state, :initial => :parked do
before_transition :parked => any - :parked, :do => :put_on_seatbelt
after_transition :on => :crash, :do => :tow
after_transition :on => :repair, :do => :fix
after_transition any => :parked do |vehicle, transition|
vehicle.seatbelt_on = false
end
...
I recently had a rails model that had several callbacks on it like so:
class Model < ActiveRecord::Base
before_validation :fetch_posts
after_create :build_posts
def fetch_posts
fetch_collection
rescue MyException => e
self.errors.add(:post, e.message)
end
def build_posts
fetch_collection.each do |item|
DifferentModel.build(item)
end
end
def fetch_collection
#collection ||= method_that_fetches_collection_from_external_source
end
end
This was working just fine but it was making it extremely difficult to write tests, as whenever I wanted to create a Model I had to stub out all the callbacks. Enter service objects:
class ModelFetcher
attr_reader :model
def initialize(model)
#model = model
end
def save
model.fetch_posts
if model.save
model.build_posts
return true
else
return false
end
end
end
The problem I'm seeing now, in the case where a model does indeed contain an error (from the fetch posts method), it doesn't get carried over to the model.save call in the SO. That is to say, the Model.new has an error, but once I call .save on that Model.new it doesn't maintain the error and the model saves properly.
I considered adding validate :fetch_posts but then I am back in the same situation I was before as this is essentially a callback.
Any advice on how to structure this better? Is it possible to maintain an error from Model.new to .save? Am I fundamentally misunderstanding something?
Thanks!
Here is an alternate solution which is to overwrite run_validations! since you have none.
class Model < ActiveRecord::Base
after_create :build_posts
def fetch_posts
fetch_collection
rescue MyException => e
self.errors.add(:post, e.message)
end
def build_posts
fetch_collection.each do |item|
DifferentModel.build(item)
end
end
def fetch_collection
#collection ||= method_that_fetches_collection_from_external_source
end
private
def run_validations!
fetch_posts
errors.empty?
end
end
Usually this method looks like
def run_validations!
run_callbacks :validate
errors.empty?
end
but since you have no validations it should serve a similar purpose on #save.
Or as I suggested in a comment you can replace save with model.errors.any? Since save will clear your original errors set by fetch_posts but errors.any? Will check if there were errors during the fecth_posts method.
I know that before_filter is only for controllers in Rails, but I would like something like this for a model: any time a method in my model is called, I'd like to run a method that determines whether the called method should run. Conceptually, something like this:
class Website < ActiveRecord::Base
before_filter :confirm_company
def confirm_company
if self.parent.thing == false?
return false
end
end
def method1
#do stuff
end
end
So when I call #website.method1, it will first call confirm_company, and if I return false, will not run method1. Does Rails have functionality like this? I hope i'm just missing out on something obvious here...
class MyModel
extend ActiveModel::Callbacks
define_model_callbacks :do_stuff
before_do_stuff :confirm
def do_stuff
run_callbacks :do_stuff do
#your code
end
end
def confirm
#confirm
end
end
I'm really not sure this will work, but you can try it, as I really dont have time now. Take a look at that: http://api.rubyonrails.org/classes/ActiveModel/Callbacks.html
I've made a gem just for this.
You can plug this in any ruby class, and do something like in the controller.
before_action :foobar, on: [:foo]
https://github.com/EdmundLeex/action_callback