I am new to Ruby and I got a question: is the nil checking necessary in following code? would you please explain a little bit? Thank you in advance!!if you think this is too easy to answer, would you please tell me the document( or link) that I need check to solve my doubts?
this is the original question:
within the say_hi method, the author checks if the instance variable #names is nil. Why is this check done? Is the check really needed in the MegaGreeter class as it is written? Why or Why not?
class MegaGreeter
attr_accessor :names
# Create the object
def initialize(names = "World")
#names = names
end
# Say hi to everybody
def say_hi
if #names.nil?
puts "..."
elsif #names.respond_to?("each")
# #names is a list of some kind, iterate!
#names.each do |name|
puts "Hello #{name}!"
end
else
puts "Hello #{#names}!"
end
end
# Say bye to everybody
def say_bye
if #names.nil?
puts "..."
elsif #names.respond_to?("join")
# Join the list elements with commas
puts "Goodbye #{#names.join(", ")}. Come back soon!"
else
puts "Goodbye #{#names}. Come back soon!"
end
end
end
if __FILE__ == $0
mg = MegaGreeter.new
mg.say_hi
mg.say_bye
# Change name to be "Zeke"
mg.names = "Zeke"
mg.say_hi
mg.say_bye
# Change the name to an array of names
mg.names = ["Albert", "Brenda", "Charles",
"Dave", "Engelbert"]
mg.say_hi
mg.say_bye
# Change to nil
mg.names = nil
mg.say_hi
mg.say_bye
end
The way the initializer is set up, #names should never be nil because of the default value of 'World' being assigned if nothing is provided.
The problem is the public attr_accessor in the MegaGreeter class, which you can read about this method here. It creates a setter method on the #names instance variable, meaning it can be changed to anything, including nil.
Normally, we can do this with Proc objects:
[15] pry(main)> pr = -> { puts "test message" }
=> #<Proc:0x000000057b8a90#(pry):12 (lambda)>
[16] pry(main)> pr.call
test message
=> nil
[17] pry(main)> define_method("test_method", pr)
=> :test_method
[18] pry(main)> test_method
test message
But, what if I have a raw code string and want to load it into a proc? Fake code below:
raw_code = "puts 'test message'"
pr = -> { load(raw_code) } # how to define the `load` method to get this working?
pr.call # => test message
define_method("test_method", pr}
test_method # => test message
Actually, my original problem is how to write a method hook like this:
class TestClass
def test_method
puts url
end
def test_method_a
puts url
end
before :test_method, :test_method_a do
url = __method__.split("_").join("/")
end
end
TestClass.new.test_method # => "test/method"
TestClass.new.test_method_a # => "test/method/a"
My problem is more complicated, this is just a simple example to illustrate the key problem.
how to define the load method to get this working?
By spelling it eval:
raw_code = "puts 'test message'"
pr = -> { eval(raw_code) } # how to define the `load` method to get this working?
pr.call # => test message
define_method("test_method", pr)
test_method # => test message
--output:--
test message
test message
Actually, my original problem is how to write a method hook...
class TestClass
def initialize
#url = %q{__method__.to_s.split("_").join("/")}
end
def test_method
puts(eval #url)
end
def test_method_a
puts(eval #url)
end
end
TestClass.new.test_method # => "test/method"
TestClass.new.test_method_a # => "test/method/a"
--output:--
test/method
test/method/a
Actually, my original problem is how to write a method hook like this:
Module TestClass
def test_method
puts url
end
The problem is that url can never refer to a value outside the def. A def cuts off the visibility of local variables outside the def.
class TestClass
def test_method
puts #url
end
def test_method_a
puts #url
end
def self.before(*method_names, &block)
method_names.each do |method_name|
alias_method "first_#{method_name}", method_name
define_method(method_name, block) #This changes __method__ inside the block from nil to method_name
alias_method "second_#{method_name}", method_name
define_method(method_name) do
send "second_#{method_name}" #This method executes: #url = __method__.to_s.split(...
send "first_#{method_name}" #This method executes: puts #url
end
end
end
before :test_method, :test_method_a do
#url = __method__.to_s.split("_").join("/")
end
end
TestClass.new.test_method # => "test/method"
TestClass.new.test_method_a # => "test/method/a"
--output:--
test/method
test/method/a
Short version: load loads code from a file. If you want to run code that you already have in a string, you can use eval, or one of it's friends, class_eval or instance_eval.
If you do end up using eval, however, you need to be very careful so that you won't accidentally run code that could delete your files, install malware or whatever.
Longer version: For a working version (in ruby 2.2.3) with load, you would need to put your TestClass class in a file:
class TestClass
def test_method
puts "OHAI"
end
end
Let's save this class in a file called "test_class.rb".
With that, the following should just work:
pr = -> { load(File.join(__dir__, "test_class.rb")) }
pr.call
TestClass.new.test_method
This will not solve your "original problem", but it should give you a little better understanding on how load works.
My own solution and more detail shown here after reading reply:
Maybe there is a lot of confusion about why does my problem method hook matter with load raw source code to Proc instance.
And also, I've solved my problem,now.
So,let me explain the whole thing in detail:
1, my original problem comes from that I need to extract a bunch of duplicated code at head of some methods of a module just like this:
module TestClass
extend self
def test_method
url = __method__.to_s.split("_").join("/") # duplicated code,need to extract
puts url
end
def test_method_a
url = __method__.to_s.split("_").join("/") # duplicated code, need to extract
puts url
end
2, Then, after a lot of thinking, I come up with an idea, that's get test_method test_method_a's source code, and modify the source code by adding url = __method__.to_s.split("_").join("/") at the head of it, then redefine the method with new code.
3, After lot of hack, I failed with using eval and then, post here asking help.
4, After reading reply, I make sure eval is definitely what I want.
5, Finally, I succeed, code show here:
module TestClass
extend self
def test_method
puts url
end
def test_method_a
puts url
end
[ :test_method, :test_method_a ].each do |name|
method_source_code_ar = instance_method(name).source.split("\n")
method_source_code_ar.insert(1, 'url = __method__.to_s.split("_").join("/")')
method_source_code = method_source_code_ar[1..-2].join("\n")
define_method(name, -> { eval(method_source_code) })
end
end
TestClass.test_method # => test/method
TestClass.test_method_a # => test/method/a
6, More concise code version show:
module TestClass
extend self
def test_method
puts url
end
def test_method_a
puts url
end
[ :test_method, :test_method_a ].each do |name|
method_source_code_ar = instance_method(name).source.split("\n")
method_source_code_ar.insert(1, 'url = __method__.to_s.split("_").join("/")')
method_source_code = method_source_code_ar.join("\n")
eval(method_source_code)
end
end
7, As eval,for me, I think the key to understand it is,thinking not as if you're in writing mode, but as if code was running line by line until reaching the eval line code, by that time, what do you want the code to do? Sure, just eval the method_source_code string.
I've read my code up and down for about 30 mins now. I can't for the life of me see where user_response is undefined. I'm very new to coding so I don't know how much of the code would be appropriate to paste in here. I figure that launch and get_action are essential but the rest couldn't hurt?
error => rb:32:in `launch!': undefined local variable or method `user_response' for
<Guide:0x007fb019984718> (NameError)
class Guide
class Config
##actions = ['list', 'find', 'add', 'quit']
def self.actions
##actions
end
end
def initialize(path=nil)
# locate the restaurant text file at path
Restaurant.filepath = path
if Restaurant.file_usable?
puts "Found restaurant file."
# or IF a new text file can't be created, create a new file
elsif Restaurant.create_file
puts "Created restaurant file."
# exit if create fails
else
puts "Exiting"
exit!
end
end
def launch! #! means that it is a strong powerful method!
introduction
# action loop
result = nil
until result == :quit
action = get_action
result = do_action(user_response)
end
conclusion
end
def get_action
action = nil
# Keep asking for user input until we get a valid action
until Guide::Config.actions.include?(action)
puts "Actions: " + Guide::Config.actions.join(", ") if action
print "> "
user_response = gets.chomp
action = user_response.downcase.strip
end
return action
end
def do_action(action)
case action
when 'list'
puts "Listing..."
when 'find'
puts "Finding..."
when 'add'
puts "Adding..."
when 'quit'
return :quit
else puts " I don't understand that command."
end
end
def introduction
puts "<<< Welcome to the Food Finder >>>"
puts "This is an interactive guide to help you find the food you crave."
end
def conclusion
puts "<<< Goodbye and Bon Appetit! >>>>"
end
end
I think you want to do this :
def launch! #! means that it is a strong powerful method!
introduction
# action loop
result = nil
until result == :quit
result = do_action(get_action)
end
conclusion
end
The only time you define a variable called user_response is in your get_action method.
The way you define it there makes it a local variable and it will not be accessible from anywhere but inside the get_action method.
In rspec 1 I could do
describe "Something", :shared => true do
include SomeModule # which has the :a_method method
def a_method(options)
super(options.merge(:option => #attr)
end
it "foofoofoo" do
end
end
describe "Something else" do
before(:each) do
#attr = :s_else
end
it_should_behave_like "Something"
it "barbarbar" do
a_method(:name => "something else")
Something.find("something else").name.should == "Something else"
end
...
end
That is, I could use :shared => true to not only refactor examples but also share method definitions and attributes. I realize the example is contrived, but how would one write it in rspec >= 2 without touching the SomeModule module or the Something class?
You can do this with shared_examples_for
shared_examples_for "something" do
include SomeModule # which has the :a_method method
def a_method(options)
super(options.merge(:option => #attr))
end
it "foofoofoo" do
end
end
And call with it_behaves_like:
it_behaves_like "something"
EDIT
Joao correctly points out that this fails to include SomeModule for the examples in the describe block. The include would have to take place outside the shared example group, e.g. at the top of the spec file
include SomeModule # which has the :a_method method
# ...
shared_examples_for "something" do
def a_method(options)
super(options.merge(:option => #attr))
end
it "foofoofoo" do
end
end
David Chelimsky discusses some new features of shared examples in RSpec 2 which may be pertinent in this blog post.
I have a class that I want to compare to both strings and symbols in a case statement, so I thought that I just override the ===() method for my class and all would be gold. However my ===() method never gets called during the case statement. Any ideas?
Here is some example code, and what happens in a irb session:
class A
def initialize(x)
#x=x #note this isn't even required for this example
end
def ===(other)
puts "in ==="
return true
end
end
irb(main):010:0> a=A.new("hi")
=> #
irb(main):011:0> case a
irb(main):012:1> when "hi" then 1
irb(main):013:1> else 2
irb(main):014:1> end
=> 2
(it never prints the message and should always return true anyway)
Note that ideally I'd like to do a
def ===(other)
#puts "in ==="
return #x.===(other)
end
Thanks in advance.
The expression after the 'case' keyword is the right hand side of the === expression, and the expression after the 'when' keyword is on the left hand side of the expression. So, the method that is being called is String.===, not A.===.
A quick approach to reversing the comparison:
class Revcomp
def initialize(obj)
#obj = obj
end
def ===(other)
other === #obj
end
def self.rev(obj)
Revcomp.new(obj)
end
end
class Test
def ===(other)
puts "here"
end
end
t = Test.new
case t
when Revcomp.rev("abc")
puts "there"
else
puts "somewhere"
end