referring to module level variables from within module - ruby

I'm having some difficulty with referring to module-level variables in ruby. Say I have a situation like this, where I'm referring to M.a internally:
module M
##a=1
def self.a
##a
end
class A
def x
M.a
end
end
end
Now, this example works fine for me but it is failing in a slightly more complicated context (where the module is spread over a number of files installed in a local gem - but my understanding is that that should not effect the way the code is executed) with an error like this: undefined method `a' for M::M (NoMethodError).
So, is this the correct way to refer to module level variables in context? is there a simpler/more idiomatic way?

If the module is spread out over other files, you need to ensure that your initialization is run before the method is called. If they are in the same file, this should be as much as guaranteed, but if you somehow split them there could be trouble.
I've found you can usually get away with this:
module M
def self.a
#a ||= 1
end
end
If this variable is subject to change, you will need a mutator method. Rails provides mattr_accessor that basically does what you want, part of ActiveSupport.

Related

RSpec test method is called on `main` object

Sometimes we call methods on the ruby main objects. For example we call create for FactoryBot and we call _() for I18n.
What's a proper way to test these top level methods got called in RSpec?
For example, I want to test N_ is called, but it would not work because the self in Rspec and self in the file are different.
# spec
describe 'unfound_translations' do
it 'includes dynamic translations' do
expect(self).to receive(:N_)
load '/path/to/unfound_translations.rb')
end
end
# unfound_translations.rb
N_('foo')
However this does not pass.
Ok, I get your problem now. Your main issue is that self in it block is different that self inside unfound_translations.rb. So you're setting expectations on one object and method N_ is called on something completely different.
(Edit: I just realized, when reading the subject of this question again, that you already was aware of it. Sorry for stating the obvious... leaving it so it may be useful to others)
I managed to have a hacky way that is working, here it is:
# missing_translations.rb
N_('foo')
and the spec (I defined a simple module for tests inside it for simplicity):
module N
def N_(what)
puts what
end
end
RSpec.describe 'foo' do
let(:klass) do
Class.new do
extend N
end
end
it do
expect(klass).to receive(:N_)
klass.class_eval do
eval(File.read('missing_translations.rb'))
end
end
end
What it does it's creating an anonymous class that. And evaluating contents of missing_translations.rb inside means that klass is the thing that receives N_ method. So you can set expectations there.
I'm pretty sure you can replace extend N module with whatever module is giving you N_ method and this should work.
It's hacky, but not much effort so maybe good enough until more elegant solution is provided.

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

Avoiding globals with dynamically loaded ruby files

I am in a position at the moment where I have a plugins folder where there there could be 1 or 100 plugins to be loaded.
Now the problem is, each plugin requires an instance of a class defined within the startup ruby file.
A really simplified example would be:
#startup.rb
def load_plugins
#... get each plugin file
require each_plugin
end
class MuchUsedClass
def do_something
#...
end
end
muchUsedInstance = MuchUsedClass.new
load_plugins
#some_plugin.rb
class SomePluginClass
def initialize(muchUsedInstance)
#muchUsedInstance = muchUsedInstance
end
def do_something_with_instance
#muchUsedInstance.do_something
end
end
somePluginInstance = SomePluginClass.new(muchUsedInstance)
somePluginInstance.do_something_with_instance
The main problem is that when you call require, it doesn't have any clue about what has happened before it is being required. So I find it nasty making a global variable within the startup file just to satisfy all other required files, but it seems like one of the only ways to be able to pass some data down to an included file, I could also make a singleton class to expose some of this, but that also seems a bit nasty.
As I am still new to ruby and am still looking through the statically typed glasses, I will probably be missing a decent pattern to solve this, in C# I would opt for dependency injection and just hook everything up that way...
Your example code does not have a global variable. Global variables have names that start with $. The code as you wrote it won't work, because muchUsedInstance is just a local variable and will not be shared between different Ruby files.
If you are not going to change the instance ever, you could easily store it as a constant:
MuchUsedInstance = MuchUsedClass.new
You could store it as a nested constant inside the class:
MuchUsedClass::Instance = MuchUsedClass.new
You could store it as an instance variable inside the class object, with a getter method that automatically creates it if it isn't there already:
def MuchUsedClass.instance
#instance ||= MuchUsedClass.new
end

Extending Enumerable in Rails 3

UPDATE TO QUESTION
Here is what I have done based on some research and findings.
STEP 1 - I have this module in my Rails 3 project and place it in my lib folder
# lib/enumerable.rb
module Enumerable
def sum
return self.inject(0){|acc,i|acc +i}
end
def average
return self.sum/self.length.to_f
end
def sample_variance
avg=self.average
sum=self.inject(0){|acc,i|acc +(i-avg)**2}
return(1/self.length.to_f*sum)
end
def standard_deviation
return Math.sqrt(self.sample_variance)
end
end
STEP 2 - According to this blog article, in Rails 3 your lib folder will not get loaded automatically. In order to load this module you need to go to your config / application.rb and type this in:
config.autoload_paths += %W(#{config.root}/lib)
STEP 3 - Then in your model my understanding is you type this in to get the module picked up.
class MyModel < ActiveRecord::Base
include Enumerable
end
STEP 4 - I then try restart the rails server and try this out and I get false when I would expect it to be true.
MyModel.respond_to?('sample_variance')
# false, when it should be true
What am I doing wrong? Should I not be getting true?
Your inclusion of the main Enumerable module (not your extension) undoubtedly worked, and you can test it by simply checking for any of the methods that were mixed in. The problem is, your 'Include Enumerable' may not have included your file, but rather the main module.
One suggestion is to rename the file name for your extension, and have it loaded through an initializer with a
require 'my_enumerable.rb'
That way you for sure get both Enumerable and your extension to Enumerable loaded.
If I understand what you're driving at, you're trying to use Enumerable's sum method in ActiveRecord. You can do that by converting the current object to an array, then calling Enumerable's sum method on that array.
One more thing: you don't need to use return like you are using it. Ruby will return the last calculated thing from your method. You don't need to use self like that either -- in Ruby, self is the current object.
So if you have a method:
def charlie
inject{|i, j| i + j + 1}
end
and you call it like this:
(1..2).charlie
self is the current object (1..2).
The output will be 4, with no self or return.
I highly recommend Dave Thomas' lecture on Ruby metaprogramming, I tried to find it, but I could not, it's out there on the web somewhere.
You might want to take a look at this:
http://www.ruby-doc.org/docs/ProgrammingRuby/html/tut_modules.html
You can include a module in a class, and thereby make that module's methods available to that class.
If you include Enumerable into a Rails model, then its methods would be available to that model. But since Enumerable's methods are already available to certain types of objects inside your Rails project, and those objects are available to be instantiated from inside your model, I don't see why you might do that, because Enumerable's methods are working just fine for the purposes they were designed.
Anyway, you might find that one of the following might work for you:
-- use Activerecord's sum method
-- convert your object to an array, and use Enumerable's sum method
-- write your own method, but don't call it sum, because you don't want to confuse yourself.
Try commenting out the second occurrence of module Neuone in the following snippet, and see what happens. Then try commenting out the Charlie.one method, and see what happens.
module Neuone
def one
'neuone one'
end
def two
'neuone two'
end
end
module Neuone
def two
'neuone two two'
end
end
class Charlie
include Neuone
def one
'charlie one'
end
end
c = Charlie.new
p c.one
p c.two

Test modules with Test::Unit

I encountered a problem when trying to test a module with Test::Unit. What I used to do is this:
my_module.rb:
class MyModule
def my_func
5 # return some value
end
end
test_my_module.rb:
require 'test/unit'
require 'my_module'
class TestMyModule < Unit::Test::TestCase
include MyModule
def test_my_func
assert_equal(5, my_func) # test the output value given the input params
end
end
Now the problem is, if my_module declares an initialize method, it gets included in the test class and this causes a bunch of problems since Test::Unit seems to override/generate an initialize method. So I'm wondering what is the best way to test a module?
I'm also wondering wether my module should become a class at this point since the initialize method is made for initializing the state of something. Opinions?
Thanks in advance !
Including an initialize method in a module feels very wrong to me, so I'd rethink that at the very least.
To answer your question about testing this as a module more directly, though, I would create a new, empty class, include your module in it, create an instance of that class, and then test against that instance:
class TestClass
include MyModule
end
class TestMyModule < Unit::Test::TestCase
def setup
#instance = TestClass.new
end
def test_my_func
assert_equal(5, #instance.my_func) # test the output value given the input params
end
end
Yeah, your initialize should definitely suggest that you're going towards a class. A module in ruby often feels like an interface in other languages, as long as you implement some basic things when you include the module you'll get a lot for free.
Enumerable is a great example, as long as you define [] and each when you include Enumerable you suddenly get pop, push, etc.
So my gut feeling about testing modules is that you should probably be testing classes that include the module rather than testing the module itself unless the module is designed to not be included in anything, it's simply a code storage mechanism.

Resources