Sidekiq undefined method for string - ruby-on-rails

Here is what I'm calling:
UpdateRatingAndCountWorker.perform_async(133)
Here is my worker:
# app/workers/update_rating_and_count_worker.rb
class UpdateRatingAndCountWorker
include Sidekiq::Worker
def perform(review_id)
review = Review.find(review_id.to_i)
review.style.update_average_rating!
end
end
Here is the error:
"NoMethodError: undefined method `style' for \"#<Review:0x00000005bef438>\":String"

In the error message it looks like the variable review.style is of the type String.
Since you haven't posted the code for the model Review I can only guess but, should it be
# app/workers/update_rating_and_count_worker.rb
class UpdateRatingAndCountWorker
include Sidekiq::Worker
def perform(review_id)
review = Review.find(review_id.to_i)
review.update_average_rating!
end
end

Restart Sidekiq works for me, it picks up new method in my model

Related

Rspec "NoMethodError" from nested module

I'm running into a weird error:
Class:
module AnimalSanctuary
module AnimalInspector
class AnimalPicker
def initialize(number_of_animals, ids)
#number_of_animals = number_of_animals
#ids = ids
end
...
def pick_animals(animal)
end
end
end
test:
require 'rails_helper'
RSpec.describe AnimalSanctuary::AnimalInspector::AnimalPicker do
describe ".pick_animals" do
context "pick an available animal" do
it "returns animal name" do
expect(AnimalSanctuary::AnimalInspector::AnimalPicker.pick_animals("Dog")).to eq("Dog")
end
end
end
end
I get the following error:
NoMethodError:
undefined method `pick_animals' for AnimalSanctuary::AnimalInspector::AnimalPicker:Class
Rspec calls the class but not the method which has stumped me. Am I doing something wrong?
The definition of pick_animals is an instance method.
To call it, you will need to instantiate an object of the class using the new method as shown below. I have passed in random values to your initializer (1, [1,2]) however you can set them as you like.:
number_of_animals = 1
ids = [1,2]
AnimalSanctuary::AnimalInspector::AnimalPicker.new(number_of_animals, ids).pick_animals("Dog")
Otherwise, to call it the way you are calling it, you will need to redefine it as a class method by using self.pick_animals as shown below:
module AnimalSanctuary
module AnimalInspector
class AnimalPicker
...
def self.pick_animals(animal)
end
end
end
yeah pick_animals is an instance method.
you can use the following in your rspec
expect_any_instance_of(nimalSanctuary::AnimalInspector::AnimalPicker).to receive(:pick_animals).with("dogs").to_eq("Dog")
Hope this helps

Rails 5 upgrade broke delayed tasks

I upgraded my Rails application from Rails 4.2 to Rails 5.0 and now a lot of my delayed jobs which were created pre-upgrade are breaking. I get the following error:
undefined method 'game_completion_feedback' for ConfirmationMailer:Class
Even though I have the method defined in the ConfirmationMailer class and nothing was changed in that class or where it's being invoked from while upgrading.
On doing a YAML.load_dj I get the following error:
ArgumentError: undefined class/module ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Integer
from <path>/.rvm/rubies/ruby-2.2.5/lib/ruby/2.2.0/psych/class_loader.rb:53:in `path2class'
Caused by NameError: uninitialized constant
ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Integer
from <path>/.rvm/gems/ruby-2.2.5/gems/activesupport-5.0.7.1/lib/active_support/inflector/methods.rb:283:in `const_get'
It looks like it broke because something changed during the Rails 4.2 to Rails 5.0 upgrade.
I found online that running Rails.cache.clear can help fix this issue but my tmp folder in the production environment is empty so running Rails.cache.clear just throws an error as:
No such file or directory # dir_initialize - /var/app/current/tmp/cache/
Is there any way that I can make these old delayed jobs still work in Rails 5.0 or do I just have to recreate all of them individually?
My ConfirmationMailer class:
class ConfirmationMailer < ApplicationMailer
def game_completion_feedback(user, date, feedback)
#user = user
#date = format_time(date.to_time)
#feedback = feedback
mail(to: user.email, subject: 'Game Completed')
end
end
And I call that function as:
def send_feedback_to_client
ConfirmationMailer.delay.game_completion_feedback(user, date, feedback)
end
This is coming up in other situations as well where I am calling a Struct such as:
class RemindersForGame < Struct.new(:gamer_email, :leader_email, :start)
def perform
ConfirmationMailer.game_reminder_email_gamer(gamer_email, leader_email, start).deliver_now
ConfirmationMailer.game_reminder_email_leader(gamer_email, leader_email, start).deliver_now
end
end
And I call this struct as:
def create_reminder_email(start)
reminders = Delayed::Job.enqueue RemindersForGame.new(client.user, coach, start),
run_at: start - 2.day,
queue: 'game_reminder'
self.reminders_job_id = reminders.id
end
The game_reminder_email_gamer and game_reminder_email_leader are defined as the exact same way as the other method in that class and I didn't change anything related to how it's being called.
For versions < Rails 4.2: ConfirmationMailer.delay.game_completion_feedback(user, date, feedback)
For versions > Rails 4.2: ConfirmationMailer.game_completion_feedback(user, date, feedback).deliver_later
Please try using this and let us know if it solves the problem.
Also, when passing variables to your Mailer class, using the with() method will create instance variables for you to use in the mailer instance. For example:
ConfirmationMailer.with(u: user, d: date, f: feedback).game_completion_feedback.deliver_later
Would then create #u, #d, #f as instance variables for use in your Mailer instance.
I'm not suggesting you name your variables as single characters :) But show that you don't need the positional arguments.
ArgumentError: undefined class/module ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Integer is caused by a change in Rails 5 such that ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Integer and ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Float don't exist anymore. Here is a migration you can use to fix that:
class UpdatePostgresDataTypeInDj < ActiveRecord::Migration[5.1]
REPLACEMENTS = {
'ruby/object:ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Integer': 'ruby/object:ActiveModel::Type::Integer',
'ruby/object:ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Float': 'ruby/object:ActiveModel::Type::Float',
}
def up
REPLACEMENTS.each do |old, new|
Delayed::Job.where("handler LIKE ?", "%#{old}%")
.update_all("handler = replace(handler, '#{old}', '#{new}')")
end
end
def down
REPLACEMENTS.each do |old, new|
Delayed::Job.where("handler LIKE ?", "%#{new}%")
.update_all("handler = replace(handler, '#{new}', '#{old}')")
end
end
end

Sidekiq: ArgumentError: When assigning attributes, you must pass a hash as an argument

I guess this question is common with Rails 4, but my situation is different.
I am using Sidekiq to delay the creation of jobs; think this is possible as with simple data, it works. By means of simple data:
def perform
Foo.create(bar: "staff")
end
Here's my data with issues:
supports_controller.rb:
def create
params = support_params // seems to be issues here?
DelayedJobs.perform_in(1.minutes, current_user.id, params)
...
end
private
def support_params
params.require(:support).permit(:foo1, :foo2, :foo3)
end
app/workers/delayed_jobs.rb:
class DelayedJobs
include Sidekiq::Worker
def perform(user_id, params)
u = User.find(user_id)
support = u.supports.build(params)
Support.create(support) // create and save to db
end
end
Via web (localhost:3000/sidekiq/scheduled, I see the details. Great. But after a minute it goes to retries with the error. Any help on this one?
EDIT:
In the sidekiq web argument:
40, {"foo1"=>"a", "foo2"=>"b", "foo3"=>"c"}
Why is that the user_id (40) is outside?
The problem isn't with Sidekiq; it's an ActiveRecord problem with this line:
Support.create(support)
create only takes a hash, but you're giving it a Support.
This should work:
class DelayedJobs
include Sidekiq::Worker
def perform(user_id, params)
u = User.find(user_id)
u.supports.create!(params) # `create!` will raise an error if the save fails; allowing you to catch invalid params
end
end
Protip: you can eliminate Sidekiq as a suspect by running the body of your perform method in a Rails console. You'll see that you get the same error even when Sidekiq isn't involved.
I suggest that you call save method on support object because when you are using build method it returns a new instance of support so you need only to save it.
class DelayedJobs
include Sidekiq::Worker
def perform(user_id, params)
u = User.find(user_id)
support = u.supports.build(params)
support.save // save to db
end
end
In your controller try to change params to:
def create
params = params[:support]

Why sidekiq returned error?

I trying to use sidekiq in my app, but when I write this simple worker
class ParseWorker
include Sidekiq::Worker
def perform(instance)
instance.spideys << Spidey.create
end
end
and I use this worker here
def create
#user_link = UserLink.new(user_link_params)
if #user_link.save
binding.pry
ParseWorker.perform_async(#user_link)
redirect_to results_user_links_path
end
end
was returned the error
2015-04-09T13:14:56.757Z 11644 TID-ay1nc ERROR: Actor crashed!
NoMethodError: undefined method `spideys' for "#<UserLink:0x007f65f00d99b0>":String
/home/weare138/simple-parser/app/workers/parse_worker.rb:7:in `perform'
but why? #user_link is not a string
how fix?
upd
def perform(id)
user_link = UserLink.find(id)
user_link.spideys << Spidey.create
end
error
2015-04-09T14:15:21.889Z 11644 TID-ay1nc ERROR: Actor crashed!
NoMethodError: undefined method `spideys' for 39:Fixnum
upd2
class ParseWorker
include Sidekiq::Worker
require 'open-uri'
def perform(id)
user_link = UserLink.find(id)
user_link.spideys << Spidey.create
end
end
Sidekiq uses Redis, so when you pass to the worker an object, it serializes it to JSON.
From the Sidekiq documentation:
This means the arguments to your worker must be simple JSON datatypes
(numbers, strings, boolean, array, hash). Complex Ruby objects (e.g.
Date, Time, ActiveRecord instances) will not serialize properly.
Instead you should do
def create
...
ParseWorker.perform_async(#user_link.id)
end
def perform(id)
UserLink.find(id).spideys << Spidey.create
end

Class and Module with the same name - how to choose one or another?

I have encountered following situation:
There is
ModuleA::ModuleB::ClassC.do_something
in the definition of do_something I need to use model from the application
def do_something
...
data = Order.all
...
end
But there also exists a module
ModuleA::Order
So I get an error
undefined method `all' for ModuleA::Order:Module
I found a solution by doing
def do_something
...
data = Kernel.const_get('Order').all
...
end
That returns the model. My question is:
what's the best way to do it? is there a cleaner solution?
(despite the fact, that having the same name for Class and Module it's not the greatest idea, but it cannot be changed here...)
Prefix the class name with :: in the do_something method...
def do_something
...
data = ::Order.all
...
end

Resources