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
Related
Rails 6.1 has released an improvement for a tag_helper (specifically for the rich_text_area from ActionText), that I would require now, for my Rails 6.0.x app. Basically the improvement is a very small change in just one line of code, so it should be simple to just monkey patch the current rails method and get the improvement now, right?
Specifically I'm trying to monkey patch the following ActionText tag helper method (link to Github rails/rails) with the following code, but the code is not applied. What am I doing wrong?
lib/core_ext/rich_text_area.rb
module ActionView::Helpers
class Tags::ActionText < Tags::Base
def render
options = #options.stringify_keys
debugger
add_default_name_and_id(options)
options["input"] ||= dom_id(object, [options["id"], :trix_input].compact.join("_")) if object
#template_object.rich_text_area_tag(options.delete("name"), options.fetch("value") { editable_value }, options.except("value"))
end
end
end
Added the following to a file in config/initializers
Dir[File.join(Rails.root, 'lib', 'core_ext', '*.rb')].each { |l| require l }
You can monkey patch in a cleaner way something like this in your lib/core_ext/rich_text_area.rb file:
require 'action_text/tag_helper'
module ActionTextOverride
def render
options = #options.stringify_keys
add_default_name_and_id(options)
options['input'] ||= dom_id(object, [options['id'], :trix_input].compact.join('_')) if object
#template_object.rich_text_area_tag(options.delete('name'), options.fetch('value') { editable_value }, options.except('value'))
end
end
class ActionView::Helpers::Tags::ActionText
prepend ActionTextOverride
end
Note: The error you were getting RailsError: uninitialized constant ActionView::Helpers::Tags::ActionText (NameError) when trying to use class_eval can be solved by using require 'action_text/tag_helper'
Source: When monkey patching an instance method, can you call the overridden method from the new implementation?
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
I am using Solidus with Ruby on Rails to create a webshop and I have multiple modules for that webshop.
So, I defined a me controller into an module called 'solidus_jwt_auth' with the followin code:
module Spree
module Api
class MeController < Spree::Api::BaseController
def index
...
end
def orders
...
end
def addresses
...
end
end
end
end
I want to extend this in another module called 'solidus_prescriptions' so I created a decorator for this with the following code me_decorator:
if defined? Spree::Api::MeController.class
Spree::Api::MeController.class_eval do
def prescriptions
...
end
def create_prescription
...
end
private
def prescription_params
params.require(:prescription).permit(
*Spree::CustomerPrescription.permitted_attributes
)
end
end
end
And for this I wrote unit tests in solidus_prescription module and integration tests in webshop. The unit tests are working fine, but the integration tests are giving the following error:
Error:
MeEndpointsTest#test_me/prescriptions_post_endpoint_throws_an_error_when_wrong_params:
AbstractController::ActionNotFound: The action 'create_prescription' could not be found for Spree::Api::MeController
test/integration/me_endpoints_test.rb:68:in `block in '
Which means that he can not find the MeController defined in another module. How can I make the check if the MeController is defined since the code bellow does not help me with anything:
if defined? Spree::Api::MeController.class
end
This worked in the end:
def class_defined?(klass)
Object.const_get(klass)
rescue
false
end
if class_defined? 'Spree::Api::MeController'
....
end
if defined? should do exactly what you want it to do in theory. The problem is you're checking if defined? Spree::Api::MeController.class. The #class of your class is Class. So what you're really getting is if defined? Class which will always be true!
This issue is most likely not that the conditional is failing but that it's never getting read. Rails lazy loads most of the code you write, meaning the file is not read until it's called somewhere in execution.
The decorator module should just contain the methods you want to add, without the conditionals or the use of class_eval. Then in the original class you can include it.
module Spree
module Api
class MeController < Spree::Api::BaseController
include MeDecorator
end
end
end
If for any reason you're not certain MeDecorator will be defined, don't use defined?, because defined? MeDecorator will not actually go looking for it if it's not defined and load the necessary file. It will return nil if the constant has no value. Just rescue a NameError
module Spree
module Api
class MeController < Spree::Api::BaseController
begin
include MeDecorator
rescue NameError => e
logger.error e
end
end
end
end
I'm backporting an ActiveSupport method from Rails 5 into a class within a Rails 4 app. There won't be a naming collision since the class name will be different, but I would like to warn or raise an exception when the class is loaded in Rails 5 (after an upgrade) to remind us to remove the code. Is it possible to do that?
I suppose it depends on how you're loading the class. But, to check a version, see https://github.com/rails/rails/blob/master/version.rb — Rails::VERSION::MAJOR will be your friend here.
What I would do is hook into included
class Tracked
def self.tracked
#tracked ||= []
end
end
module Track
def self.included(base)
puts "Woohoo I am included."
if Rails::VERSION::MAJOR == 5
puts "yeah, 5"
else
puts "thanks, DHH"
end
Tracked.tracked << base
end
end
class Steve
include Track
end
I'm using
Ruby version 1.8.7
Rails version 3.0.3
I have a method called alive in every model of my rails app:
def alive
where('deleter is null')
end
I don't want to copy this code in every model so I made a /lib/life_control.rb
module LifeControl
def alive
where('deleter is null')
end
def dead
where('deleter is not null')
end
end
and in my model (for example client.rb) I wrote:
class Client < ActiveRecord::Base
include LifeControl
end
and in my config/enviroment.rb I wrote this line:
require 'lib/life_control'
but now I get a no method error:
NoMethodError in
ClientsController#index
undefined method `alive' for
#<Class:0x10339e938>
app/controllers/clients_controller.rb:10:in
`index'
what am I doing wrong?
include will treat those methods as instance methods, not class methods. What you want to do is this:
module LifeControl
module ClassMethods
def alive
where('deleter is null')
end
def dead
where('deleter is not null')
end
end
def self.included(receiver)
receiver.extend ClassMethods
end
end
This way, alive and dead will be available on the class itself, not instances thereof.
I'm aware this is a pretty old question, the accepted answer did work for me, but that meant me having to re-write a lot of code because i have to change the module to a nested one.
This is what helped me with my situation and should work with most of today's applications.(not sure if it'll work in the ruby/rails version in the question)
instead of doing include use extend
So as per the question, the sample code would look like:
class Client < ActiveRecord::Base
extend LifeControl
end
Just put this line in application.rb file
config.autoload_paths += Dir["#{config.root}/lib/**/"]
Edited:
This line is working fine for me.
I want to suggest one more thing, ruby 1.8.x is not compatible with rails 3.x.
So just update your ruby for version 1.9.2
Following is my POC
In lib folder:
lib/test_lib.rb
module TestLib
def print_sm
puts "Hello World in Lib Directory"
end
end
In model file:
include TestLib
def test_method
print_sm
end
And In application.rb
config.autoload_paths += Dir["#{config.root}/lib/**/"]
Now you can call test_method like this in controller:
ModelName.new.test_method #####Hello World in Lib Directory