undefined method `set' for nil:NilClass in Rails even though similar code works in irb - ruby-on-rails

The following code works fine in IRB (Interactive Ruby Shell):
require 'prometheus/client'
prometheus = Prometheus::Client.registry
begin
#requests = prometheus.gauge(:demo, 'Random number selected for this users turn.')
rescue Prometheus::Client::Registry::AlreadyRegisteredError => e
end
#requests.set({name: "test"}, 123)
test = #requests.get name: "test"
puts 'output: ' + test.to_s
2.4.0 :018 > load 'test.rb'
output: 123.0
=> true
2.4.0 :019 >
However, when I put the same code into my Ruby on Rails controller, the second time the user uses the application, the following error is returned:
undefined method `set' for nil:NilClass
Can someone tell me when I'm doing wrong? Thank you.
require 'prometheus/client'
class RandomnumbersController < ApplicationController
def index
#randomnumbers = Randomnumber.order('number DESC').limit(8)
#counter = 0
end
def show
#randomnumber = Randomnumber.find(params[:id])
end
def new
end
def create
#randomnumber = Randomnumber.new(randomnumber_params)
prometheus = Prometheus::Client.registry
begin
#requests = prometheus.gauge(:demo, 'Random number selected for this users turn.')
rescue Prometheus::Client::Registry::AlreadyRegisteredError => e
end
#requests.set({name: "test"}, 123)
test = #requests.get name: "test"
#randomnumber.save
redirect_to #randomnumber
end
private
def randomnumber_params
params.require(:randomnumber).permit(:name, :number)
end
end

Because there is no #requests for :demo argument.
When ORM cannot find any info in db it returns nil (NilClass)
and You're trying to do:
#requests.set({name: "test"}, 123)
it's interpreted like:
nil.set({name: "test"}, 123)
why it's causes this issue in second time?
cuz Your code changes #requests name attribute to be test and seems like :demo is not test or maybe in another part of Your app You're replacing/deleting data in database that makes: #requests = prometheus.gauge(:demo, 'Random number selected for this users turn.') to return nil
Solution:
in code level add this fixes to avoid such unpredictable situations (check for nil) :
unless #requests.nil?
#requests.set({name: "test"}, 123)
test = #requests.get name: "test"
end

Related

How to fix Delayed Job deserialization errors after upgrading from Rails 4.2 to 5.1+?

I am working on upgrading an app from Rails 4.2 to 5.2. I have am running into an issue were jobs that were created in 4.2 are raising errors when they are invoked under Rails 5.2.
Delayed::DeserializationError (Job failed to load: not delegated...
I have narrowed it down to a problem after moving from 5.0 to 5.1. In 5.0.7 there is no problem but there is in 5.1.0. I can reproduce on a simple test case (taken from job.handler) by doing YAML.load(yml) where yml:
object: !ruby/object:Account
raw_attributes:
id: '8469'
attributes: !ruby/object:ActiveRecord::AttributeSet
attributes: !ruby/object:ActiveRecord::LazyAttributeHash
types:
id: &4 !ruby/object:ActiveRecord::Type::Integer
precision:
scale:
limit: 8
range: !ruby/range
begin: -9223372036854775808
end: 9223372036854775808
excl: true
values:
id: '8469'
created_at: '2019-11-15 21:16:15.257401'
additional_types: {}
materialized: true
delegate_hash:
id: !ruby/object:ActiveRecord::Attribute::FromDatabase
name: id
value_before_type_cast: '8469'
type: *4
value: 8469
created_at: !ruby/object:ActiveRecord::Attribute::FromDatabase
name: created_at
value_before_type_cast: '2019-11-15 21:16:15.257401'
type: !ruby/object:ActiveRecord::AttributeMethods::TimeZoneConversion::TimeZoneConverter
subtype: !ruby/object:ActiveRecord::ConnectionAdapters::PostgreSQL::OID::DateTime
precision:
scale:
limit:
value: 2019-11-15 21:16:15.257401000 Z
new_record: false
active_record_yaml_version: 0
That gives the error ArgumentError (not delegated). I have found that removing the subtype under created_at makes the problem go away but not idea why. I have tried changing the subtype to something simple like an integer and get the same problem.
Anyone have thoughts on how to approach this? I am really surprised that I have not found any info on others running into the same problem.
Turned out it was bigger than just 5.0 being able to serialize but 5.1 couldn't. There are a bunch of methods and classes that have been moved or removed in Rails 5 so it is just difficult to deserialize. With some help by posting an issue in the delayed job issues (https://github.com/collectiveidea/delayed_job/issues/1111) I was able to come up with a solution. I ended up writing the following migration that deserializes and then serializes all delayed_jobs.
migrate_dj_to_rails5.rb
require 'ruby-progressbar'
class MigrateDjToRails5 < ActiveRecord::Migration[5.2]
include MigrationHelper
def up
execute create_backup('delayed_jobs')
migrator = DelayedJobMigrator.new(Delayed::Job.all)
migrator.migrate
end
def down
execute copy_field_from_backup('delayed_jobs', 'handler')
execute load_dropped_records_from_backup('delayed_jobs')
end
class DelayedJobMigrator
def initialize(jobs = Delayed::Job.all)
#exceptions = []
#corrected_job_ids = []
#deleted_job_ids = []
#jobs = jobs
#progress_bar = ProgressBar.create(total: jobs.count, format: "%t: |%w| %e")
end
def migrate_job(job)
begin
data = YAML.load_dj(job.handler)
rescue
begin
job.payload_object = TempToRuby.create.accept(Psych.parse(job.handler))
rescue => exception
if exception.message =~ /Couldn't find (.+) with 'id'=/
# job is no longer valid
#deleted_job_ids << job.id
job.delete
end
#exceptions << exception
return
end
job.save
#corrected_job_ids << job.id
end
end
def migrate
#jobs.find_each do |job|
migrate_job(job)
#progress_bar.increment
end
#corrected_job_ids.each do |id|
puts "Corrected job_id: #{id}"
end
#exceptions.each do |exception|
puts "Exceptions:: #{exception}"
end
puts "#{#corrected_job_ids.count} jobs corrected"
puts "#{#exceptions.count} exceptions encountered (should be same as jobs deleted)"
puts "#{#deleted_job_ids.count} jobs deleted"
end
class TempToRuby < Delayed::PsychExt::ToRuby
def visit_Psych_Nodes_Mapping(object)
if %r{^!ruby/object:ActiveRecord::AttributeSet}.match(object.tag.to_s)
{}
elsif %r{^!ruby/object:(.+)$}.match(object.tag.to_s)
klass = resolve_class(Regexp.last_match[1])
if klass < ActiveRecord::Base
payload = Hash[*object.children.map { |c| accept c }]
return super unless payload['raw_attributes']
id = payload['raw_attributes'][klass.primary_key]
klass.unscoped.find(id)
else
super
end
else
super
end
end
end
end
end
migration_helper.rb
# Helper module for commonly used migration related methods
module MigrationHelper
def create_backup(table_name, suffix='_copy')
table_name_copy = table_name + suffix
<<-SQL
DROP TABLE IF EXISTS #{table_name_copy};
CREATE TABLE #{table_name_copy} AS TABLE #{table_name};
SQL
end
def copy_field_from_backup(table_name, field, suffix='_copy')
table_name_copy = table_name + suffix
records_to_update = <<-SQL
UPDATE #{table_name}
SET #{field} = #{table_name_copy}.#{field}
FROM #{table_name_copy}
WHERE #{table_name_copy}.id = #{table_name}.id
SQL
end
def load_dropped_records_from_backup(table_name, suffix='_copy')
table_name_copy = table_name + suffix
<<-SQL
INSERT INTO #{table_name}
SELECT #{table_name_copy}.* FROM #{table_name_copy}
LEFT JOIN #{table_name} on #{table_name}.id = #{table_name_copy}.id
where #{table_name}.id is null
SQL
end
end

Rspec which actually tests method with instance variable

I have a service method as
class Service
def maximum_match
max = #hash.values.max
Hash[#hash.select { |_k, v| v == max }]
end
end
My test is like
context 'Finding tags count' do
it 'counts tags and returns maximum match' do
service = Service.new
expect(service.maximum_match).to eq some_result
end
end
How can I pass any values #hash to run my test?
Error is NoMethodError:undefined method 'values' for nil:NilClass
Ninja, you can use service.instance_variable_set(#hash, your_value) right above the expect line
source

NoMethodError: undefined method `errors' for []:Array

I get NoMethodError when I run test for the code below
csv_importer.rb
require 'csv_importer/engine'
class WebImport
def initialize(url)
#url = url
end
def call
url = 'http://example.com/people.csv'
csv_string = open(url).read.force_encoding('UTF-8')
string_to_users(csv_string)
end
def string_to_users(csv_string)
counter = 0
duplicate_counter = 0
user = []
CSV.parse(csv_string, headers: true, header_converters: :symbol) do |row|
next unless row[:name].present? && row[:email_address].present?
user = CsvImporter::User.create row.to_h
if user.persisted?
counter += 1
else
duplicate_counter += 1
end
end
p "Email duplicate record: #{user.email_address} - #{user.errors.full_messages.join(',')}" if user.errors.any?
p "Imported #{counter} users, #{duplicate_counter} duplicate rows ain't added in total"
end
end
csv_importer_test.rb
require 'csv_importer/engine'
require 'test_helper'
require 'rake'
class CsvImporterTest < ActiveSupport::TestCase
test 'truth' do
assert_kind_of Module, CsvImporter
end
test 'should override_application and import data' do
a = WebImport.new(url: 'http://example.com/people.csv')
a.string_to_users('Olaoluwa Afolabi')# <-- I still get error even I put a comma separated list of attributes that is imported into the db here.
assert_equal User.count, 7
end
end
csv format in the url in the code:
This saves into DB once I run the Rake Task
Name,Email Address,Telephone Number,Website
Coy Kunde,stone#stone.com,0800 382630,mills.net
What I have done to debug:
I use byebug and I figured out the in csv_importer_test.rb, the line where I have a.string_to_users('Olaoluwa Afolabi') is throwing error. See byebug error below:
So, I when I run rails test, I get the error below:
So, how do I solve this error, I have no clue what am doing wrong??
If you don't have any row in your csv_string, this line:
user = CsvImporter::User.create row.to_h
isn't executed, so user variable holds previous value, which is []:
user = []
As we know, there's no method errors defined for Array, yet you try to call it in this line:
p "Email duplicate record: #{user.email_address} - #{user.errors.full_messages.join(',')}" if user.errors.any?
and that's why you get an error.

Why doesn't my Object update?

I have this test:
describe 'check_account_status' do
it 'should send the correct reminder email one week prior to account disablement' do
# Three weeks since initial email
reverification = create(:reverification)
initial_notification = reverification.twitter_reverification_sent_at.to_datetime
ActionMailer::Base.deliveries.clear
Timecop.freeze(initial_notification + 21) do
Reverification.check_account_status
ActionMailer::Base.deliveries.size.must_equal 1
ActionMailer::Base.deliveries.first.subject.must_equal I18n.t('.account_mailer.one_week_left.subject')
reverification.reminder_sent_at.class.must_equal ActiveSupport::TimeWithZone
reverification.notification_counter.must_equal 1
must_render_template 'reverification.html.haml'
end
end
This test produces this error:
check_account_status#test_0001_should send the correct reminder email one week prior to account disablement [/Users/drubio/vms/ohloh-ui/test/models/reverification_test.rb:67]:
Expected: ActiveSupport::TimeWithZone
Actual: NilClass
Here is my code:
class Reverification < ActiveRecord::Base
belongs_to :account
FIRST_NOTIFICATION_ERROR = []
class << self
def check_account_status
Reverification.where(twitter_reverified: false).each do |reverification|
calculate_status(reverification)
one_week_left(reverification)
end
end
private
def calculate_status(reverification)
#right_now = Time.now.utc.to_datetime
#initial_email_date = reverification.twitter_reverification_sent_at.to_datetime
#notification_counter = reverification.notification_counter
end
def one_week_left(reverification)
# Check to see if three weeks have passed since the initial email
# and check to see if its before the one day notification before
# marking an account as spam.
if (#right_now.to_i >= (#initial_email_date + 21).to_i) && (#right_now.to_i < (#initial_email_date + 29).to_i)
begin
AccountMailer.one_week_left(reverification.account).deliver_now
rescue
FIRST_NOTIFICATION_FAILURE << account.id
return
end
update_reverification_fields(reverification)
end
end
def update_reverification_fields(reverification)
reverification.notification_counter += 1
reverification.reminder_sent_at = Time.now.utc
reverification.save!
reverification.reload
end
end
Forgive the indentation, but what seems to be the problem, is that my reverification object doesn't update when it leaves the check_account_status method. I've placed puts statements through out the code and I can see without a doubt that the reverification object is indeed updating. However after it leaves the update_reverification_fields and returns to the test block, the fields are not updated. Why is that? Has anyone encountered this?
I believe you have a scope issue, the methods you call from check_account_status certainly don't return the updated object back to your method and Ruby only passes parameters by value.
Try something like this instead:
def check_account_status
Reverification.where(twitter_reverified: false).each do |reverification|
reverification = calculate_status(reverification)
reverification = one_week_left(reverification)
end
end
private
def calculate_status(reverification)
# ...
reverification
end
def one_week_left(reverification)
# ...
reverification = update_reverification_fields(reverification)
reverification
end
def update_reverification_fields(reverification)
# ...
reverification
end
The problem is that reverification object in your test and objects inside of check_account_status are different instances of the same model.
def update_reverification_fields(reverification)
reverification.notification_counter += 1
reverification.reminder_sent_at = Time.now.utc
reverification.save!
reverification.reload
end
This reload here, it's doing nothing. Let's walk through your test.
# check_account_status runs a DB query, finds some objects and does things to them
Reverification.check_account_status
# this expectation succeeds because you're accessing `deliveries` for the
# first time and you don't have it cached. So you get the actual value
ActionMailer::Base.deliveries.size.must_equal 1
# this object, on the other hand, was instantiated before
# `check_account_status` was called and, naturally, it doesn't see
# the database changes that completely bypassed it.
reverification.reminder_sent_at.class.must_equal ActiveSupport::TimeWithZone
So, before making expectations on reverification, reload it, so that it pulls latest data from the DB.
reverification.reload # here
reverification.reminder_sent_at.class.must_equal ActiveSupport::TimeWithZone

Rake task - undefined method

I tinkering my way into creating a rake task that grabs the amount of checkins for a given page throw facebook-graph. I usign the koala gem and rails.
I do this by creating a rake task:
task :get_likes => :environment do
require 'koala'
# Grab the first user in the database
user = User.first
# Loop throw every school & and call count_checkins
School.columns.each do |column|
user.facebook.count_checkins(column.name, user)
end
end
# Count like for every school else return 0
def count_checkins(name, u)
a = u.facebook.fql_query('SELECT checkins FROM page WHERE name = "' + name + '"')
if a[0].nil?
return 0
else
return b = a[0]["checkins"]
end
end
# Initialize an connection to the facebook graph
def facebook
#facebook ||= Koala::Facebook::API.new(oauth_token)
end
But I get a error:
private method `count_checkins' called for #<Koala::Facebook::API:0x007fae5bd348f0>
Any ideas or better way to code a rake task would be awesome!
Check the full error here: https://gist.github.com/shuma/4949213
Can't really format this properly in a comment, so I'll put it in an answer. I would put the following into the User model:
# Count like for every school else return 0
def count_checkins(name)
a = self.facebook.fql_query('SELECT checkins FROM page WHERE name = "' + name + '"')
if a[0].nil?
return 0
else
return b = a[0]["checkins"]
end
end
# Initialize an connection to the facebook graph
def facebook
#facebook ||= Koala::Facebook::API.new(oauth_token)
end
Then change the rake task to:
task :get_likes => :environment do
require 'koala'
# Grab the first user in the database
user = User.first
# Loop throw every school & and call count_checkins
School.columns.each do |column|
user.count_checkins(column.name)
end
end
That way count_checkins is defined on the user model, rather than trying to modify a class within Koala -- and you aren't duplicating work by having to pass around more User and Facebook parameters than are necessary.

Resources