Ruby including Errors module in another module - uninitialized constant Errors - ruby

I've got NON-RAILS app where I want to include Errors module to Formatter module to access the method error_class from Errors. Like below:
#lib/formatter.rb
module Formatter
module_function
include ::Errors
def format(number)
number.delete!(' ')
raise error_class(:invalid_number), 'Invalid phone number, check your entries' unless valid?(number)
end
end
#lib/errors.rb
module Errors
class FormatterExceptionError < StandardError; end
PhoneNumberInvalid = Class.new(FormatterExceptionError)
def error_class(status)
case status
when :invalid_number
PhoneNumberInvalid
end
end
end
With this code I'm getting an error:
Failure/Error: include ::Errors
NameError:
uninitialized constant Errors
Did you mean? Errno

Non-rails apps doesn't have auto-load classes, so you need to manually require it.
Also one thing of your code, I don't know why you are using :: before constant, I don't see any naming-confusion in order to use it. It is used only when you have your Errors modules on different namespace, and you need to resolve the one from your namespace.
# lib_formatter
require 'errors'
module Formatter
extend Errors
# ...
end
The second issue with your code, that you have defined instance method, but where you include your code there class method (module method)
So errors class should be adjusted to
module Errors
# ...
module_function
# ...
end
The third issue is include/extend misusage.
Include used to make all methods from module you using to become instance methods.
Extend used to make all methods from module you using to become class methods.
include/extend info

Related

Stubbing a module, its methods, and inner classes with RSpec

I have a module like this that I'm trying to write unit tests for
module MyThing
module Helpers
def self.generate_archive
# ...
::Configuration.export(arg)
rescue ::Configuration::Error => error
raise error
end
end
end
The ::Configuration module can't exist in my unit testing environment for reasons that are beyond my control, so I need to stub it out. Here's what I've come up with so far.
RSpec.describe 'MyThing' do
it 'generates an archive' do
configuration_stub = stub_const("::Configuration", Module.new)
configuration_error_stub = stub_const("::Configuration::Error", Class.new)
expect_any_instance_of(configuration_stub).to receive(:export).with("arg")
MyThing::Helpers.generate_archive
end
end
This gets me an error.
NoMethodError:
Undefined method `export' for Configuration:Module
If I put the configuration_stub definition inline with the expect_any_instance_of like this
RSpec.describe 'MyThing' do
it 'generates an archive' do
configuration_error_stub = stub_const("::Configuration::Error", Class.new)
expect_any_instance_of(stub_const("::Configuration", Module.new)).to receive(:export).with("arg")
MyThing::Helpers.generate_archive
end
end
I also get an error.
NameError:
Uninitialized constant Configuration::Error
...
# --- Caused by: ---
# NoMethodError:
# Undefined method `export' for Configuration:Module
expect_any_instance_of works on instance methods. export is being called as a class method.
Instead, use a normal expect on the class.
expect(Configuration).to receive(:export).with("arg")
Note: it is not necessary to write ::Configuration in the tests. The :: is to clarify between MyThing::Helpers::Configuration and Configuration.
Note: if you're calling methods directly on Configuration it should probably be a Class not a Module.
Note: instead of calling methods on a class, consider using a Configuration object. App.config.export where App.config returns the default Configuration object. This is more flexible.
The problem with that is RSpec will verify that Configuration.export exists. It doesn't. You could turn off verification, or you could make a real class to test with.
before {
stub_const(
"Configuration",
Class.new do
def self.export(*args)
end
end
)
}
it 'exports' do
expect(Configuration).to receive(:export).with("arg")
Configuration.export("arg")
end
You could write a stub Configuration module for testing only and put it into spec/support, but your tests are increasingly divorced from reality.
The real problem is your project should have a real Configuration class!
I'm going to guess the real Configuration contains production information which cannot be checked into the repository. This is a common anti-pattern. The Configuration module should hide the details of where the configuration values are coming from. There should be independent development, test, and production configurations. There are many ways of doing this, the most common are to use environment specific config files, or to store environment specific config values in environment variables.
While you could mock the Configuration::Error exception, there should be no reason Configuration::Error cannot exist in your test environment. Add it and simulate an error like so:
context 'when Configuration.export raises an error'
before {
allow(Configuration).to receive(:export).and_raise(Configuration::Error)
}
it 'does whatever its supposed to do' do
end
end

Why would `reload!` in the Rails console cause "superclass mismatch for class" (Rails 4)

I am getting the "superclass mismatch for class" error when I run reload! in the Rails console. I have some super simple classes defined in ruby, something like this:
# base_class.rb
module A
module B
module C
class BaseClass
def close
#stub
end
end
end
end
end
And:
# more_specific.rb
module A
module B
module C
class MoreSpecific < BaseClass
def initialize
# ...
end
def close
end
end
end
end
end
I can see that in fact there's a problem because if I do this before I do reload!:
A::B::C::MoreSpecific.superclass.equal? A::B::C::BaseClass
I get true, and then if I do it after I get the error, I get a false. Additionally, the object_id of the BaseClass does in fact change.
Why might this happen? I've checked for additional references to the MoreSpecific class in the codebase because I thought that might lead to the BaseClass being established as a constant more than once, but did not see anything.
What could be causing the object_id of A:B:C::BaseClass to switch after the reload!?
Autoloading Modules Without a Require Statement
reload! is a Rails console method, not a standard Ruby method. While there could be other causes for the behavior you're seeing, it's worth noting that your C module in more_specific.rb doesn't require base_class at runtime, and may be losing its lookup; Rails may not autoload modules the way you're expecting without it.
Make sure that modules that depend on BaseClass contain a require base_class statement to be executed when the module reloads. If that doesn't resolve it, there may be other problems with your code as well that aren't shown in your current post.

require_relative and utility methods

I wanted to separate some methods into a module for abstraction purposes but I am getting a nomethod error when testing the first function in my module.
functions.rb
module Functions
def avg_ticket(vol,count)
(vol.to_f/count).round(2)
end
end
example.rb
require_relative 'functions'
vol = 5000
count = 2500
avg_ticket = Functions.avg_ticket(vol,count)
I am getting a undefined method 'avg_ticket' for functions:Module (NoMethodError)
both files are in the same folder so I am using require_relative if that makes a difference. this may be basic, but can anyone help me understand why the method is undefined here?
edit: changed module functions to module Functions in question
You named your module functions, but you're trying to call Functions. Names are case sensitive. Also, you need to name your module with an upper case first letter anyway. Also, to be defined on the module itself, you want to use def self.avg_ticket, see the following:
module Functions
def self.avg_ticket(vol,count)
(vol.to_f/count).round(2)
end
end
And using it:
p Functions.avg_ticket(2, 25)
> 0.08

Self value in Module within Module in Ruby

Why given a module like this:
module TestModule
module Configuration
# Return the configuration values set in this module
def options
puts "OPTIONS IS IN"
puts self.inspect
end
end
end
I get that the options method is in TestModule and not in Configuration?
EDIT: I add the gem that I am taking a look at, and the one that has given me this confusion:
Check this file: configuration.rb in line 37 the method options is defined.
In the class Client however, when options is called (line 11) is used doing Awesome instead of Configuration. Why is that? I don't see any class named Awesome where those modules are being mixed.
The gem in question does (in awesome.rb)
module Awesome
extend Configuration
end
So all of the methods on Awesome::Configuration (such as options) become singleton methods on the Awesome module
Technically, it's not part of either. It would have to be mixed in to a class to make it part of a class. To make it callable directly on the module, you need to prefix it with self. in the definition to make it a module method instead of an instance method:
def self.options
self #=> TestModule::Configuration
end
Here's a good tutorial on mixins which allows you to use the instance methods: http://rubylearning.com/satishtalim/modules_mixins.html

Ruby exception inheritance with dynamically generated classes

I'm new to Ruby, so I'm having some trouble understanding this weird exception problem I'm having. I'm using the ruby-aaws gem to access Amazon ECS: http://www.caliban.org/ruby/ruby-aws/. This defines a class Amazon::AWS:Error:
module Amazon
module AWS
# All dynamically generated exceptions occur within this namespace.
#
module Error
# An exception generator class.
#
class AWSError
attr_reader :exception
def initialize(xml)
err_class = xml.elements['Code'].text.sub( /^AWS.*\./, '' )
err_msg = xml.elements['Message'].text
unless Amazon::AWS::Error.const_defined?( err_class )
Amazon::AWS::Error.const_set( err_class,
Class.new( StandardError ) )
end
ex_class = Amazon::AWS::Error.const_get( err_class )
#exception = ex_class.new( err_msg )
end
end
end
end
end
This means that if you get an errorcode like AWS.InvalidParameterValue, this will produce (in its exception variable) a new class Amazon::AWS::Error::InvalidParameterValue which is a subclass of StandardError.
Now here's where it gets weird. I have some code that looks like this:
begin
do_aws_stuff
rescue Amazon::AWS::Error => error
puts "Got an AWS error"
end
Now, if do_aws_stuff throws a NameError, my rescue block gets triggered. It seems that Amazon::AWS::Error isn't the superclass of the generated error - I guess since it's a module everything is a subclass of it? Certainly if I do:
irb(main):007:0> NameError.new.kind_of?(Amazon::AWS::Error)
=> true
It says true, which I find confusing, especially given this:
irb(main):009:0> NameError.new.kind_of?(Amazon::AWS)
=> false
What's going on, and how am I supposed to separate out AWS errors from other type of errors? Should I do something like:
begin
do_aws_stuff
rescue => error
if error.class.to_s =~ /^Amazon::AWS::Error/
puts "Got an AWS error"
else
raise error
end
end
That seems exceptionally janky. The errors thrown aren't class AWSError either - they're raised like this:
error = Amazon::AWS::Error::AWSError.new( xml )
raise error.exception
So the exceptions I'm looking to rescue from are the generated exception types that only inherit from StandardError.
To clarify, I have two questions:
Why is NameError, a Ruby built in exception, a kind_of?(Amazon::AWS::Error), which is a module?
Answer: I had said include Amazon::AWS::Error at the top of my file, thinking it was kind of like a Java import or C++ include. What this actually did was add everything defined in Amazon::AWS::Error (present and future) to the implicit Kernel class, which is an ancestor of every class. This means anything would pass kind_of?(Amazon::AWS::Error).
How can I best distinguish the dynamically-created exceptions in Amazon::AWS::Error from random other exceptions from elsewhere?
Ok, I'll try to help here :
First a module is not a class, it allows you to mix behaviour in a class. second see the following example :
module A
module B
module Error
def foobar
puts "foo"
end
end
end
end
class StandardError
include A::B::Error
end
StandardError.new.kind_of?(A::B::Error)
StandardError.new.kind_of?(A::B)
StandardError.included_modules #=> [A::B::Error,Kernel]
kind_of? tells you that yes, Error does possess All of A::B::Error behaviour (which is normal since it includes A::B::Error) however it does not include all the behaviour from A::B and therefore is not of the A::B kind. (duck typing)
Now there is a very good chance that ruby-aws reopens one of the superclass of NameError and includes Amazon::AWS:Error in there. (monkey patching)
You can find out programatically where the module is included in the hierarchy with the following :
class Class
def has_module?(module_ref)
if self.included_modules.include?(module_ref) and not self.superclass.included_modules.include?(module_ref)
puts self.name+" has module "+ module_ref.name
else
self.superclass.nil? ? false : self.superclass.has_module?(module_ref)
end
end
end
StandardError.has_module?(A::B::Error)
NameError.has_module?(A::B::Error)
Regarding your second question I can't see anything better than
begin
#do AWS error prone stuff
rescue Exception => e
if Amazon::AWS::Error.constants.include?(e.class.name)
#awsError
else
whatever
end
end
(edit -- above code doesn't work as is : name includes module prefix which is not the case of the constants arrays. You should definitely contact the lib maintainer the AWSError class looks more like a factory class to me :/ )
I don't have ruby-aws here and the caliban site is blocked by the company's firewall so I can't test much further.
Regarding the include : that might be the thing doing the monkey patching on the StandardError hierarchy. I am not sure anymore but most likely doing it at the root of a file outside every context is including the module on Object or on the Object metaclass. (this is what would happen in IRB, where the default context is Object, not sure about in a file)
from the pickaxe on modules :
A couple of points about the include statement before we go on. First, it has nothing to do with files. C programmers use a preprocessor directive called #include to insert the contents of one file into another during compilation. The Ruby include statement simply makes a reference to a named module. If that module is in a separate file, you must use require to drag that file in before using include.
(edit -- I can't seem to be able to comment using this browser :/ yay for locked in platforms)
Well, from what I can tell:
Class.new( StandardError )
Is creating a new class with StandardError as the base class, so it is not going to be a Amazon::AWS::Error at all. It is just defined in that module, which is probably why it is a kind_of? Amazon::AWS::Error. It probably isn't a kind_of? Amazon::AWS because maybe modules don't nest for purposes of kind_of? ?
Sorry, I don't know modules very well in Ruby, but most definitely the base class is going to be StandardError.
UPDATE: By the way, from the ruby docs:
obj.kind_of?(class) => true or false
Returns true if class is the class of obj, or if class is one of the superclasses of obj or modules included in obj.
Just wanted to chime in: I would agree this is a bug in the lib code. It should probably read:
unless Amazon::AWS::Error.const_defined?( err_class )
kls = Class.new( StandardError )
Amazon::AWS::Error.const_set(err_class, kls)
kls.include Amazon::AWS::Error
end
One issue you're running into is that Amazon::AWS::Error::AWSError is not actually an exception. When raise is called, it looks to see if the first parameter responds to the exception method and will use the result of that instead. Anything that is a subclass of Exception will return itself when exception is called so you can do things like raise Exception.new("Something is wrong").
In this case, AWSError has exception set up as an attribute reader which it defines the value to on initialization to something like Amazon::AWS::Error::SOME_ERROR. This means that when you call raise Amazon::AWS::Error::AWSError.new(SOME_XML) Ruby ends up calling Amazon::AWS::Error::AWSError.new(SOME_XML).exception which will returns an instance of Amazon::AWS::Error::SOME_ERROR. As was pointed out by one of the other responders, this class is a direct subclass of StandardError instead of being a subclass of a common Amazon error. Until this is rectified, Jean's solution is probably your best bet.
I hope that helped explain more of what's actually going on behind the scenes.

Resources