How to create this little DSL in Ruby? - ruby

My functions are:
def hello(str)
puts "hello #{str}"
end
def hello_scope(scope, &block)
# ???
end
I would like to temporarily augment a function within a block of my method.
In hello_scope, I simply want to prepend the scope string to the str before passing it to the original hello method. Here's an example:
hello 'world' #=> hello world
hello_scope "welcome!" do
hello 'bob' #=> welcome!hello bob
hello 'alice' #=> welcome!hello alice
end
I'm kind of a noob when it comes to this kind of thing in Ruby. Can someone help me solve this in an elegant way?
Edit:
If it makes things easier, it's OK if we pass the method in as an argument to the block, such as:
hello_scope "welcome!" do |h|
h "bob" #=> welcome!hello bob
h "alice" #=> welcome!hello alice
end

One way is to create a "evaluation context object" on which the block is going to be instance-eval'd. This object has to provide all the methods that are specific to the block. In the example below, I did not use the same name as I don't remember how to explicitly referring to the global method "hello" (to avoid infinite recursion). In a proper library, "hello" would be defined as a class method somewhere, so that would not be an issue.
For instance
def hello(str)
puts "hello #{str}"
end
class HelloScope
def h(str)
print scope
hello(str)
end
end
def hello_scope(scope, &block)
HelloScope.new(scope).instance_eval(&block)
end

Just modify your "hello" method to take into account current scope:
class Greeter
def initialize
#scope = nil
end
def hello(str)
puts "#{#scope}hello #{str}"
end
def with_scope(scope)
#scope = scope
yield
#scope = nil
end
end
Greeter.new.instance_eval do
hello 'world' #=> hello world
with_scope "welcome!" do
hello 'bob' #=> welcome!hello bob
hello 'alice' #=> welcome!hello alice
end
end

Related

How can I reference an added Method to an existing Class in Ruby?

I extended the String Class with an Method. Let's say 'foo'.
class String
def foo
puts "Hello World."
end
end
Why is it not possible to call either String.foo or String.method("foo")?
I am getting an NoMethodError when I try.
My ultimate Goal is to pass 'foo' to another Method. Something like bar(String.method('foo'))
Thanks in advance
foo is an instance method. You can use Module#instance_method to get the method, then bind it to a String object using .bind(string) and then call that using .call(args).
class String
def foo
puts "Hello #{self}."
end
end
p String.instance_method(:foo)
p String.instance_method(:foo).bind("World")
String.instance_method(:foo).bind("World").call
Output:
#<UnboundMethod: String#foo() a.rb:2>
#<Method: String#foo() a.rb:2>
Hello World.
If you truly want it to be a class method, you can define it so using self.methodname:
class String
def self.foo
puts "Hello"
end
end
String.foo # => Hello
You could also make it return a proc so it's passable elsewhere:
class String
def self.foo = -> { puts "Hello" }
end
String.foo # => #<Proc:0x0000000102a05ea8 (irb):14 (lambda)>
String.foo[] # => Hello
With arguments:
class String
def self.foo = ->(name){ puts "Hello #{name}" }
end
String.foo["Malte"] # => Hello Malte
You are defining an instance method, but tried to call a static method.
It works as expected when you call it the correct way.
class String
def foo
puts "Hello World."
end
end
String.new("test").foo # output: Hello World.
Your first question is, "If I execute
class String
def foo
puts "Hello World."
end
end
why is it not possible to call either String.foo or String.method("foo")?
When you execute
String.foo
#=> NoMethodError: undefined method 'foo' for String:Class
the error message1 is very specific: the class String does not have a method foo. That is consistent with the following.
String.methods.include?(:foo)
#=> false
What you have done is create an instance method foo of the class string. Let's take "cat", an instance of the class String:
"cat".methods.include?(:foo)
#=> true
"cat".foo
#=> "Hello World."
We can also look at
String.instance_methods
#=> [:unicode_normalize, :unicode_normalize!, :ascii_only?,
# :to_r,..., :foo, :count,..., :instance_exec]
or to find :foo more easily among String's 187 or so instance methods, we could examine
String.instance_methods.sort
#=> [:!, :!=,..., :extend, :foo, :force_encoding,..., :yield_self]
See Kernel#methods and Module#instance_methods
If you want to invoke foo on String you need to write
class String
def self.foo
puts "Hello World."
end
end
String.foo
#=> "Hello World."
When the method is constructed self equals String, so the above is the same as
class String
def String.foo
puts "Hello World."
end
end
The reason self is generally used in place of the literal class name is that it allows the class name to be changed without having to change the method definition. (We obviously wouldn't rename String, but we might want to change a user-defined class name (e.g., change Amount to Quantity.)
Actually, Ruby only has instance methods, so let's look at what def String.foo means in terms of an instance method.
We see that
String.class
#=> Class
meaning that String is an instance of Class. We therefore could write
class Class
def foo
puts "Hello World."
end
end
String.foo
#=> "Hello World."
but this has the undesirable effect of making this instance method Class available to all classes:
Array.foo
#=> "Hello World."
Hash.foo
#=> "Hello World."
Instead, we want is to limit the availability of foo to just one of Class' instances, String.
Every Ruby object has a special class called a "Singleton class" (one of several names used). I say "special" because it differs from regular classes in that it cannot be subclassed and one cannot create instances of it.
We can write the following to create a method foo that we can invoke on String.
string_single = String.singleton_class
#=> #<Class:String>
string_single.define_method(:foo) { puts "Hello World." }
#=> :foo
string_single.instance_methods.include?(:foo)
#=> true
String.foo
#=> Hello World.
See Module#define_method.
In fact,
def String.foo
puts "Hello World."
end
is just shorthand for the use of define_method above. Similarly, the familiar
class String
def foo
puts "Hello World."
end
end
#=> :foo
is just shorthand for
String.define_method(:foo) { puts "Hello World." }
#=> :foo
"cat".foo
#=> "Hello World."
The second part of your questions asks how to pass one singleton method to another. That's easy. We have already defined foo on String's singleton class so let's define another, bar, that calls foo.
String.singleton_class.define_method(:bar) { foo }
#=> :bar
String.bar
#=> Hello World.
1. Pay careful attention to error messages in their entirety. Often they will pinpoint the problem.

How to chain methods in ruby passing the output of one method to consecutive methods

How can I pass the results of a method to another method in ruby?
eg:
class D
def initialize(text)
#text = text
end
def a s
"hello #{s}"
end
def b s
"hi #{s}"
end
end
So, what I want to do is pass the output of method a to method b. So essentially(if the methods aren't inside a class) I can do the following the following:
puts b(a "Tom") #=>hi hello Tom
However, even if this isn't inside a class, it wouldn't look good if there are a lot of methods so there must be a more elegant way to do this. So what is the proper way to get the output hi hello Tom by applying the methods a and b to an instance of the class D?
UPDATE
I just wanted to make it a little bit more clear. Eg, in F# you can do something like this:
let a s = "hello " + s
let b s = "hi " + s
"Tom" |> a |> b #=> hello hi Tom
Here we defined functions a and b and then passed on the results to the next function.
I know that its a functional language so ways of doing things would be different there. But I am just wondering if there are any such tricks in Ruby?
You can leave the ()
def a s
"hello #{s}"
end
def b s
"hi #{s}"
end
puts b a "Tom"
If you have many methods :
puts [:a,:b].inject("Tom"){|result,method| self.send(method,result)}
If you want to use those methods with any object (including Classes) :
module Kernel
def chain_methods(start_value, *methods)
methods.inject(start_value){|result,method| self.send(method,result)}
end
end
class D
def a s
"hello #{s}"
end
def b s
"hi #{s}"
end
end
class E
class << self
def a s
"hello #{s}"
end
def b s
"hi #{s}"
end
end
end
# Example with instance methods
puts D.new.chain_methods("Tom", :a, :b)
# Example with class methods
puts E.chain_methods("Tom", :a, :b)
# Thanks mudasobwa :
E.chain_methods("Tom", :a, :b, :puts)

pass method with arguments in other method in Ruby

I'm interesting how to pass method with arguments in ruby. I need to implement something like command pattern with flexible function setting. Example => lambda functions in C#.
Ruby lambda functions are defined as follows:
a.lambda{ puts "Hello"}
a.call #=> Hello
a = lambda{|str| puts str }
a.call("Hello world !!!") #=> Hello world !!!
a = lambda{|*args| puts args.join(' ')}
a.call("Hello", "World") #=> Hello World
You could do the command pattern the way you do most things in Ruby: with a block.
class Soldier
def initialize(&block)
#command = block
end
def action
#command.call if #command
end
end
s = Soldier.new do #the block
line = "We are drill machines, drill machines feel no pain"
2.times{ puts line }
puts line.upcase
end
puts "Action:"
s.action
You can dynamically invoke methods along with their argument lists. Below is only one of the ways
class Foo
def foo(what)
puts what
end
end
Foo.new.send(:what, "something") # ==> "something"

class_eval how to pass parameter to method

How do I pass the parameter name in the following case..the name is being is evaluated before being passed to class_eval
class Foo
end
Foo.class_eval %Q{
def hello(name)
p "hello #{name}"
end
}
Sorry about not giving the entire scenario...
I just wanted to add a instance method dynamically to a class and that method should be able to take arguments...
the above code would not compile complaining that the name is not defined as local variable when executing in irb..
Thanks
The other answers are the "right" answer, but you could also just skip interpolating inside the p call:
Foo.class_eval %Q{
def hello(name)
p "hello \#{name}"
end
}
I thought you wanted to change the actual parameter name (possibly useful for completion or when using Pry on dynamic methods), here assuming it's in a global, but could also be passed into a method doing the class_eval:
Foo.class_eval %Q{
def hello(#{$argname})
p "hello \#{$argname}"
end
}
Really simple:
Foo.class_eval do
def hello(name)
p "hello #{name}"
end
end
Try passing a block to class_eval instead of an array (from this link):
class Foo
end
Foo.class_eval {
def hello(name)
p "hello #{name}"
end
}
You then can call the instance method hello in the usual fashion:
boo = Foo.new
boo.hello("you")
which produces:
>> boo.hello("you")
"hello you"
=> nil
class Foo
end
Foo.class_eval do
define_method :hello do |name|
p "hello #{name}"
end
end
Foo.new.hello("coool") # => "hello coool"

Ruby - How to not overwrite instance variables in methods?

Say that we have this class:
class Hello
def hello
#hello
end
def hello=(hello)
#hello = hello
end
def other_method
hello = 'hi!'
...
end
end
In this case,
h = Hello.new
h.other_method
puts h.hello
will write 'hi!', due to the interpretation of hello = as self.hello =. Is there a way to avoid this behaviour, giving to the hello = declaration a method-local scope (as, for example , var hello = would do with javascript code)?
EDIT I'm sorry, I was sure I had a problem for this reason! But now I have two other questions!
why hello = is not interpreted as self.hello =??? it is declared as an instance method...
why, even if I write
...
def other_method
self.hello = 'hi!'
end
end
h = Hello.new
h.other_method
puts h.hello
it returns nil??? now it should be an explicit assignment!
Thank you for the patience, I'm a bit confused! :-/
Actually this will not print hi! because hello = 'hi!' in other_method assigns to a local variable and is not interpreted as self.hello=.
If this is not the behaviour that you're seeing then there must be something else specific to your code that you haven't included in the question.
Answers to the follow up questions:
Within the body of a method identifier = value assigns a local variable (no method lookup is performed (so it's doesn't matter that there's a hello= method. You need the explicit self. to invoke the method.
When you write puts h.hello it prints 'hi!' which is the return value of h.hello but then returns nil which is the return value of puts.
It works for me:
$ irb
irb(main):001:0> class Hello
irb(main):002:1> def hello
irb(main):003:2> #hello
irb(main):004:2> end
irb(main):005:1> def hello=(hello)
irb(main):006:2> #hello = hello
irb(main):007:2> end
irb(main):008:1> def other_method
irb(main):009:2> self.hello = 'hi!'
irb(main):010:2> end
irb(main):011:1> end
=> nil
irb(main):012:0> h = Hello.new
=> #<Hello:0xb746b7cc>
irb(main):013:0> h.other_method
=> "hi!"
irb(main):014:0> puts h.hello
hi!
=> nil

Resources