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
Related
First of all, this is really just a golf question. My code works fine as it is. But I feel like there is probably a better (i.e. cooler) way to do this.
So I've got a class that acts a lot like a hash. However, it really internally generates a hash for each call to its hash-ish methods. The private method for generating that hash is calculated(). So my code currently has a lot of method definitions like this:
def each(&block)
return calculated.each(&block)
end
def length()
return calculated.length
end
Is there a concise way to delegate all those method calls to the calculated method?
I figured it out and it's incredibly simple. Just delegate to the name of the method. Here's a working example:
class MyClass
extend Forwardable
delegate %w([] []=) => :build_hash
def build_hash
return {'a'=>1}
end
end
edit: don't do this; I forgot Forwardable existed
You can write a "macro" for this. Well, Ruby doesn't technically have actual "macros" but it's a fairly common pattern nonetheless. Rails in particular uses it extensively - stuff like belongs_to, validates, etc are all class methods which are being used to generate instance-level functionality.
module DelegateToFunc
def delegate_to_func(delegate, delegators)
delegators.each do |func_name|
# Note: in Ruby 2.7 you can use |...| instead of |*args, &blk|
define_method(func_name) do |*args, &blk|
send(delegate).send(func_name, *args, &blk)
end
end
end
end
class SequenceBuilder
extend DelegateToFunc
delegate_to_func(:calculated, [:length, :each])
attr_accessor :min, :max
def initialize(min:, max:)
#min, #max = min, max
end
def calculated
min.upto(max).to_a
end
end
SequenceBuilder.new(min: 5, max: 10).length # => 6
SequenceBuilder.new(min: 1, max: 4).each { |num| print num } # => 1234
I will say, though, that methods generated by metaprogramming can sometimes be hard to track down and can make a program confusing, so try and use them tastefully ...
For example, do you really need your object to expose these hash-like methods? Why not just let the caller read the hash via calculated, and then call the hash methods directly on that?
I am trying to write this inside my class:
class << self
def steps
#steps.call
end
def transitions
#transitions.call
end
def steps(&steps)
#steps = steps
end
def transitions(&transitions)
#transitions = transitions
end
end
That won't work since in Ruby, I can't do this kind of method overloading. Is there a way around this?
You can kind of do this with method aliasing and mixins, but the way you handle methods with different signatures in Ruby is with optional arguments:
def steps(&block)
block.present? ? #steps = block : #steps.call
end
This sort of delegation is a code smell, though. It usually means there's something awkward about the interface you've designed. In this case, something like this is probably better:
def steps
#steps.call
end
def steps=(&block)
#steps = block
end
This makes it clear to other objects in the system how to use this interface since it follows convention. It also allows for other cases, like passing a block into the steps method for some other use:
def steps(&block)
#steps.call(&block)
end
Ruby does not support method overloading (see "Why doesn't ruby support method overloading?" for the reason). You can, however, do something like:
def run(args*)
puts args
end
args will then be an array of the arguments passed in.
You can also pass in a hash of options to handle arguments, or you can pass in nil when you don't want to supply arguments and handle nil in your method body.
I'm often in a situation where I have a class that contains a collection. I'd like external code to be able to iterate over this collection, but not modify it.
I end up writing something like this:
def iter
#internal_collection.each do |o|
yield o
end
end
This allows the external code to do:
object.iter do |o|
do_something(o)
end
There must be a more elegant way of writing the "iter" method. Any ideas?
Before elegance, I would make sure I return an Enumerator if no block is given.
This way your users can do object.iter.with_index do |obj, i|
An easy way to do this is and shorten your code is:
def iter(&block)
#internal_collection.each(&block)
end
In other circumstances, you might want to simply return a copy...
def collection
#internal_collection.dup
end
As far as explicitly writing the method goes, that's about as simple as it gets. But I think what you're after is the Forwardable module. Your code would look like this:
require 'forwardable'
class YourClass
extend Forwardable
def_delegator :#internal_collection, :each, :iter
end
Or if you wanted, you could delegate the whole Enumerable protocol to your internal collection and get all the standard Enumerable behavior that your internal collection features:
class YourClass
extend Forwardable
def_delegators :#internal_collection, *Enumerable.instance_methods
end
I'd use dup and freeze on your internal collection, then expose it to the world:
def collection
#internal_collection.dup.freeze
end
collection.map!(&:to_s) #=> raise RuntimeError: can't modify frozen Array
I wrote code for an Enumerables module:
module Enumerables
def palindrome?
if self.is_a?(Hash)
return false
else
self.join('').gsub(/\W/,"").downcase == self.join('').gsub(/\W/,"").downcase.reverse
end
end
end
The problem is, I have to write these:
class Array
include Enumerables
end
class Hash
include Enumerables
end
to make the code run successfully.
Is there an easy way to make the "palindrome?" method run with different instance types?
The module is not Enumerables but Enumerable so if you have
module Enumerable
def palindrome?
...
end
end
it will work without includes.
If you want to add this method to all objects see texasbruce's answer.
Open any class below Object level and add this method there. Then it will be accessible to almost all built-in types and all user defined types.
You can put it in Object, Kernel(it is a module), BasicObject.
For example,
class Object
def foo
puts "hello"
end
end
[].foo
You could use the ObjectSpace.each_object iterator with a filter to find classes that include Enumerable and extend them dynamically:
# XXX: this iterator yields *every* object in the interpreter!
ObjectSpace.each_object do |obj|
if obj.is_a?(Class) && (obj < Enumerable)
obj.module_eval { include Enumerables }
end
end
[1,2,1].palindrome? # => true
{}.palindrome? # => false
Now the trick is to write something that works for all enumerable types in a meaningful way! Note also that this sort of metaprogramming is fun for kicks but has serious implications if you plan to use it for anything other than "toy" programs.
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>