Ruby switch like idiom - ruby

I have recently started a project in Ruby on Rails. I used to do all my projects before in Python but decided to give Ruby a shot.
In the projects I wrote in Python I used a nice little technique explained by the correct answer in this post:
Dictionary or If statements, Jython
I use this technique due to Python not having a native switch function and it also get rid of big if else blocks
I have been trying to do recreate the above method in Ruby but can't seem to quite get it.
Could anyone help me out?

If you only need to call a method by its name stored in a string, standard Ruby way of doing it is using method Object#send:
def extractTitle dom
puts "title from #{dom}"
end
def extractMetaTags dom
puts "metatags from #{dom}"
end
dom = 'foo'
type = 'extractTitle'
send type, dom
#=> title from foo
type = 'extractMetaTags'
send type, dom
#=> metatags from foo
Otherwise, you can use Ruby's case statement, as already suggested.

While nothing prevents you from using the class-based approach, why avoid rubys case statement?
case thing
when 'something'
do_something
when 'nothing'
do_nothing
else
do_fail
end

As others have said, there are alternative ways of doing this in Ruby, but if you are just curious then an equivalent to that Python approach in Ruby (making use of Object#send once you have determined the method name) would be:
class MyHandler
def handle_test(arg)
puts "handle_test called with #{arg}"
end
def handle_other(arg)
puts "handle_other called with #{arg}"
end
def handle(type, *args)
method_name = "handle_#{type}"
if respond_to? method_name
send(method_name, args)
else
raise "No handler method for #{type}"
end
end
end
You can then do:
h = MyHandler.new
h.handle 'test', 'example'
h.handle 'other', 'example'
h.handle 'missing', 'example'
and the output would be:
handle_test called with example
handle_other called with example
handle.rb:15:in `handle': No handler method for missing (RuntimeError)
from handle.rb:23

Related

Does Ruby support type-hinting?

PHP Example:
function do_something(int $i) {
return $i + 2;
}
Ruby Example:
class MyClass
# ...
end
def do_something(MyClass x)
x.prop1 = "String..."
end
Is there anything similar to this? Thanks.
Ruby 3 will introduce types to Ruby (Source). You can already use Sorbet now to add types to your ruby code.
Ruby does not have such thing, but all you have to do is add a single line as so:
def do_something(x)
raise "Argument error blah blah" unless x.kind_of?(MyClass)
...
end
Not a big deal. But if you feel it is too verbose, then just define a method:
module Kernel
def verify klass, arg
raise "Argument error blah blah" unless arg.kind_of?(klass)
end
end
and put that in the first line:
def do_something(x)
verify(MyClass, x)
...
end
If you really want to make sure a certain action is performed on an instance of a specific class (like an integer), then consider defining that action on the specific class:
class Integer
def two_more
self + 2
end
end
3.two_more #=> 5
2.0.two_more #=> undefined method `two_more' for 2.0:Float (NoMethodError)
class MyClass
def do_something
self.prop1 = "String..."
end
end
Shameless plug - I just published a ruby gem for this - http://rubygems.org/gems/type_hinting
Usage is here - https://github.com/beezee/ruby_type_hinting
I went looking for it first and found this thread. Couldn't find what I was looking for so I wrote it and figured I'd share here.
I think it's more concise than any of the proposed solutions here and also allows you to specify return type for a method.
No, Ruby itself doesn't support it, and I think that isn't the intention of Matz to add it to the language... Ruby really embraces duck typing in its own internal API and it makes Ruby a very dynamic and productive language...

method_missing with unquoted string arguments in Ruby - possible?

I'm learning Ruby and want to be able to do this:
Printer.hi there
and have Ruby output
"hi there"
So far I have the following implementation
class Printer
def method_missing(name, *args)
puts "#{name} #{args.join(',')}"
end
end
But this only lets me do
Printer.hi "there"
If I attempt
Printer.hi there
I get a
NameError: undefined local variable or method `there' for main:Object
which makes sense as I haven't ever defined 'there'. Is there a way to make this work though?
No, this is not possible in the form given (as far as I know).
You aren't looking for method missing, you are looking for the equivalent in the Ruby interpreter to capture when it cannot find a given symbol. So while you cannot intercept it there, you can do it inside of a block:
def hi(&block)
begin
yield
rescue NameError => e
e.message =~ /variable or method `(.+)'/
puts "hi #{$1}"
end
end
hi { there } # => hi there
Please note that I feel like a terrible world citizen for showing you this. Please don't use it anywhere, ever.
Yes, there is a way. When you write there without an explicit receiver, the receiver is the self object of that scope. In this case, it is main. Define methods_missing in the main context.
def method_missing(name, *args)
puts "#{name} was called with arguments: #{args.join(',')}"
end
But if you do so, that would mess up the rest of your code, perhaps. I see not point in doing this.
Since the return value of puts is nil, if you do Printer.hi there, it will evaluate to Printer.hi(nil). So in order for it to output "hi there", you need to define:
class Printer
def self.hi _; puts "hi there" end
end
No because strings need to be quoted, so they are not seen as variables.
Otherwise variables such as there would need some special sort of character to indicate that it is a string. However this still wouldn't work well as spaces would then need to be dealt with.
Use single or double quotes.
It's how the language works. accept this and move on to the next challenge :)
Interestingly you can do this in ruby 1.8.7 with just this code:
def method_missing(*args)
puts args.join ' '
end
I learned about this from Gary Bernhardt's talk, Wat. In 1.9 this gives you a stack level too deep error unless you do it inside a class. Google lead me to this post on Aurthur's tech blog thing, which claims you can do something similar in JRuby 1.9 mode:
def method_missing(*args)
puts [method.to_s, args].flatten.join ' '
end
However when I tried this on MRI 1.9.3 it did not work either. So in 1.9 you can't quite do what you want. Here is the closest I could come:
class Printer
def self.hi(message)
puts "hi #{message}"
end
def self.method_missing(m, *args)
[m.to_s, args].flatten.join ' '
end
def self.bare
hi there
end
end
Printer.bare

What is the point of using "send" instead of a normal method call?

as far as I understand 'send' method, this
some_object.some_method("im an argument")
is same as this
some_object.send :some_method, "im an argument"
So what is the point using 'send' method?
It can come in handy if you don't know in advance the name of the method, when you're doing metaprogramming for example, you can have the name of the method in a variable and pass it to the send method.
It can also be used to call private methods, although this particular usage is not considered to be a good practice by most Ruby developers.
class Test
private
def my_private_method
puts "Yay"
end
end
t = Test.new
t.my_private_method # Error
t.send :my_private_method #Ok
You can use public_send though to only be able to call public methods.
In addition to Intrepidd's use cases, it is convenient when you want to route different methods on the same receiver and/or arguments. If you have some_object, and want to do different things on it depending on what foo is, then without send, you need to write like:
case foo
when blah_blah then some_object.do_this(*some_arguments)
when whatever then some_object.do_that(*some_arguments)
...
end
but if you have send, you can write
next_method =
case foo
when blah_blah then :do_this
when whatever then :do_that
....
end
some_object.send(next_method, *some_arguments)
or
some_object.send(
case foo
when blah_blah then :do_this
when whatever then :do_that
....
end,
*some_arguments
)
or by using a hash, even this:
NextMethod = {blah_blah: :do_this, whatever: :do_that, ...}
some_object.send(NextMethod[:foo], *some_arguments)
In addition to everyone else's answers, a good use case would be for iterating through methods that contain an incrementing digit.
class Something
def attribute_0
"foo"
end
def attribute_1
"bar"
end
def attribute_2
"baz"
end
end
thing = Something.new
3.times do |x|
puts thing.send("attribute_#{x}")
end
#=> foo
# bar
# baz
This may seem trivial, but it's occasionally helped me keep my Rails code and templates DRY. It's a very specific case, but I think it's a valid one.
The summing briefly up what was already said by colleagues: send method is a syntax sugar for meta-programming. The example below demonstrates the case when native calls to methods are likely impossible:
class Validator
def name
'Mozart'
end
def location
'Salzburg'
end
end
v = Validator.new
'%name% was born in %location%'.gsub (/%(?<mthd>\w+)%/) do
# v.send :"#{Regexp.last_match[:mthd]}"
v.send Regexp.last_match[:mthd].to_sym
end
=> "Mozart was born in Salzburg"
I like this costruction
Object.get_const("Foo").send(:bar)

Can I make ruby display method parameter values in a backtrace?

Using MRI 1.9
When an exception is raised that causes a backtrace to be printed, it would often be immensely easier to debug if the backtrace showed the receiver and values of method parameters as well as the method name. Is there any way of doing this?
Think about situations such as passing a nil deep into library code that wasn't expecting it, or where two strings have incompatible encodings and some routine is trying to concatenate them
you can with 1.8.6 by using the backtracer gem.
1.9 has slightly broken callbacks so isn't compatible yet, though. I might be able to get it to work, if desired.
You could use something like a delegate and see the parameters to a single object:
class A
def go a, b
end
end
class A2
def initialize *args
#delegate = A.new *args
end
def method_missing meth, *args
p "got call to #{meth}", args.join(', ')
#delegate.send(meth,*args)
end
end
which outputs
"in go2"
"got call to go"
"3, 4"

Ruby core extensions with modules

Basically I have two modules: CoreExtensions::CamelcasedJsonString and …::CamelcasedJsonSymbol. The latter one overrides the Symbol#to_s, so that the method returns a String which is extended with the first module. I don't want every string to be a CamelcasedJsonString. This is the reason why I try to apply the extension instance specific.
My problem is, that Symbol#to_s seems to be overridden again after I included my module (the last spec fails):
require 'rubygems' if RUBY_VERSION < '1.9'
require 'spec'
module CoreExtensions
module CamelcasedJsonString; end
module CamelcasedJsonSymbol
alias to_s_before_core_extension to_s
def to_s(*args)
to_s_before_core_extension(*args).extend(CamelcasedJsonString)
end
end
::Symbol.send :include, CamelcasedJsonSymbol
end
describe Symbol do
subject { :chunky_bacon }
it "should be a CamelcasedJsonSymbol" do
subject.should be_a(CoreExtensions::CamelcasedJsonSymbol)
end
it "should respond to #to_s_before_core_extension" do
subject.should respond_to(:to_s_before_core_extension)
end
specify "#to_s should return a CamelcasedJsonString" do
subject.to_s.should be_a(CoreExtensions::CamelcasedJsonString)
end
end
However the following example works:
require 'rubygems' if RUBY_VERSION < '1.9'
require 'spec'
module CoreExtensions
module CamelcasedJsonString; end
end
class Symbol
alias to_s_before_core_extension to_s
def to_s(*args)
to_s_before_core_extension(*args).extend(CoreExtensions::CamelcasedJsonString)
end
end
describe Symbol do
subject { :chunky_bacon }
it "should respond to #to_s_before_core_extension" do
subject.should respond_to(:to_s_before_core_extension)
end
specify "#to_s should return a CamelcasedJsonString" do
subject.to_s.should be_a(CoreExtensions::CamelcasedJsonString)
end
end
Update: Jan 24, 2010
The background of my problem is that I try to convert a huge nested hash
structure into a JSON string. Each key in this hash is a Ruby Symbol in the
typical underscore notation. The JavaScript library which consumes the JSON
data expects the keys to be strings in camelcase notation. I thought that
overriding the Symbol#to_json method might be the easiest way. But that
didn't work out since Hash#to_json calls first #to_s and afterwards
#to_json on each key. Therefore I thought it might be a solution to extend
all Strings returnd by Symbol#to_s with a module which overrides the
#to_json method of this specific string instance to return a string that has
a #to_json method which returns itself in camelcase notation.
I'm not sure if there is an easy way to monkey patch Hash#to_json.
If someone wants to take a look into the JSON implementation I'm using, here is the link: http://github.com/flori/json/blob/master/lib/json/pure/generator.rb (lines 239 and following are of interest)
Your second monkeypatch works since you are re-opening the Symbol class.
The first one doesn't because all the include does is add the module in the list of included modules. These get called only if the class itself doesn't define a specific method, or if that method calls super. So your code never gets called.
If you want to use a module, you must use the included callback:
module CamelcasedJsonSymbol
def self.included(base)
base.class_eval do
alias_method_chain :to_s, :camelcase_json
end
end
def to_s_with_camelcase_json(*args)
to_s_without_camelcase_json(*args).extend(CamelcasedJsonString)
end
end
I've used active_record alias_method_chain, which you should always do when monkey patching. It encourages you to use the right names and thus avoid collisions, among other things.
That was the technical answer.
On a more pragmatic approach, you should rethink this. Repeatedly extending strings like this is not nice, will be a huge performance drain on most implementations (it clears the whole method cache on MRI, for instance) and is a big code smell.
I don't know enough about the problem to be sure, or suggest other solutions (maybe a Delegate class could be the right thing to return?) but I have a feeling this is not the right way to arrive to your goals.
Since you want to convert the keys of a hash, you could pass an option to #to_json and monkeypatch that instead of #to_s, like:
{ :chunky_bacon => "good" }.to_json(:camelize => true)
My first idea was to monkeypatch Symbol#to_json but that won't work as you point out because Hash will force the keys to strings before calling to_json, because javascript keys must be strings. So you can monkeypatch Hash instead:
module CamelizeKeys
def self.included(base)
base.class_eval do
alias_method_chain :to_json, :camelize_option
end
end
def to_json_with_camelize_option(*args)
if args.empty? || !args.first[:camelize]
to_json_without_camelize_option(*args)
else
pairs = map do |key, value|
"#{key.to_s.camelize.to_json(*args)}: #{value.to_json(*args)}"
end
"{" << pairs.join(",\n") << "}"
end
end
end
That looks kind of complicated. I probably don't understand what it is you're trying to achieve, but what about something like this?
#!/usr/bin/ruby1.8
class Symbol
alias_method :old_to_s, :to_s
def to_s(*args)
if args == [:upcase]
old_to_s.upcase
else
old_to_s(*args)
end
end
end
puts :foo # => foo
puts :foo.to_s(:upcase) # => FOO
and a partial spec:
describe :Symbol do
it "should return the symbol as a string when to_s is called" do
:foo.to_s.should eql 'foo'
end
it "should delegate to the original Symbol.to_s method when to_s is called with unknown arguments" do
# Yeah, wish I knew how to test that
end
it "should return the symbol name as uppercase when to_s(:upcase) is called" do
:foo.to_s(:upcase).should eql "FOO"
end
end

Resources