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
Related
I have this program that basically uses a method called reverse_complement to reverse a string and replaces some characters with other characters; However, if I use the method twice for the same instance it gives me an undefined error. So, puts dna1.reverse_complement.reverse_complement == dna1.nucleotide should give me a true value. However, it gives an undefined method error.
class DNA
attr_reader :nucleotide
def initialize (nucleotide)
#nucleotide = nucleotide
end
def reverse_complement()
#nucleotide.reverse.tr("ATCG", "TAGC")
end
end
dna1 = DNA.new("ATTGCC")
puts dna1.reverse_complement
puts dna1.nucleotide
puts dna2 = dna1.reverse_complement
puts dna1.reverse_complement.reverse_complement == dna1.nucleotide
I think your method works but it's your chaining that's the problem:
puts dna1.reverse_complement.reverse_complement
you've defined reverse_complement to return a string, and you can't call String#reverse_compliment.
Instead, write this:
dna2 = DNA.new(dna1.reverse_compliment)
puts dna2.reverse_compliment == dna1.nucleotide
Why can't I use the same method twice for one instance in ruby?
Because you aren't using it for one instance. You are calling it on dna1, which is an instance of DNA, and then you call it again on the return value of reverse_complement, which is a completely different instance of class String.
Personally, I find it very confusing that a method called reverse_complement would return an object of a completely different type than the one it was called on. I would rather expect it to return a reversed and complementary instance of the same type, i.e. DNA:
class DNA
def initialize(nucleotide)
self.nucleotide = nucleotide.dup.freeze
end
def reverse_complement
self.class.new(#nucleotide.reverse.tr('ATCG', 'TAGC'))
end
def ==(other)
nucleotide == other.nucleotide
end
def to_s
"DNA: #{nucleotide}"
end
protected
attr_reader :nucleotide
private
attr_writer :nucleotide
end
dna1 = DNA.new('ATTGCC')
puts dna1.reverse_complement
# DNA: GGCAAT
puts dna2 = dna1.reverse_complement
# DNA: GGCAAT
puts dna1.reverse_complement.reverse_complement == dna1
# true
Would something like this suffice?:
class DNA < String
def reverse_complement
reverse.tr("ATCG", "TAGC")
end
end
dna1 = DNA.new("ATTGCC")
puts dna1
# ATTGCC
puts dna1.reverse_complement
# GGCAAT
puts dna1.reverse_complement.reverse_complement == dna1
# true
Notes
def reverse_complement
reverse.tr("ATCG", "TAGC")
end
can be written as:
def reverse_complement
self.reverse.tr("ATCG", "TAGC")
end
where self is the current object. In the examples above, self is dna1 which is an instance of DNA.
caveat: inheriting from core classes is a bad idea. This answer will be deleted if OP kindly unaccepts this answer. See link for more details about inheriting from core classes.
Within a method at runtime, is there a way to know if that method has been called via super in a subclass? E.g.
module SuperDetector
def via_super?
# what goes here?
end
end
class Foo
include SuperDetector
def bar
via_super? ? 'super!' : 'nothing special'
end
end
class Fu < Foo
def bar
super
end
end
Foo.new.bar # => "nothing special"
Fu.new.bar # => "super!"
How could I write via_super?, or, if necessary, via_super?(:bar)?
There is probably a better way, but the general idea is that Object#instance_of? is restricted only to the current class, rather than the hierarchy:
module SuperDetector
def self.included(clazz)
clazz.send(:define_method, :via_super?) do
!self.instance_of?(clazz)
end
end
end
class Foo
include SuperDetector
def bar
via_super? ? 'super!' : 'nothing special'
end
end
class Fu < Foo
def bar
super
end
end
Foo.new.bar # => "nothing special"
Fu.new.bar # => "super!"
However, note that this doesn't require explicit super in the child. If the child has no such method and the parent's one is used, via_super? will still return true. I don't think there is a way to catch only the super case other than inspecting the stack trace or the code itself.
An addendum to an excellent #ndn approach:
module SuperDetector
def self.included(clazz)
clazz.send(:define_method, :via_super?) do
self.ancestors[1..-1].include?(clazz) &&
caller.take(2).map { |m| m[/(?<=`).*?(?=')/] }.reduce(&:==)
# or, as by #ndn: caller_locations.take(2).map(&:label).reduce(&:==)
end unless clazz.instance_methods.include? :via_super?
end
end
class Foo
include SuperDetector
def bar
via_super? ? 'super!' : 'nothing special'
end
end
class Fu < Foo
def bar
super
end
end
puts Foo.new.bar # => "nothing special"
puts Fu.new.bar # => "super!"
Here we use Kernel#caller to make sure that the name of the method called matches the name in super class. This approach likely requires some additional tuning in case of not direct descendant (caller(2) should be changed to more sophisticated analysis,) but you probably get the point.
UPD thanks to #Stefan’s comment to the other answer, updated with unless defined to make it to work when both Foo and Fu include SuperDetector.
UPD2 using ancestors to check for super instead of straight comparison.
Here's a simpler (almost trivial) approach, but you have to pass both, current class and method name: (I've also changed the method name from via_super? to called_via?)
module CallDetector
def called_via?(klass, sym)
klass == method(sym).owner
end
end
Example usage:
class A
include CallDetector
def foo
called_via?(A, :foo) ? 'nothing special' : 'super!'
end
end
class B < A
def foo
super
end
end
class C < A
end
A.new.foo # => "nothing special"
B.new.foo # => "super!"
C.new.foo # => "nothing special"
Edit Improved, following Stefan's suggestion.
module SuperDetector
def via_super?
m0, m1 = caller_locations[0].base_label, caller_locations[1]&.base_label
m0 == m1 and
(method(m0).owner rescue nil) == (method(m1).owner rescue nil)
end
end
The ultimate mix between my other, #mudasobwa's and #sawa's answers plus recursion support:
module SuperDetector
def self.included(clazz)
unless clazz.instance_methods.include?(:via_super?)
clazz.send(:define_method, :via_super?) do
first_caller_location = caller_locations.first
calling_method = first_caller_location.base_label
same_origin = ->(other_location) do
first_caller_location.lineno == other_location.lineno and
first_caller_location.absolute_path == other_location.absolute_path
end
location_changed = false
same_name_stack = caller_locations.take_while do |location|
should_take = location.base_label == calling_method and !location_changed
location_changed = !same_origin.call(location)
should_take
end
self.kind_of?(clazz) and !same_origin.call(same_name_stack.last)
end
end
end
end
The only case that wont work (AFAIK) is if you have indirect recursion in the base class, but I don't have ideas how to handle it with anything short of parsing the code.
According to the documentation for modules and classes, calling super (without arguments or parentheses) calls the parent method with the same arguments:
When used without any arguments super uses the arguments given to the subclass method.
Assigning a new value to the "argument variable" seems to alter this behavior:
class MyClass
def foo(arg)
puts "MyClass#foo(#{arg.inspect})"
end
end
class MySubclass < MyClass
def foo(arg)
puts "MySubclass#foo(#{arg.inspect})"
super
arg = 'new value'
super
end
end
MySubclass.new.foo('inital value')
Output:
MySubclass#foo("inital value")
MyClass#foo("inital value")
MyClass#foo("new value") # <- not the argument given to MySubclass#foo
Is this expected?
Update
This seems to be the expected behavior for positional and keyword arguments, but it doesn't work for block arguments:
class MyClass
def foo(&block)
puts "MyClass#foo { #{block.call.inspect} }"
end
end
class MySubclass < MyClass
def foo(&block)
puts "MySubclass#foo { #{block.call.inspect} }"
super
block = Proc.new { 'new value' }
super
end
end
MySubclass.new.foo { 'initial value' }
Output:
MySubclass#foo { "initial value" }
MyClass#foo { "initial value" }
MyClass#foo { "initial value" }
Lets take one example from the Ruby core:
Keyword2
class Base
def single(a) a end
def double(a, b) [a,b] end
def array(*a) a end
def optional(a = 0) a end
def keyword(**a) a end
end
class Keyword2 < Base
def keyword(foo: "keyword2")
foo = "changed1"
x = super
foo = "changed2"
y = super
[x, y]
end
end
Now, see the test case :-
def test_keyword2
assert_equal([{foo: "changed1"}, {foo: "changed2"}], Keyword2.new.keyword)
end
Above example exactly mathes the keyword documentation.
Called with no arguments and no empty argument list, super calls the appropriate method with the same arguments, and the same code block, as those used to call the current method. Called with an argument list or arguments, it calls the appropriate methods with exactly the specified arguments (including none, in the case of an empty argument list indicated by empty parentheses).
same arguments means it is saying the current values of argument variables.test_super.rb files contains all the varieties of stuffs we can do with super in Ruby.
No, it work with block too (taken from core) :
a = Class.new do
def foo
yield
end
end
b = Class.new(a) do
def foo
super{
"b"
}
end
end
b.new.foo{"c"} # => "b"
But, have no idea why the below is giving "c"? This is actually the updated question of the OP:
c = Class.new do
def foo(&block)
block.call
end
end
d = Class.new(c) do
def foo(&block)
block = -> { "b" }
super
end
end
d.new.foo{"c"} # => "c"
It seems to be the expected behavior, based on the RubySpec anyway.
module RestArgsWithSuper
class A
def a(*args)
args
end
end
class B < A
def a(*args)
args << "foo"
super
end
end
end
(language/fixtures/super.rb).
It's then expected that the arguments are modified:
it "passes along modified rest args when they weren't originally empty" do
Super::RestArgsWithSuper::B.new.a("bar").should == ["bar", "foo"]
end
(language/super_spec.rb)
It's the expected behaviour. Technically, arg is the same argument, it just points to another value.
This answer might explain it better: https://stackoverflow.com/a/1872159/163640
I have a method that accepts a method as an argument:
def acceptor_method(received_method)
an_arry.each do |attr|
received_method if some_condition
end
end
This works well if all the received_method does is run through some code:
def some_method
do_something
end
Which is the equivalent of:
def acceptor_method(received_method)
an_arry.each do |attr|
do_something if some_condition
end
end
But what if I want the received_method to break the loop and return a value, as with:
def acceptor_method(received_method)
an_arry.each do |attr|
return true if some_condition
end
end
Unfortunately, this doesn't work:
def some_method
return true
end
As it only returns true for some method, not for acceptor_method--which continues to play through the loop.
So is there a way to send a method that when run is the equivalent of return true?
def acceptor_method
[1, 2, 3, 4].each do |attr|
ret = yield attr
puts attr
end
end
test = acceptor_method do |attr|
break 'test' if attr == 3
end
puts test
outputs:
1
2
test
You can do this using blocks rather than methods. See How can I return something early from a block?
Basically if you have a block with break value and yield to it, the function will return value. Unfortunately I don't see a way to do this using methods, since Ruby really doesn't like having break outside of a block or loop.
I'm trying to figure out how to dynamically create methods
class MyClass
def initialize(dynamic_methods)
#arr = Array.new(dynamic_methods)
#arr.each { |m|
self.class.class_eval do
def m(*value)
puts value
end
end
}
end
end
tmp = MyClass.new ['method1', 'method2', 'method3']
Unfortunately this only creates the method m but I need to create methods based on the value of m, ideas?
There are two accepted ways:
Use define_method:
#arr.each do |method|
self.class.class_eval do
define_method method do |*arguments|
puts arguments
end
end
end
Use class_eval with a string argument:
#arr.each do |method|
self.class.class_eval <<-EVAL
def #{method}(*arguments)
puts arguments
end
EVAL
end
The first option converts a closure to a method, the second option evaluates a string (heredoc) and uses regular method binding. The second option has a very slight performance advantage when invoking the methods. The first option is (arguably) a little more readable.
define_method(m) do |*values|
puts value
end