Why does Ruby hang when I use method_missing on String to redirect to chars? [closed] - ruby

Closed. This question is not reproducible or was caused by typos. It is not currently accepting answers.
This question was caused by a typo or a problem that can no longer be reproduced. While similar questions may be on-topic here, this one was resolved in a way less likely to help future readers.
Closed 8 years ago.
Improve this question
When I implement this code:
class String
def method_missing(meth,*args, &block)
if self.chars.respond_to? meth
self.chars.send meth, *args, &block
else
super
end
end
def respond_to?(meth)
if self.chars.respond_to? meth
true
else
super
end
end
end
Ruby gets stuck on flatten. Even requiring another file internally calls flatten so it hangs there. I've even tried this:
class String
def method_missing(meth,*args, &block)
if meth.to_sym == :flatten
super
elsif self.chars.respond_to? meth
self.chars.send meth, *args, &block
else
super
end
end
def respond_to?(meth)
if meth.to_sym == :flatten
super
elsif self.chars.respond_to? meth
true
else
super
end
end
end
But the same results occur. What's happening internally to cause flatten to fail?
Here's the error output:
2.1.2 :003 > require 'mygem'
=> true
2.1.2 :004 > require 'pry'
^CIRB::Abort: abort then interrupt!
from /home/user/dev/MyGem/lib/mygem/string_method_missing.rb:17:in `call'
from /home/user/dev/MyGem/lib/mygem/string_method_missing.rb:17:in `respond_to?'
from /home/user/.rvm/rubies/ruby-2.1.2/lib/ruby/site_ruby/2.1.0/rubygems/requirement.rb:112:in `flatten'
from /home/user/.rvm/rubies/ruby-2.1.2/lib/ruby/site_ruby/2.1.0/rubygems/requirement.rb:112:in `initialize'
from /home/user/.rvm/rubies/ruby-2.1.2/lib/ruby/site_ruby/2.1.0/rubygems/requirement.rb:70:in `new'
from /home/user/.rvm/rubies/ruby-2.1.2/lib/ruby/site_ruby/2.1.0/rubygems/requirement.rb:70:in `default'
from /home/user/.rvm/rubies/ruby-2.1.2/lib/ruby/site_ruby/2.1.0/rubygems/dependency.rb:260:in `merge'
from /home/user/.rvm/rubies/ruby-2.1.2/lib/ruby/site_ruby/2.1.0/rubygems/specification.rb:1323:in `block in activate_dependencies'
from /home/user/.rvm/rubies/ruby-2.1.2/lib/ruby/site_ruby/2.1.0/rubygems/specification.rb:1306:in `each'
from /home/user/.rvm/rubies/ruby-2.1.2/lib/ruby/site_ruby/2.1.0/rubygems/specification.rb:1306:in `activate_dependencies'
from /home/user/.rvm/rubies/ruby-2.1.2/lib/ruby/site_ruby/2.1.0/rubygems/specification.rb:1288:in `activate'
from /home/user/.rvm/rubies/ruby-2.1.2/lib/ruby/site_ruby/2.1.0/rubygems.rb:194:in `try_activate'
from /home/user/.rvm/rubies/ruby-2.1.2/lib/ruby/site_ruby/2.1.0/rubygems/core_ext/kernel_require.rb:132:in `rescue in require'
from /home/user/.rvm/rubies/ruby-2.1.2/lib/ruby/site_ruby/2.1.0/rubygems/core_ext/kernel_require.rb:144:in `require'
from (irb):4
from /home/user/.rvm/rubies/ruby-2.1.2/bin/irb:11:in `<main>'
It happens strictly for any Array of strings which gets flattened.
2.1.2 :005 > [1,2,3].flatten
=> [1, 2, 3]
2.1.2 :006 > ["1","2","3"].flatten
^CIRB::Abort: abort then interrupt!
from /home/user/dev/MyGem/lib/mygem/string_method_missing.rb:17:in `call'
from /home/user/dev/MyGem/lib/mygem/string_method_missing.rb:17:in `respond_to?'
from (irb):6:in `flatten'
from (irb):6
from /home/user/.rvm/rubies/ruby-2.1.2/bin/irb:11:in `<main>'
UPDATE
I've updated the code for the fixes recommended in the comments. The problem is still happening. I'm looking forward to the answer.
Flatten seems to work if I tell it how deep to go. So that seems to indicate an infinite loop on flattening the array on the string object. Example:
2.1.2 :013 > ["1","2","3"].flatten(0)
=> ["1", "2", "3"]
2.1.2 :021 > ["1","2","3"].flatten(8)
=> ["1", "2", "3"]
2.1.2 :022 > ["1","2","3"].flatten(9)
=> ["1", "2", "3"]
How can I avoid the infinite loop and still do this method_missing? It would help to know what test flatten calls on the inner object so I could define that in the string and avoid the loop.

There isn't an inbuilt cap on flatten. So redirecting everything to chars in String through method_missing creates an Array like object that will cause the flatten method to infinitely loop.
Add this, it is a working solution:
class Array
# To fix a bug that our method_missing creates
# we need to set a MAXIMUM for FLATTEN
alias_method :_old_flatten, :flatten
alias_method :_old_flatten!, :flatten!
def flatten(level = 99)
_old_flatten(level)
end
def flatten!(level = 99)
_old_flatten!(level)
end
end

Related

Dependency Injection causing both Rspec failure and IRB failure

Note: I am a Ruby and programming novice.
I have a class called JourneyLog I am trying to get a method called start to instantiate a new instance of another class, called Journey
class JourneyLog
attr_reader :journey_class
def initialize(journey_class: Journey)
#journey_class = journey_class
#journeys = []
end
def start(station)
journey_class.new(entry_station: station)
end
end
When I go into irbi get the following issue
2.2.3 :001 > require './lib/journeylog'
=> true
2.2.3 :002 > journeylog = JourneyLog.new
NameError: uninitialized constant JourneyLog::Journey
from /Users/BartJudge/Desktop/Makers_2018/oystercard-challenge/lib/journeylog.rb:4:in `initialize'
from (irb):2:in `new'
from (irb):2
from /Users/BartJudge/.rvm/rubies/ruby-2.2.3/bin/irb:15:in `<main>'
2.2.3 :003 >
I also have the following Rspec test
require 'journeylog'
describe JourneyLog do
let(:journey) { double :journey, entry_station: nil, complete?: false, fare: 1}
let(:station) { double :station }
let(:journey_class) { double :journey_class, new: journey }
describe '#start' do
it 'starts a journey' do
expect(journey_class).to receive(:new).with(entry_station: station)
subject.start(station)
end
end
end
I get the following Rspec failure;
1) JourneyLog#start starts a journey
Failure/Error: expect(journey_class).to receive(:new).with(entry_station: station)
(Double :journey_class).new({:entry_station=>#<Double :station>})
expected: 1 time with arguments: ({:entry_station=>#<Double :station>})
received: 0 times
# ./spec/jorneylog_spec.rb:9:in `block (3 levels) in <top (required)>'
I am at a total loss on what the problem is, or where to look for some answers.
I'm assuming I'm not injecting the Journey class properly, but thats as far as I can get myself.
Could someone provide some assistance?
In the journeylog.rb file you need to load the Journey class:
require 'journey' # I guess the Journey class is defined in lib/journey.rb
In the spec file you need to pass journey_class to the JourneyLog constructor:
describe JourneyLog do
subject { described_class.new(journey_class: journey_class) }
# ...

Ruby Script: undefined method `symbolize_keys' error loading YAML files

I have a ruby script for yaml merging as follows
#!/usr/bin/env ruby
require 'yaml'
raise "wrong number of parameters" unless ARGV.length == 2
y1 = YAML.load_file(ARGV[0]).symbolize_keys
y2 = YAML.load_file(ARGV[1]).symbolize_keys
puts y1.merge!(y2).to_yaml
when I execute it:
./test.rb ./src/api/config/config1.yml ./src/api/config/config2.yml
I've got the following error:
./test.rb:5:in `<main>': undefined method `symbolize_keys' for {"root"=>{"cloud.n2"=>{"accessKey"=>"I5VAJUYNR4AAKIZDH777"}}}:Hash (NoMethodError)
Hash#symbolize_keys method comes from activesupport gem (activesupport/lib/active_support/core_ext/hash/keys.rb).
In order to use it, you need to add the following line to your script:
require "active_support"
While the other answers/comments are correct it seems like overkill to require all of ActiveSupport for this. Instead either use:
require 'active_support/core_ext/hash/keys'
Or if you have control over the yml files then just make the keys symbols there and avoid any transformation. For Example
require 'yaml'
yml = <<YML
:root:
:cloud.n2:
:accessKey: "I5VAJUYNR4AAKIZDH777"
YML
YAML.load(yml)
#=> {:root=>{:"cloud.n2"=>{:accessKey=>"I5VAJUYNR4AAKIZDH777"}}}
This does not really the answer your question, but Ruby 2.5.0 introduced Hash#transform_keys (release notes) which also can be used to symbolize keys and is in core Ruby.
{'a' => 1, 'b' => 2}.transform_keys(&:to_sym)
#=> {:a=>1, :b=>2}
There is also a bang version which mutates the hash instead of creating a new one.
As other have already noted, symbolize_keys is an ActiveSupport method. If you are not using ActiveSupport, and/or on a pre-2.5 version of Ruby that does not include transform_keys, you could define it yourself.
class Hash
def transform_keys
return enum_for(:transform_keys) unless block_given?
result = self.class.new
each_key do |key|
result[yield(key)] = self[key]
end
result
end
def transform_keys!
return enum_for(:transform_keys!) unless block_given?
keys.each do |key|
self[yield(key)] = delete(key)
end
self
end
def symbolize_keys
transform_keys{ |key| key.to_sym rescue key }
end
def symbolize_keys!
transform_keys!{ |key| key.to_sym rescue key }
end
end
This is not to say that there are not likely other dependencies on Rails or ActiveSupport that will be required for your script.

undefined method 'execute' for nil:NilClass

I am making a tool in ruby which can interact with databases.
I am using amalgalite as an adapter for sqlite3.
Code:
require 'amalgalite'
# this is class RQuery
class RQuery
def db_open(db_name)
#db = Amalgalite::Database.new "#{db_name}.db"
make_class
end
def exec_this(query)
#db.execute(query)
end
def make_class
tables_list = exec_this("select name from sqlite_master where type='table'")
tables_list.each do |table|
#class_created = Object.const_set(table[0].capitalize, Class.new)
#class_created.class_eval do
define_singleton_method :first do
RQuery.new.exec_this("select * from #{table[0]} order by #{table[0]}.id ASC limit 1")
end
end
end
end
def eval_this(input)
instance_eval(input)
end
def code
print '>>'
input = gets
exit if input =~ /^q$/
puts eval_this(input)
code
end
end
Now when I am running the code everything works fine until I call table_name.first
It gives output
vbhv#fsociety ~/git/R-Query/bin $ ruby main.rb
Enter the code or q for quit
>>db_open('vbhv')
users
persons
people
programmers
>>Users.first
/home/vbhv/git/R-Query/lib/r-query.rb:36:in `instance_eval': undefined method `execute' for nil:NilClass (NoMethodError)
Did you mean? exec
from /home/vbhv/git/R-Query/lib/r-query.rb:29:in `block (3 levels) in make_class'
from (eval):1:in `eval_this'
from /home/vbhv/git/R-Query/lib/r-query.rb:36:in `instance_eval'
from /home/vbhv/git/R-Query/lib/r-query.rb:36:in `eval_this'
from /home/vbhv/git/R-Query/lib/r-query.rb:43:in `code'
from /home/vbhv/git/R-Query/lib/r-query.rb:44:in `code'
from /home/vbhv/git/R-Query/lib/r-query.rb:44:in `code'
from main.rb:4:in `<main>'
Now the 'execute' function it is talking about is inside amalgalite. What am I doing wrong here?? Thanks in Advance!
The problem in this was that the new class formed dynamically doesn't know about the connection variable '#db'. Hence the code solves the problem.
#class_created.instance_variable_set(:#database, #db)
A big thanks to Jagdeep Singh.

Confusion with IRB output in Ruby when object#initialize is overloaded

What I actually trying to see when no 'initialize' method is given to an class definition then the class as you said should call the "Object#initialize",which here I tried to customize and see if it has been called or not. With that approach I reached to a conclusion(although that's wrong), when I typed "ob = A .new" that yes I can overload the Object#initialize method.But all has been ended up with the below exception. Then I thought I did something wrong in my customization.So I tried to create the object creation within an exception block and when I typed "begin" and pressed "ENTER" - i got the same error.
>> class A
>> def Object.new initialize
>> p "hi"
>> rescue
>> end
>> end
=> nil
>> begin # <~~~ Here I have pressed on ENTER
"hi" #<~~~~ How was it print out?
/usr/lib/ruby/1.9.1/irb/ruby-token.rb:94:in `Token': undefined method `set_backtrace' for "hi":String (NoMethodError)
from /usr/lib/ruby/1.9.1/irb/ruby-lex.rb:348:in `block in lex_init'
from /usr/lib/ruby/1.9.1/irb/slex.rb:236:in `call'
from /usr/lib/ruby/1.9.1/irb/slex.rb:236:in `match_io'
from /usr/lib/ruby/1.9.1/irb/slex.rb:221:in `match_io'
from /usr/lib/ruby/1.9.1/irb/slex.rb:75:in `match'
from /usr/lib/ruby/1.9.1/irb/ruby-lex.rb:286:in `token'
from /usr/lib/ruby/1.9.1/irb/ruby-lex.rb:262:in `lex'
from /usr/lib/ruby/1.9.1/irb/ruby-lex.rb:233:in `block (2 levels) in each_top_level_statement'
from /usr/lib/ruby/1.9.1/irb/ruby-lex.rb:229:in `loop'
from /usr/lib/ruby/1.9.1/irb/ruby-lex.rb:229:in `block in each_top_level_statement'
from /usr/lib/ruby/1.9.1/irb/ruby-lex.rb:228:in `catch'
from /usr/lib/ruby/1.9.1/irb/ruby-lex.rb:228:in `each_top_level_statement'
from /usr/lib/ruby/1.9.1/irb.rb:155:in `eval_input'
from /usr/lib/ruby/1.9.1/irb.rb:70:in `block in start'
from /usr/lib/ruby/1.9.1/irb.rb:69:in `catch'
from /usr/lib/ruby/1.9.1/irb.rb:69:in `start'
from /usr/bin/irb:12:in `<main>'
#ubuntu:~$
Now my questions are -
How has the "hi" been printed?
What is the cause of the error as printed above?
If such initialize definition is not allowed,then why has the error not come after I ended with the class definition?
EDIT
As per #casper I tried below:
>> def Object.new
>> p "hi"
>> end
=> nil
>> begin
/usr/lib/ruby/1.9.1/irb/ruby-token.rb:96: stack level too deep (SystemStackError)
But here no "hi" printed back.
So what made the "hi" to print back in the first case?
What exactly are you trying to do? You just redefined Object.new, so there is no surprise you make everything go haywire.
You can basically get the same effect by just:
>> def Object.new
>> end
>> [press enter]
KABOOM
The reason "hi" is printed is that someone just called Object.new, probably the irb REPL loop, and it expected an object, but instead it gets gobledygook.
You can also try this:
def Object.new *args
p args
end
And you will see funny stuff. However you won't be able to quit irb or do anything useful with it after that. Again: you just broke Object.
To make some sense of it you should read this:
In Ruby, what's the relationship between 'new' and 'initialize'? How to return nil while initializing?
And then you can try this:
class Object
class << self
alias :old_new :new
end
end
Now you can do:
def Object.new *args
p args
old_new *args
end
This won't break new because you are still calling the old version of it. However you will now be printing out stuff every time someone calls new.

TypeError: superclass mismatch for class Word in Ruby

I am creating a Word class and I am getting an error:
TypeError: superclass mismatch for class Word
Here is the irb code:
irb(main):016:0> class Word
irb(main):017:1> def palindrome?(string)
irb(main):018:2> string == string.reverse
irb(main):019:2> end
irb(main):020:1> end
=> nil
irb(main):021:0> w = Word.new
=> #<Word:0x4a8d970>
irb(main):022:0> w.palindrome?("foobar")
=> false
irb(main):023:0> w.palindrome?("level")
=> true
irb(main):024:0> class Word < String
irb(main):025:1> def palindrome?
irb(main):026:2> self == self.reverse
irb(main):027:2> end
irb(main):028:1> end
TypeError: superclass mismatch for class Word
from (irb):24
from C:/Ruby193/lib/ruby/gems/1.9.1/gems/railties-3.2.1/lib/rails/commands/console.rb:47:in `start'
from C:/Ruby193/lib/ruby/gems/1.9.1/gems/railties-3.2.1/lib/rails/commands/console.rb:8:in `start'
from C:/Ruby193/lib/ruby/gems/1.9.1/gems/railties-3.2.1/lib/rails/commands.rb:41:in `<top (required)>'
from script/rails:6:in `require'
from script/rails:6:in `<main>'
A thumb rule for irb (either way irb or rails console)
If you are creating the same class twice with inheritance (superclass), exit the irb instance and create it again. Why this? Because otherwise class conflicts will happen.
In your case, you are using Windows (found from the question), so just type exit on DOS prompt and again type irb or rails console and create your Word class and it should work. Please let me know if it doesn't work for you.
The reason it gives you a superclass mismatch error is because you have already defined the Word class as inheriting from Object
class Word
...
end
In Ruby (like in most dynamic languages) you can monkey-patch classes by reopening the definition and modifying the class. However, in your instance, when you are reopening the class you are also attempting to redefine the class as inheriting from the super class String.
class Word < String
...
end
Once a class and it's inheritance structure have been defined, you cannot define it again.
As a few people have said, exiting and restarting irb will allow you to start from scratch in defining the Word class.
link664 has clearly explained the problem.
However, there's an easier fix without quitting irb (and losing all your other work).
You can delete an existing class definition this way.
irb(main):051:0> Object.send(:remove_const, :Word)
and you can verify with:
irb(main):052:0> Word.public_instance_methods
which should return:
NameError: uninitialized constant Word
from (irb):52
An easy way to bypass this issue is to encapsulate both classes between different modules:
> module M
> class Word
> def palindrome?(string)
> string == string.reverse
> end
> end
> end
=> nil
> w = M::Word.new
=> #<Word:0x4a8d970>
> w.palindrome?("foobar")
=> false
> w.palindrome?("level")
=> true
> module N
> class Word < String
> def palindrome?
> self == self.reverse
> end
> end
> end
> N::Word.new("kayak").palindrome?
=> true

Resources