Delayed_job in rails raising 'nil object' error - ruby-on-rails

I'm struggling to use Delayed_job (collective idea v2.0 in a Rails 2.3.8 app).
I'm calling the job from an application_controller method:
...
Delayed::Job.enqueue(S3MoverJob.new(docs))
Where docs is a Hash with ids and names of files.
At my Lib directory I have the class S3MoverJob:
class S3MoverJob < Struct.new(:docs)
def perform
#setup connection to Amazon
...
#connect
...
#loop to move files not transfered already
docs.each do |id,file_name|
# Move file
begin
doc = Document.find(id)
... perform actions
rescue Exception => exc
raise "failed!"
end
end
end
end
The problem is that it's raising: S3MoverJob failed with NoMethodError: You have a nil object when you didn't expect it!
I looked into the handler, in the DB, and it was delivering to the perform method the Yaml file with the list of ids and file names, like this:
docs:
3456: name_of_file_01.png
4567: name_of_file_02.txt
What am I missing? Thanks for helping me.

I think you should puts more information for debug.
Such as: add statements like 'Rails.logger.info "some stuff"' under the perform method to see where the exception been throw

My fault. I didn't know it was an idenpendent process and needed some requirements:
require 'yaml'
require 'uri'
class Document < ActiveRecord::Base
end
After that, everything works fine.
Thanks anyway.

Related

uninitialised constant after upgrade of Gemfile

I've just updated my Gemfile.
At the beginning I thought problem came from Zeitwerk (from 2.4.2 to 2.5.4) but I've downgraded it and I still have an issue on my spec. I've isolated that the problem does not come from RSpec and dependencies.
Actually, RSpec does not found a class which is defined within another file and does not match the file name/class name.
Important point: Filter::MyStandardError is found.
# app/services/filter/my_standard_error.rb
module Filter
class MyStandardError < StandardError; end
class MySpecificError < MyStandardError; end
# ...
end
# app/services/filter/my_tested_service.rb
module Filter
class MyTestedService
def initialize
raise ::Filter::MySpecificError
end
end
end
RSpec.describe Filter::MyTestedService do
subject { described_class.new }
it 'raises an error'
expect{subject}.to raise_error(::Filter::MySpecificError)
end
end
And I got the error:
NameError:
uninitialized constant Filter::MySpecificError
I got the Changelog but breaking changes are not used on my configuration.
Does anybody have an idea for this one?
You do not need to add app/services to the autoload paths, that is done automatically by Rails. I'd suggest to remove that configuration to keep things simple/idiomatic.
The implementation of app/services/filter.rb should not be needed. Your application is doing something that is not right, we just need to find it.
Could you please delete app/services/filter.rb, throw Rails.autoloaders.log! in config/application.rb, trigger the error, and share the traces?
After reading one-file-one-constant-at-the-same-top-level
I found this to fix my issue
# app/services/filter.rb
class Filter
class MyStandardError < StandardError; end
class MySpecificError < MyStandardError; end
end
# app/services/filter/my_tested_service.rb
class Filter
class MyTestedService
def initialize
raise ::Filter::MySpecificError
end
end
end
I still don't know why it was working before..
You cannot define two constants at the same level in the same file. It is one constant, one file. This has not changed in Zeitwerk upgrades. You need one file for the standard error, and another file for the specific error.

Why does Ruby not throw an error in an instance where the class name and file name of the class are mismatched?

Why isn't the Ruby interpreter throwing a NameError in this instance here?
class OrangeTurtle
self.table_name = 'turtles'
end
Filename: orange_turtles.rb
This answer might sound like a cop out, but it doesn't throw an error because Ruby doesn't care even the slightest what your filenames are called.
e.g. in file asdfasdf.no_rb_ending_here we can have
#!/usr/bin/env ruby
module Something
class Test
def test
puts 'test'
end
end
end
class SomethingElse
def otherThings
puts 'haha'
end
end
Then to make things even weirder, I can have a separate file that modifies (monkey patches) the classes defined in that file.
in more_stuff.rb
#!/usr/bin/env ruby
require_relative 'asdfasdf.no_rb_ending_here'
module Something
class Test
def test2
test()
puts '2'
end
end
end
class SomethingElse
def moreThings
otherThings()
puts 'MOAR'
end
end
Something::Test.new.test2()
# test
# 2
SomethingElse.new.moreThings()
# haha
# MOAR
Ruby is pretty cool - you don't get errors for things that don't NEED to cause an error.
The name error, or uninitialized constant error only appears in Rails. The reason for that is, that active record (which is also a general design pattern) is mapping the tables in the database with the models (or with objects in general).
Active Record can only make that connection via the naming conventions for files and the classes.
As mentioned in the other answer, pure ruby doesn't need to comply with these conventions. However, it is a general rule to name the files like the classes they contain to have better organised code.

How to create a ActiveRecord::RecordInvalid for testing?

I have this piece of code that I am trying to test:
def error_from_exception(ex)
if ex.is_a?(ActiveRecord::RecordInvalid)
...
To get into the if block, I need to pass in the correct ex param.
How do I create an ActiveRecord::RecordInvalid?
With rspec, I'm trying to do something like this:
context 'exception is ActiveRecord::RecordInvalid' do
it 'returns the validation error' do
begin
raise ActiveRecord::RecordInvalid
rescue Exception => ex
binding.pry
###
# From my pry session:
# $ ex
# $ <ArgumentError: wrong number of arguments (0 for 1)>
# $ ex.class
# $ ArgumentError < StandardError
###
end
end
end
How do I find out what argument type the library is looking for?
RecordInvalid link
None of the above methods worked for me so I finally did the following while doing a spec:
class InvalidRecord
include ActiveModel::Model
end
raise ActiveRecord::RecordInvalid.new(InvalidRecord.new)
Hope it helps!
EDIT: This is now possible in Rails 5.1.1. The record argument is no longer required after this commit: https://github.com/rails/rails/commit/4ff626cac901b41f86646dab1939d2a95b2d26bd
If you are on a Rails version under 5.1.1, see the original answer below:
It doesn't seem like it is possible to raise an ActiveRecord::RecordInvalid by itself. If you look at the source code for ActiveRecord::RecordInvalid, it requires a record when initializing:
class RecordInvalid < ActiveRecordError
attr_reader :record # :nodoc:
def initialize(record) # :nodoc:
#record = record
...
end
end
(source: https://github.com/rails/rails/blob/master/activerecord/lib/active_record/validations.rb)
Something you could do to work around this would be to simply create an actual record that is invalid and attempt to save it using save! (such as calling User.new.save! when User.name is required). However, keep in mind that this might potentially become a problem in the future if the model you use is changed and it becomes valid inside your test (User.name is no longer required).
I needed to do something similar to test my code's handling of ActiveRecord::RecordInvalid. I wanted to do
allow(mock_ar_object).to receive(:save!).and_raise(ActiveRecord::RecordInvalid)
but that gives ArgumentError: wrong number of arguments (0 for 1) when RSpec tries to instantiate RecordInvalid.
Instead I wrote a RecordInvalid subclass and overrode initialize like this:
class MockRecordInvalid < ActiveRecord::RecordInvalid
def initialize
end
end
allow(mock_ar_object).to receive(:save!).and_raise(MockRecordInvalid)
Then a rescue ActiveRecord::RecordInvalid will catch MockRecordInvalid and MockRecordInvalid.new.is_a?(ActiveRecord::RecordInvalid) is true.
ActiveRecord::RecordInvalid needs an object to be created.
If you want to test just the exception itself, try:
null_object = double.as_null_object
ActiveRecord::RecordInvalid.new(null_object)
Understand the double as null object here (https://www.relishapp.com/rspec/rspec-mocks/v/2-6/docs/method-stubs/as-null-object)
I am using update_attribute(attr, 'value') instead of save! and I can mock the update_attribute method as follows:
expect(mock_object).to receive(:update_attribute).with(attr, 'value').and_raise(ActiveRecord::RecordInvalid)
We can stub it to the Model like this,
ModelName.any_instance.stubs(<method_name>).raises(ActiveRecord::RecordInvalid.new(record))
Example:
Post.any_instance.stubs(:save!).raises(ActiveRecord::RecordInvalid.new(post))

Rails doesn't load classes on deserializing YAML/Marshal objects

Rails: 3.0.3
Ruby: 1.9.2
Trying to deserialize a very simple object using YAML.load or Marshal.load produces a corrupted object because the class which belongs to is not required on the deserializing process.
Example:
# app/models/my_model.rb
class MyModel
attr_accessor :id
end
# test/unit/serializing_test.rb
require 'test_helper'
class SerializingTest < Test::Unit::TestCase
def test_yaml_serialize_structure
my_model = MyModel.new
my_model.id = 'my model'
File.open( "#{Rails.root}/tmp/object.yml" , 'w' ) do |f|
YAML::dump(my_model, f)
end
end
def test_yaml_deserialize_structure
object = YAML.load_file "#{Rails.root}/tmp/object.yml"
assert( object.instance_of? MyModel )
assert_equal( 'my model', object.id )
end
end
With this code we can run this shell console session without any error:
$ ruby -Itest test/unit/serializing_test.rb -n test_yaml_serialize_structure
$ ruby -Itest test/unit/serializing_test.rb -n test_yaml_deserialize_structure
But if I run the deserialization calls from a Rails console the object is not deserialized properly because the class is never required:
$ rails c
ruby-1.9.2-p0 > object = YAML.load_file "#{Rails.root}/tmp/object.yml"
=> #<Syck::Object:0x0000010322ea30 #class="MyModel", #ivars={"id"=>"my model"}>
I know the only problem is that the class is not required because if I require it by hand everything works:
ruby-1.9.2-p0 > require "#{Rails.root}/app/models/my_model"
=> ["MyModel"]
ruby-1.9.2-p0 > object = YAML.load_file "#{Rails.root}/tmp/object.yml"
=> #<MyModel:0x0000010320c8e0 #id="my model">
I have presented only the YAML examples but with Marshal is pretty the same.
Also say that although I'm reproducing the problem in a Rails console originally this problem was turning me crazy in a normal request to my application.
So the question is: How can I deserialize objects in Rails without have to require all my classes by hand?
Thanks
f.
Well, after read #tadman and a bunch of answers I have received in the spanish ror mailing list [1] I have collected a few hot tips when you have to deal with Ruby deserializing and class loading in Rails:
Super fast solution
Use config.cache_classes = true in your development.rb but you will lost the class auto-refreshing.
Better solution
Require all the classes that are gonna be deserialized but not with require but with require_dependency[2] so in development environment the class auto-refreshing will remain working.
Elegant solution
Monkey-patch the YAML and the Marshal gem to tell them to call require_dependency when they find a non-defined class to deserialize.
And #Xavi has sent me a proposition of monkey-patch Marshal (he says he wrote it on the air and it is not tested so use it in your own risk) [3]
[1] http://lists.simplelogica.net/pipermail/ror-es/2011-January/024787.html
[2] http://apidock.com/rails/ActiveSupport/Dependencies/Loadable/require_dependency
[3] http://lists.simplelogica.net/pipermail/ror-es/2011-January/024796.html
I described this "issue" on GitHub: https://github.com/rails/rails/issues/1585
To automatically require classes on YAML loading in the manner #fguillen suggests is elegant, I wrote this short monkey-patch.
It simply attempts to require_dependency any class the Psych ToRuby class resolves to classes.
Works for me in a serialised Active Record that stores a custom class instance, YMMV.
module Psych::Visitors
ToRuby.class_eval do
alias :resolve_class_without_autoload :resolve_class
def resolve_class klassname
begin
require_dependency klassname.underscore
rescue NameError, LoadError
end
resolve_class_without_autoload klassname
end
end
end
I had to adapt #ben-patterson's answer a bit to make it work (using Rails 5.0.2):
module Psych::Visitors
ToRuby.class_eval do
def resolve_class(klassname)
begin
class_loader.load klassname
rescue ArgumentError
require_dependency klassname.underscore
klassname.constantize
end
end
end
end
As far as I know, both YAML and Marshal do not make use of the Rails autoloader. You must go ahead and pre-load any classes that might need to be deserialized.
It's a bit if a fuss, especially in the development environment where almost nothing is loaded before it is needed.
In rails version 7 the monkey patch has to catch Psych::DisallowedClass error instead of ArgumentError or NameError, LoadError as proposed by
#ben-patterson and #panzi
module Psych::Visitors
ToRuby.class_eval do
def resolve_class(klassname)
begin
class_loader.load klassname
rescue Psych::DisallowedClass => e
require_dependency klassname.underscore
klassname.constantize
end
end
end
end

Rails generator m.directory returns can't convert nil into String

I have written this generator code but it returns 'can't convert nil into String' when I call m.directory inside the manifest. Anyone know what had happened?
class AuthGenerator < Rails::Generator::NamedBase
attr_reader :user_class_name
def initialize(runtime_args, runtime_options={})
#user_class_name="User"
#controller_class_name="AccountController"
#user_class_file_name="#{#user_class_name}.rb"
#controller_class_file_name="#{#controller_class_name}.rb"
end
def manifest
record do |m|
m.class_collisions #controller_class_name, #user_class
puts #user_class_name
m.directory File.join('app/models', #user_class_name)
end
end
end
Where is it choking? Please post the full error. You can see the source of the directory method here.
Plus, you probably just want
m.directory File.join('app/models')
Having an app/models/user directory for your generated code is not standard -- unless you're intending namespacing, which it doesn't look like.
Your initialize method needs a call to super.

Resources