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>
Related
I want to create a bunch of methods for a find_by feature. I don't want to write the same thing over and over again so I want to use metaprogramming.
Say I want to create a method for finding by name, accepting the name as an argument. How would I do it? I've used define_method in the past but I didn't have any arguments for the method to take.
Here's my (bad) approach
["name", "brand"].each do |attribute|
define_method("self.find_by_#{attribute}") do |attr_|
all.each do |prod|
return prod if prod.attr_ == attr_
end
end
end
Any thoughts? Thanks in advance.
If I understand your question correctly, you want something like this:
class Product
class << self
[:name, :brand].each do |attribute|
define_method :"find_by_#{attribute}" do |value|
all.find {|prod| prod.public_send(attribute) == value }
end
end
end
end
(I'm assuming that the all method returns an Enumerable.)
The above is more-or-less equivalent to defining two class methods like this:
class Product
def self.find_by_name(value)
all.find {|prod| prod.name == value }
end
def self.find_by_brand(value)
all.find {|prod| prod.brand == value }
end
end
It if you read the examples here http://apidock.com/ruby/Module/define_method you will find this one:
define_method(:my_method) do |foo, bar| # or even |*args|
# do something
end
is the same as
def my_method(foo, bar)
# do something
end
When you do this: define_method("self.find_by_#{attribute}")
that is incorrect. The argument to define_method is a symbol with a single word.
Let me show you some correct code, hopefully this will be clear:
class MyClass < ActiveRecord::Base
["name", "brand"].each do |attribute|
define_method(:"find_by_#{attribute}") do |attr_|
first(attribute.to_sym => attr_)
end
end
end
This will produce class methods for find_by_brand and find_by_name.
Note that if you're looking into metaprogramming, this is a good use-case for method_missing. here's a tutorial to use method_missing to implement the same functionality you're going for (find_by_<x>)
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
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.
Is there any other ways to make it another way than with closure and define_method?
Say i have this:
def test
result=[1,2,3]
metadata=['foo', 'bar'] # for simplicity, could be fetched from database
result.define_singleton_method :headers, lambda { metadata }
result
end
I'm curious, are there other ways to embed, make static or well, "copy" metadata variable into method in Ruby?
I find it kind of iffy to be defining methods like this (probably you should have an object that looks like an array rather than making the array look like your object), but this will work as well.
def test
result=[1,2,3]
result.instance_eval { #headers = ['foo', 'bar'] }
result.define_singleton_method(:headers) { #headers }
result
end
You could also do something like this (it's a little different in that it creates a setter as well).
module HasHeaders
attr_accessor :headers
end
def test
result = [1,2,3].extend HasHeaders
result.headers = ['foo', 'bar']
result
end
Well, method definitions aren't closures, so this will not work:
def result.headers
metadata
end
Since you are testing, I recommend stubbing the method. With RSpec::Mocks:
result.stub(:headers).and_return metadata
Related:
Define a method that is a closure in Ruby
I a writing a DSL to generate parsers for bioinformatics flat files. I would like to let the user define helper functions in block and then include the function in the parsing context object. I would like to use a syntax like:
rules = Rules.new do
helpers do
def foo()
#...
end
def bar( baz )
#...
end
end
# Here come the parsing rules which can access both helper methods
end
I would like to add the helper methods to a module definition and the include the module in a instance (just the instance not the class).
Do you have an idea how I can reach that goal ? Answers with a slightly different syntax are appreciated too.
Something like this, perhaps?
class Rules
def initialize(&block)
instance_eval &block
end
def helpers
yield
end
end
Rules.new do
helpers do
def hi_world
puts "Hello World!"
end
end
hi_world
end
Note though that here the helpers method does nothing special, it just relies on the fact that the Rules block is already the current scope.