Wrap block to use refinement - ruby

Given the following refinement:
module StringRefinement
refine String do
def bar
length
end
end
end
I want to implement a module to execute blocks using my refinement:
module Demo
using StringRefinement
def self.wrap(*args, &block)
instance_eval(&block)
end
end
And now I should be able to use it like this:
Demo.wrap { puts "some text".bar }
Which doesn't work :-(
I've been playing with the block binding, yield, context, singleton_class... but I still cannot get this to work. How can I do it?

You need to move your using StringRefinement statement outside of your module.
Check the following paragraph from the docs:
You may only activate refinements at top-level, not inside any class, module or method scope.
http://ruby-doc.org/core-2.1.1/doc/syntax/refinements_rdoc.html#label-Scope

Related

Why can't Ruby find a method declared right above?

I have a simple file called helper.rb that looks like this:
module MyHelper
def initialize_helper
puts "Initialized"
end
initialize_helper()
end
And another simple file like this:
require_relative 'helper.rb'
include MyHelper
puts "Done"
But when I run this second file, it results in this error:
helper.rb:6:in `<module:MyHelper>': undefined method `initialize_helper' for MyHelper:Module (NoMethodError)
Why can't Ruby find this initializeHelper method defined directly above where I'm calling it???
Try
def self.initialize_helper
puts "Initialized"
end
Without the self., you're declaring an instance method intended to be called on objects, not the module itself. So, for instance, your original code is intended to be used like
module MyHelper
def initialize_helper
puts "Initialized"
end
end
class Foo
include MyHelper
end
Foo.new.initialize_helper
But if you want to call it on the module, you need to have self. in front of it to make it a method on the module itself.

Can I call a custom method on an object with this notation?

This is an example of what I am after:
def already_taken?
# Magic goes here...
end
"Charlotte".already_taken?
Would it be possible to construct a method in a way where I can call it directly on a String object, without having to modify the String class itself?
You could patch the String class with a custom module:
module MyStringPatch
def already_taken?
'yes'
end
end
String.include MyStringPatch
"Charlotte".already_taken?
If you want to add methods to any class (String in this case), without monkey-patching it, you should consider using Refinements.
module StringRefinements
refine String do
def already_taken?
puts "yes!"
end
end
end
# in another file...
using StringRefinements
"Charlotte".already_taken?
The already_taken? method will only be available in a scope that calls using StringRefinements and nowhere else.

How do I make a Ruby method that lasts for the lifetime of a block?

Inside the body of a class, I'd like to pass a block to a method called with. For the lifetime of the block, I would like a with_value method to be available.
Otherwise, everything inside the block should behave as if it were outside the block.
Here's an example:
class C
extend M
with "some value" do
do_something_complicated
do_something_complicated
do_something_complicated
end
end
We can almost get this with:
module M
def with(str, &block)
Object.new.tap do |wrapper|
wrapper.define_singleton_method :with_value do # Here's our with_value
str # method.
end
end.instance_eval &block
end
def do_something_complicated # Push a value onto an
(#foo ||= []).push with_value # array.
end
end
but there's a problem: since we're evaluating the block passed to with inside the context of a different object, do_something_complicated isn't available.
What's the right way to pull this off?
This will make with_value available only within the block. However, _with_value will be defined within or outside of the block.
module M
def _with_value
...
end
def with(str, &block)
alias with_value _with_value
block.call
undef with_value
end
...
end
I cannot tell from the question whether this is a problem. If it is a problem, you need to further describe what you are trying to do.
Basically, the idea is to use method_missing to forward method calls from the dummy class to the calling class. If you also need to access instance variables, you can copy them from the calling class to your dummy class, and then back again after the block returns.
The Ruby gem docile is a very simple implementation of such a system. I suggest you read the source code in that repository (don't worry, it's a very small codebase) for a good example of how DSL methods like the one in your example work.
Here is a way that is closer to your attempt:
module M
def with(str, &block)
dup.tap do |wrapper|
wrapper.define_singleton_method :with_value do
...
end
end.instance_eval &block
end
...
end
dup will duplicate the class from where with is called as a class method.

Ruby Static method with local scope

The title sounds rediculous because it is. My biggest issue is actually trying to figure out what question to ask.
The goal: To be able to implement the code as described below OR to figure out what terminology I should be using to search for the correct answer.
The issue: I wish to have a system where classes register "processors" via a method within the class definition. eg:
class RunTheseMethodsWhenICallProcess
Include ProcessRunner
add_processor :a_method_to_run
add_processor :another_method_to_run
def a_method_to_run
puts "This method ran"
end
def another_method_to_run
puts "another method ran"
end
end
Module ProcessRunner
def process
processors.each {|meth| self.send(meth)}
end
end
My issues are mostly with understanding the scope and reference of the class to make them interact. As it stands, I have been able to add a static method 'add_processor' by calling class.extend(AClass) in the included method and adding in the class there.
The idea for this syntax was inspired by DataMappers 'property' and 'before' methods. Even with the code checked out, I am having a touch of trouble following it.
Thanks so much for any help you can offer.
If I got you right, the following will do what you want.
It initializes each class (or module) including ProcessRunner to have an empty array in ##processors. Additionally it adds class methods processors (a simple getter) and add_processor.
The process method had to be adjusted to use the class method. In fact, you could add a wrapper for this, but I think that would be to verbose for such a sample.
module ProcessRunner
module ClassMethods
def add_processor(processor)
processors << processor
end
def processors
class_variable_get :##processors
end
end
def self.included(mod)
mod.send :class_variable_set, :##processors, []
mod.extend ClassMethods
end
def process
self.class.processors.each {|meth| self.send(meth)}
end
end
class RunTheseMethodsWhenICallProcess
include ProcessRunner
add_processor :a_method_to_run
add_processor :another_method_to_run
def a_method_to_run
puts "This method ran"
end
def another_method_to_run
puts "another method ran"
end
end

Using define_method to define global methods outside of a module

I'd like to write this:
[:p, :h1, :h3].each do |tag|
define_method(tag) { |text| "<#{tag}>#{text}</#{tag}>" }
end
It's just some simple methods to wrap text in HTML tags. I want to be able to use these methods in the rest of the script. Unfortunately the define_method method seems to only work inside of a module. But if I did this inside a module, I wouldn't be able to cleanly write p "This is a paragraph.", it'd be something like HTML::p "This is a paragraph." which would be pretty terrible.
So how do I define methods like this globally?
If you really need to do it:
[:p, :h1, :h3].each do |tag|
Object.send(:define_method, tag) { |text| "<#{tag}>#{text}</#{tag}>" }
end
I don't know your whole situation, but you probably don't really want to be defining global methods. If you don't want to type the HTML:: then add an include HTML statement at the beginning of your code.
One hack would be to create the method inside Object, which would then be global method you desire:
class Object
def create_method(name, &block)
self.class.send(:define_method, name, &block)
end
end
tag = 'p'
a = Object.new
a.create_method(tag.intern) {|v| puts "<#{tag}>#{v}</#{tag}>"}
send(tag.intern, 'content') # => <p>content</p>

Resources