I have two files in folder pc under my home directory.
First file:
class A
class << self
protected
def foo
puts "In foo"
end
end
end
Second file:
class B < A
def bar
self.class.class_eval { foo }
end
end
B.new.bar
My problem is when I run the second file I get the following error:
second.rb:1:in `<main>': uninitialized constant A (NameError)
Why is that?
B.new.bar
# => In foo
just works fine in my console. I guess you probably forgot to require the file containing A from the file B.
In file B use
require 'a'
(assuming the file containing A is called a.rb).
I read the various comments, and just to avoid confusion, here's the full content of the two files.
class_a.rb
class A
class << self
protected
def foo
puts "In foo"
end
end
end
class_b.rb
require_relative 'class_a'
class B < A
def bar
self.class.class_eval { foo }
end
end
puts B.new.bar
And here's how to execute them from the console
$ ruby class_b.rb
In foo
Of course, you should execute the file class_b.rb, not class_a.rb or you will not see any result.
Try require_relative 'class_a' or require class_a.
Note that the file's extension is not included.
This should work, assuming it's all in the same file. If not, you'll need to require the first file into the next:
# class_b.rb
require 'class_a.rb'
class B < A
def bar
self.class.class_eval { foo }
end
end
B.new.bar
#=> "In foo"
UPDATE:
In order to require the file, you may need to cite the path of the file relative to your current directory. For instance, if class_a.rb is located in ~/home and you're running irb (or class_b.rb is in ~/home), then you'd include class_a.rb by citing its relative path as follows:
require './class_a'
Related
I have a ruby file airplane.rb
with a ruby class like so -
class AirplaneSeat
attr_accessor :seat_row, :seat_column, :type, :order, :assigned_passenger
def initialize(seat_row, seat_column, type, order, assigned_passenger = 0)
#seat_row = seat_row
#seat_column = seat_column
#type = type
#order = order
#assigned_passenger = assigned_passenger
end
def get_passenger_seating
#some code
end
end # end of class
# outside the class
begin
puts "Enter the seating matrix as 2D array"
seat_matrix = JSON.parse gets.chomp
puts "Enter the number of passengers"
no_of_passengers = gets.chomp
raise "Please enter a valid passenger count" if (no_of_passengers.empty? || no_of_passengers.to_i <=0)
AirplaneSeat.get_passenger_seating(seat_matrix, no_of_passengers)
rescue Exception => e
puts "Error encountered -> #{e.message}"
end
So the ruby class has a few methods and couple of lines of code to execute outside the class, which takes input from the user and then calls the class method.
How do I go about writing test cases for this? I have the rspecs gem and spec folder setup done.
I don't really understand how to begin with the test cases.
Any pointers greatly appreciated.
Thanks!
As a simple example say we have our file for a Foo class, foo.rb:
class Foo
def call
'bar'
end
end
We can create a spec, foo_spec.rb:
require 'rspec'
require_relative 'foo'
RSpec.describe Foo do
describe '#call' do
it 'works' do
expect(described_class.new.call).to eq 'Bar'
end
end
end
And then from the command line we can run the spec:
$ rspec foo_spec.rb
I have two files in two different directories:
module MyModule
def my_method path
p File.join (File.dirname __FILE__), path
end
end
and
require_relative '../modules/mymodule' # definition of MyModule
class MyClass
extend MyModule
my_method 'my_file.yml'
end
I am getting output like my_home_dir/modules/my_file.yml but I want it to be my_home_dir/files/my_file.yml where files is the name of the directory where MyClass is defined.
I know I can use full path when I call my_method but is there a way for imported files to still have __FILE__ set to the name of the importing file?
Basically in my_method I need to have the full path of the file and I want to pass just a path relative to my calling file's path.
__FILE__ always is the name of the file containing the __FILE__ variable, so saying my_method will always return where my_method is defined, not where MyClass calls it.
You can probably get at the information you want using caller:
module MyModule
def my_method path
p caller
end
end
include MyModule # definition of MyModule
class MyClass
extend MyModule
my_method 'my_file.yml'
end
my_class = MyClass.new
Which outputs:
["test.rb:10:in `<class:MyClass>'", "test.rb:8:in `<main>'"]
Edit:
the caller array has only file names without paths...
Well, I'd hoped you'd know how to work around that but....
This is in test.rb:
require './test2'
class MyClass
extend MyModule
my_method __FILE__, 'my_file.yml'
end
my_class = MyClass.new
This is in test2.rb:
module MyModule
def my_method path, file
dir = File.dirname(path)
p caller.map{ |c| File.join(dir, c) }
end
end
Running test.rb outputs:
["./test.rb:4:in `<class:MyClass>'", "./test.rb:2:in `<main>'"]
There's a simpler method available if you can pass in a block; use the block's binding:
# In example.rb:
module Example
def execute_if_main(&block)
if $PROGRAM_NAME == eval("__FILE__", block.binding)
yield
end
end
end
Then in the test file:
# in test.rb:
require_relative 'example.rb'
include Example
execute_if_main do
puts "hi, I'm being loaded"
end
This will execute the block only if test.rb is being loaded directly by the Ruby interpreter.
If test.rb is loaded instead via some other file via require, the block at the end won't
be executed (which is the idea)
Ruby 2.7 adds Module#const_source_location
const_source_location(:MyMoudule)
I have two modules with the same method name. When I include both modules in some class, only the method of the last module is executed. I need instead both to be executed when I initialize the class:
class MyClass
include FirstModule
include SecondModule
def initialize
foo # foo is contained in both modules but only the one in SecondModules is executed
end
end
Is it doable?
As Yusuke Endoh might say, everything is doable in Ruby. In this case, you have to forget about convenience of just saying 'foo', and you have to be very explicit about what you actually want to do, like this:
class MyClass
include FirstModule
include SecondModule
def initialize
FirstModule.instance_method( :foo ).bind( self ).call
SecondModule.instance_method( :foo ).bind( self ).call
end
end
The line 'FirstModule.instance_method...' can be replaced by simply saying 'foo', but by being explicit, you ensure that no matter what, you are calling the method from that mixin, from which you think you do.
Can you modify the included modules? Perhaps you just call super in the second module?
module M1
def foo
p :M1
end
end
module M2
def foo
p :M2
defined?(super) && super
end
end
class SC
include M1
include M2
def initialize
foo
end
end
SC.new
Or perhaps you actually want to do this?
module M1
def bar; p :M1 end
end
module M2
include M1
def foo; bar; p :M2 end
end
class SC
include M2
def initialize; foo end
end
See live demo here
Is there a callback which can be executed when a class is loaded. I am thinking something like this.
register_callback('Foo', :debug_message)
def debug_message
puts "Foo has been loaded"
end
require 'foo'
No, there is not. And there cannot be, for the simple reason that classes in Ruby are open: they are never fully "loaded", you can always add, remove, rename and overwrite methods at any later point in time.
For example, when is the following class "loaded"?
# foo.rb
class Foo
def some_method
end
end
# bar.rb
class Foo
def some_other_method
end
end
# baz.rb
class Foo
def some_method
end
end
require 'foo'
require 'bar'
require 'baz' if rand > 0.5
Is it possible to temporarily apply certain methods to a class for tests? I want to be able to run specs depending on many ways to apply it. While I could make a bunch of fixtures with different settings, I find it easier to just class_eval the model in the tests. For example:
describe "some context"
before do
Page.class_eval do
my_applying_method :some => :option
end
end
it "should..."
end
Then in another context block:
describe "another context without the gem applied"
before do
Page.class_eval do
# nothing here since I want to page to be as is
end
end
it "should do something else..."
end
But the problem with the last context block is that it has a modified class (modified in the context block above). Is it possible to reset a class after class_eval? How?
Thanks!
I hope there is a better way to do that but you can use this(and it's with a warning at the Foo = Foo_old line):
module Bar
def baz
end
end
class Foo
end
puts Foo.method_defined? :baz #=> false
Foo_old = Foo.dup # create a copy of our class
Foo.class_eval do
include Bar
end
puts Foo.method_defined? :baz #=> true
Foo = Foo_old
puts Foo.method_defined? :baz #=> false
You haven't clarified how you are modifying the class.
The remix library allows you to temporarily include a module and properly uninclude it later.
In general, it's probably safest to just duplicate the class and test the duplicate:
irb(main):001:0> class Foo; end
#=> nil
irb(main):002:0> Foo.dup.class_eval{ #x = 42 }
#=> 42
irb(main):003:0> Foo.class_eval{ #x }
#=> nil