Rails 5 upgrade broke delayed tasks - ruby-on-rails

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

Related

Rails calling freeze constant

I tried to do some code refactor inside of my webhook manager class. The idea was to use Builder pattern (hope I didn't get the names wrong) in which one class would distribute assignments to the others. That's why I build a class with constant variable WEBHOOK_DEFINITIONS and, depending on the arguments provided, would trigger the appropriate ActiveJob's. Like below:
# frozen_string_literal: true
class ManageWebhookData
WEBHOOK_DEFINITIONS = {
default: IdentityChecks::IdentityCheckUpdaterJob,
#there will be much more
(...)
}.freeze.with_indifferent_access
def initialize(webhook, name)
#webhook = webhook
#name = name
end
def call
WEBHOOK_DEFINITIONS[type].perform_later(webhook)
end
attr_reader :webhook, :name
private
def type
WEBHOOK_DEFINITIONS.key(name.downcase) ? name.downcase : :default
end
end
I don't know why but Rails somehow call WEBHOOK_DEFINITIONS even when it's freeze because I'm getting an error:
NameError (uninitialized constant IdentityCheckUpdaterJob):
app/services/manage_webhook_data.rb:5:in `<class:ManageWebhookData>'
Why is this happening?
It's not rails, it's ruby.
Constants are resolving on class load, you could check it yourself in console
> class Foo
> BAR = puts("I'm constant")
> end
I'm constant
Besides that, I'm not sure when Indifferent hash is called on freezed hash, it will be freezed too. I'd write instead
WEBHOOK_DEFINITIONS = {
default: IdentityChecks::IdentityCheckUpdaterJob,
#there will be much more
(...)
}.with_indifferent_access.freeze

Rails 5 include helper in class

We are migrating from Rails 4.2 to 5.2. The following code works fine in 4.2 but not in 5
require 'action_view'
module OurModule
class CheckReport
include ActionView::Helpers::DateHelper
def self.our_method
start_time = Time.current
LOGGER.info "OurModule::CheckReport.our_method finished in #{distance_of_time_in_words(start_time, Time.current)}"
end
end
end
But in Rails 5 we are getting:
NoMethodError: undefined method `distance_of_time_in_words' for OurModule::CheckReport:Class
This seems to be because these are class methods and not instance methods.
So, why did it work in Rails 4 (same ruby version - 2.4.9) and what can we do to fix it (apart from making all these cases instance methods?)
Seeing as some people are coming here to find the answer, here is what I did:
require 'action_view'
module OurModule
class CheckReport
def self.our_method
start_time = Time.current
LOGGER.info "OurModule::CheckReport.our_method finished in #{ActionController::Base.helpers.distance_of_time_in_words(start_time, Time.current)}"
end
end
end

Rails Console Argument Error Creating a New object

I am new to Rails and Ruby development but I am trying to create an object called Currency which takes in two params and does some calculations on them. I am using attr_accessor to set up the params and I put the file inside the lib directory.
Whenever I run rails console and try to do c = Currency.new(100, "CAD") I get the following error:
ArgumentError: wrong number of arguments (given 2, expected 0)
from (irb):5:in `initialize'
from (irb):5:in `new'
from (irb):5
I did make sure to include the file in application.rb. Here is a skeleton of my class:
class Currency
class << self
attr_accessor :input_value, :currency_iso
USD_ISO = "USD"
USD_TO_DM = 2.8054
def converted_value
convert_to_dm
end
private
def convert_to_dm
#input_value / USD_TO_DM
end
end
end
I have looked all over and I am stumped on what this issue may be. I have tried with and without an initialize method and I have tried creating a more basic version.
The problem here is that you are defining the method as a class method. And you are not defining the initialize method with those two params. Let's check the code below:
class Currency
attr_accessor :input_value, :currency_iso
USD_ISO = "USD"
USD_TO_DM = 2.8054
def initialize(input_value, currency_iso)
#input_value = input_value
#currency_iso = currency_iso
end
def converted_value
convert_to_dm
end
private
def convert_to_dm
input_value / USD_TO_DM
end
end
Also, due to you have already defined the attr_accessor you don't need to use the # when calling those attributes.
I found this post. It can help you to understand better the difference between class method and instance method.

Delayed Job inside model - Wrong number of arguments

Here is my model:
class User
def join(race)
#blah blah blah ....
UserMailer.delay.join_race(self, race) #I'm stuck here
end
end
And my UserMailer like this
class UserMailer
def join_race(user, race)
#Another blah blah blah, nothing important here
mail(:to => user.email)
end
end
Now, whenever I call user.join(race), it shows the error like this:
ArgumentError: wrong number of arguments (2 for 1)
from /home/xxx/.rvm/gems/ruby-1.9.3-p194/gems/arel-3.0.2/lib/arel/expressions.rb:3:in `count'
from /home/xxx/.rvm/rubies/ruby-1.9.3-p194/lib/ruby/1.9.1/psych/visitors/yaml_tree.rb:224:in `binary?'
from /home/xxx/.rvm/rubies/ruby-1.9.3-p194/lib/ruby/1.9.1/psych/visitors/yaml_tree.rb:233:in `visit_String'
from /home/xxx/.rvm/rubies/ruby-1.9.3-p194/lib/ruby/1.9.1/psych/visitors/yaml_tree.rb:102:in `accept'
from /home/xxx/.rvm/rubies/ruby-1.9.3-p194/lib/ruby/1.9.1/psych/visitors/yaml_tree.rb:292:in `block in visit_Hash'
from /home/xxx/.rvm/rubies/ruby-1.9.3-p194/lib/ruby/1.9.1/psych/visitors/yaml_tree.rb:290:in `each'
...
If I convert it to normal function (without the .delay in front of join_race), it works.
I found similar issues but they are all about not calling .all method after using where. I suspect that the self could be the problem but I don't know how to make it work. If any of you have any clue, please share with me.
This issue is discussed in https://github.com/rails/arel/issues/149, including a mention of Mailer and delayed jobs, and the related https://github.com/rails/rails/issues/9263.
The following workaround was included:
module Arel
module Nodes
class SqlLiteral < String
def encode_with(coder)
coder['string'] = to_s
end
def init_with(coder)
clear << coder['string']
end
end
end
end
The problem is because I am using Rails 3.2.12. It seems like this problem only occurs in this version of Rails. Therefore, I upgrade my project to Rails 3.2.13 and everything is working properly.

Legacy table with column named "class" in Rails

I've got a legacy table that my rails application shares with another application. It has a column called "class". The first time I reference any attribute in that model, I get an error. Subsequent references to attributes work. Is there a good workaround for this, or should I just go modify the other application that uses this table (ugh)?
>> Member::Ssg.find(:first)
=> #<Member::Ssg ssg_key: #<BigDecimal:10b169688,'0.253E3',4(8)>, org_id: 2, academic_year: 2006, class: true, next_due_date: "2011-06-01", submitted_date: "2006-02-13", notes: nil, owner_id: "1">
>> Member::Ssg.find(:first).notes
NoMethodError: undefined method `generated_methods' for true:TrueClass
from /Library/Ruby/Gems/1.8/gems/activerecord-2.3.8/lib/active_record/attribute_methods.rb:247:in `method_missing'
from (irb):2
>> Member::Ssg.find(:first).notes
=> nil
SOLUTION:
I went with a combination of the Bellmyer solution and adding the code below to my model
class << self
def instance_method_already_implemented?(method_name)
return true if method_name == 'class'
super
end
end
NOTE: Please see the updated solution at the end of this answer. Leaving the original outdated solution for historic reasons.
This has come up often enough (legacy column names interfering with ruby/rails) that I might just make a plugin out of this. Here's how you can fix it right away, though. Create this file in your app:
# lib/bellmyer/create_alias.rb
module Bellmyer
module CreateAlias
def self.included(base)
base.extend CreateAliasMethods
end
module CreateAliasMethods
def create_alias old_name, new_name
define_method new_name.to_s do
self.read_attribute old_name.to_s
end
define_method new_name.to_s + "=" do |value|
self.write_attribute old_name.to_s, value
end
end
end
end
end
And now, in your model:
class Member < ActiveRecord::Base
include Bellmyer::CreateAlias
create_alias 'class', 'class_name'
end
The first parameter to create_alias is the old method name, and the second parameter is the new name you want to call it, that won't interfere with rails. It basically uses the read_attribute and write_attribute methods to interact with the column instead of the ruby methods that get defined by ActiveRecord. Just be sure to use the new name for the field everywhere, like so:
member.class_name = 'helper'
This works with ruby 1.8, but I haven't tested with ruby 1.9 yet. I hope this helps!
UPDATE: I've found a better solution that works in Rails 3, the safe_attributes gem. I've written a blog post explaining how to use it, with example code snippets, and a full sample app you can download from github and play around with. Here's the link:
Legacy Database Column Names in Rails 3
The following works in Rails 6.0.2.2
class ReasonCode < ApplicationRecord
class << self
def instance_method_already_implemented?(method_name)
return true if method_name == 'class'
super
end
end
def as_json(options={})
add_class = attributes.keys.include?('class')
if add_class
if options[:only]
add_class = Array(options[:only]).map(&:to_s).include?('class')
elsif Array(options[:except])
add_class = Array(options[:except]).map(&:to_s).exclude?('class')
end
end
options[:except] = Array(options[:except])
options[:except].push('class')
json = super(options)
json['class'] = attributes['class'] if add_class
json
end
end
Adapted from this answer https://www.ruby-forum.com/t/activerecord-column-with-reserved-name-class/125705/2. The as_json method was added because rendering the record as json gave a SystemStackError (stack level too deep). I followed the serialization code in the Rails repo to only render the class attribute if specified in the as_json options.

Resources