I have several classes, e.g., P, that share the same instance method some_method:
class P
...
def some_method
#id
end
end
Instances of these classes will be used as arguments at many places like this:
p = P.new
q = Q.new
...
def some_outside_method(p,q,r,s)
another_outside_method(p.some_method, q.some_method, r.some_method, s.some_method)
end
I'm wondering if there is a more elegant way of writing it. Is it possible to automatically call p's some_method whenever p is referenced as in some_outside_method(p)? It is something like to_s implicitly called by puts, but more generalized.
You can reduce duplication by doing this, for example:
def some_outside_method(p,q,r,s)
args = [p, q, r, s].map{|o| o.send(:some_method)}
another_outside_method(*args)
end
or, more briefly:
def some_outside_method(*args)
args = args.map(&:some_method)
another_outside_method(*args)
end
or, more more briefly:
def some_outside_method(*args)
another_outside_method args.map(&:some_method)
end
But don't. Simple code is better than terse and "clever" one.
Related
I'm trying to write a method that prints class variable names and their values. As an example:
class A
def printvars
???
end
end
class <<A
def varlist(*args)
???
end
end
class B < A
varlist :c
def initialize(c)
#c = c
end
b = B.new(10)
b.printvars()
And I would like the output to be c => 10. But I don't know what goes in the ???. I've tried using a self.class_eval in the body of varlist, but that won't let me store args. I've also tried keeping a hash in the class A and just printing it out in printvars, but the singleton class is a superclass of A and so has no access to this hash. So far everything I've tried doesn't work.
I think something similar must be possible, since Rails does something related with its validates_* methods. Ideally I could make this work exactly as expected, but even a pointer to how to print just the variable names (so just c as output) would be most appreciated.
You might like this answer: What is attr_accessor in Ruby?
Basically, as you surmised, varlist needs to be a class method which takes a variable list of arguments (*args). Once you have those arguments you could try any number of things using send, respond_to?, or maybe even instance_variable_get. Note, none of those are really recommended, but I wanted to answer your question a bit.
The other half is that you should probably look into method_missing in order to understand how things like validates_* are working. The * part necessitates that you do something like method_missing because you can't actually do module_eval until you know what you're looking for. In the case of the magic rails methods, you don't necessarily ever know what you're looking for! So we rely on the built in method_missing to let us know what got called.
For funzies, try this in IRB:
class A
def method_missing(method, *args, &block)
puts method, args.inspect
end
end
A.new.banana(13, 'snakes')
A.new.validates_serenity_of('Scooters', :within => [:calm, :uncalm])
Does that help?
Just use Module#class_variables
As far as I can tell, you're vastly over-complicating this. All you need is the pre-defined Module#class_variables method. You can call this directly on the class, or invoke it through self if you want to bind it to an instance of the class. For example:
class Foo
##bar = "baz"
def show_class_variables
self.class.class_variables
end
end
Foo.class_variables
#=> [:##bar]
foo = Foo.new
foo.show_class_variables
#=> [:##bar]
There has got to be a more efficient way to do this in Ruby. I have a list of methods that scrape the same things (title, price) across multiple sites but in slightly different ways based on the code in each store. For example:
def store1_get_title
def store1_get_price
def store2_get_title
def store2_get_price
def store3_get_title
def store3_get_price
When calling all of these functions, I would just like a generic call with say a "namespace" parameter to do invoke any of these methods without having to type out all of them, something like:
for get_all_stores().each do |store|
store::get_title
store::get_price
end
...which would invoke store1_get_title, store1_get_price, store2_get_title, store2_get_price like I want. Is there something like this or a better way to do this?
Hope that makes sense. Thanks for any input!
Edit: these tasks are in rake task code.
This is a perfect use for classes. If you find two stores with the same software powering them (maybe Yahoo commerce or EBay stores) you can make instances of the classes with different parameters.
class Amazon
def get_price; end
def get_title; end
end
class Ebay
def initialize seller; end
def get_price; end
def get_title; end
end
[Amazon.new, Ebay.new("seller1"), Ebay.new("seller2")] each do |store|
store.get_price
store.get_title
end
And you can do this in any other object-oriented language by defining a base class or interface that all of the stores implement/inherit.
I don't understand the logic of your application. Perhaps you should think about a class definition (see Ken Blooms answer).
Nevertheless you could try a dynamic call with send:
def store1_get_title
p __method__
end
def store1_get_price
p __method__
end
def store2_get_title
p __method__
end
def store2_get_price
p __method__
end
def store3_get_title
p __method__
end
def store3_get_price
p __method__
end
all_stores = ['store1', 'store2', 'store3']
all_stores.each do |store|
send("#{store}_get_title")
send("#{store}_get_price")
end
You didn't define what get_all_stores returns. In my example I used Strings. You could add some syntactical sugar and extend String (I don't recommend this)
class String
def get_title()
send("#{self}_get_title")
end
def get_price()
send("#{self}_get_price")
end
end
all_stores.each do |store|
store.get_title
store.get_price
end
One last remark. You wrote
for get_all_stores().each do |store|
each alone should be enough. for is not ruby-like and in combination with each it doen't look reasonable to me.
- EDIT, SOLVED -
Ended up creating a method Object#rec to accomplish what I needed, this is the result:
#$l stores the last object on which Object#rec was called
$l=nil;class Object;def rec;$l=self;end;end
class String
attr_accessor :ref
alias_method :old_reverse, :reverse
def reverse
self.rec.old_reverse
end
def my_method
$l.ref + ' ' + self
end
end
a = "Hello"
b = "dlroW" ; b.ref = a
p b.reverse.my_method #=> Hello World
If anyone has a better way the question is still open.
- EDIT, SOLVED -
The problem:
I have a situation similar to this:
obj.method1.method2
where method1 returns something other than obj and I need method2 to access obj again as it holds a reference I need.
For example:
class String
attr_accessor :ref
def my_method(b)
b.ref + ' ' + self
end
end
a = "Hello"
b = "dlroW" ; b.ref = a
#I want my_method to access 'b.ref' without having to pass 'b'
p b.reverse.my_method(b) #=> Hello World
Alternative:
I know I could avoid having to pass b again if I used obj.my_method and my_method did both reversing(for the example) and accessing the reference, or like commented by the Tin Man have method1 change obj but return the original obj, but what I want is to know if it's possible or not to accomplish the above.
Thanks in advance.
Sounds kind of like you're looking for Object.tap:
Yields x to the block, and then returns x. The primary purpose of this method is to “tap into” a method chain, in order to perform operations on intermediate results within the chain.
For your example, you might be able to use String's reverse! inside the tap to manipulate the object. For your application, manipulate the object as you desire inside tap, then the object will be passed on to your following method.
Ruby enthusiasts! I am trying to write a DSL in ruby and i would like to be able to create some magic methods (not sure that is the most accurate term for what i want).
I would like to be able to do things like the following:
a = [1, 2, 3]
b = 2
(a contains b)
And have it resolve to true or false.
Essentially, how can i define the function "contains" so that it takes an array a and a variable b and performs a.contains?(b), but without all of the associated ruby-specific syntax?
if you want a DSL that doesn't use ruby syntax, you need to write a parser at the very least to perform the transformation (raganwalds rewrite lib might be a starting point, http://github.com/raganwald/rewrite)
That said, you don't want to do this. This is more code to maintain and Ruby has already made a lot of the tough decisions that make writing a language syntax hard. Natural language programming also isn't much easier for nonprogrammers to use as the exactness of the format is the challenging aspect (see applescript for instance).
You can abuse method_missing. The tricky thing is, that you cannot access the blocks local variables directly. You'll have to capture the blocks inner binding somewhere (unfortunately block.binding returns the block's outer binding).
You can run this code:
DSL.new do
a = [1, 2, 3]
b = 2
a contains b
end
With the following:
class DSL
attr_reader :last_binding
def initialize(&block)
set_trace_func method(:trace).to_proc
instance_eval(&block)
set_trace_func nil
end
def trace(event, file, line, id, binding, klass)
if event.to_s == "call" and klass == self.class and id.to_s == "method_missing"
#last_binding ||= #current_binding
set_trace_func nil
else
#current_binding = binding
end
end
def lvars
eval('local_variables', last_binding).map(&:to_s)
end
def method_missing(name, *args)
name = name.to_s
if lvars.include? name
eval(name, last_binding).send(*args.flatten)
else
["#{name}?", *args]
end
end
end
class Array
alias contains? include?
end
The closest thing I could think of would be:
def contains var, useless_symbol, arr
arr.include? var
end
Then you could call it like:
contains b, :in, a
I don't think there is any way to be able to use infix notation in your own functions.
I have an object Results that contains an array of result objects along with some cached statistics about the objects in the array. I'd like the Results object to be able to behave like an array. My first cut at this was to add methods like this
def <<(val)
#result_array << val
end
This feels very c-like and I know Ruby has better way.
I'd also like to be able to do this
Results.each do |result|
result.do_stuff
end
but am not sure what the each method is really doing under the hood.
Currently I simply return the underlying array via a method and call each on it which doesn't seem like the most-elegant solution.
Any help would be appreciated.
For the general case of implementing array-like methods, yes, you have to implement them yourself. Vava's answer shows one example of this. In the case you gave, though, what you really want to do is delegate the task of handling each (and maybe some other methods) to the contained array, and that can be automated.
require 'forwardable'
class Results
include Enumerable
extend Forwardable
def_delegators :#result_array, :each, :<<
end
This class will get all of Array's Enumerable behavior as well as the Array << operator and it will all go through the inner array.
Note, that when you switch your code from Array inheritance to this trick, your << methods would start to return not the object intself, like real Array's << did -- this can cost you declaring another variable everytime you use <<.
each just goes through array and call given block with each element, that is simple. Since inside the class you are using array as well, you can just redirect your each method to one from array, that is fast and easy to read/maintain.
class Result
include Enumerable
def initialize
#results_array = []
end
def <<(val)
#results_array << val
end
def each(&block)
#results_array.each(&block)
end
end
r = Result.new
r << 1
r << 2
r.each { |v|
p v
}
#print:
# 1
# 2
Note that I have mixed in Enumerable. That will give you a bunch of array methods like all?, map, etc. for free.
BTW with Ruby you can forget about inheritance. You don't need interface inheritance because duck-typing doesn't really care about actual type, and you don't need code inheritance because mixins are just better for that sort of things.
Your << method is perfectly fine and very Ruby like.
To make a class act like an array, without actually inheriting directly from Array, you can mix-in the Enumerable module and add a few methods.
Here's an example (including Chuck's excellent suggestion to use Forwardable):
# You have to require forwardable to use it
require "forwardable"
class MyArray
include Enumerable
extend Forwardable
def initialize
#values = []
end
# Map some of the common array methods to our internal array
def_delegators :#values, :<<, :[], :[]=, :last
# I want a custom method "add" available for adding values to our internal array
def_delegator :#values, :<<, :add
# You don't need to specify the block variable, yield knows to use a block if passed one
def each
# "each" is the base method called by all the iterators so you only have to define it
#values.each do |value|
# change or manipulate the values in your value array inside this block
yield value
end
end
end
m = MyArray.new
m << "fudge"
m << "icecream"
m.add("cake")
# Notice I didn't create an each_with_index method but since
# I included Enumerable it knows how and uses the proper data.
m.each_with_index{|value, index| puts "m[#{index}] = #{value}"}
puts "What about some nice cabbage?"
m[0] = "cabbage"
puts "m[0] = #{m[0]}"
puts "No! I meant in addition to fudge"
m[0] = "fudge"
m << "cabbage"
puts "m.first = #{m.first}"
puts "m.last = #{m.last}"
Which outputs:
m[0] = fudge
m[1] = icecream
m[2] = cake
What about some nice cabbage?
m[0] = cabbage
No! I meant in addition to fudge
m.first = fudge
m.last = cabbage
This feels very c-like and I know Ruby
has better way.
If you want an object to 'feel' like an array, than overriding << is a good idea and very 'Ruby'-ish.
but am not sure what the each method
is really doing under the hood.
The each method for Array just loops through all the elements (using a for loop, I think). If you want to add your own each method (which is also very 'Ruby'-ish), you could do something like this:
def each
0.upto(#result_array.length - 1) do |x|
yield #result_array[x]
end
end
If you create a class Results that inherit from Array, you will inherit all the functionality.
You can then supplement the methods that need change by redefining them, and you can call super for the old functionality.
For example:
class Results < Array
# Additional functionality
def best
find {|result| result.is_really_good? }
end
# Array functionality that needs change
def compact
delete(ininteresting_result)
super
end
end
Alternatively, you can use the builtin library forwardable. This is particularly useful if you can't inherit from Array because you need to inherit from another class:
require 'forwardable'
class Results
extend Forwardable
def_delegator :#result_array, :<<, :each, :concat # etc...
def best
#result_array.find {|result| result.is_really_good? }
end
# Array functionality that needs change
def compact
#result_array.delete(ininteresting_result)
#result_array.compact
self
end
end
In both of these forms, you can use it as you want:
r = Results.new
r << some_result
r.each do |result|
# ...
end
r.compact
puts "Best result: #{r.best}"
Not sure I'm adding anything new, but decided to show a very short code that I wish I could have found in the answers to quickly show available options. Here it is without the enumerator that #shelvacu talks about.
class Test
def initialize
#data = [1,2,3,4,5,6,7,8,9,0,11,12,12,13,14,15,16,172,28,38]
end
# approach 1
def each_y
#data.each{ |x| yield(x) }
end
#approach 2
def each_b(&block)
#data.each(&block)
end
end
Lets check performance:
require 'benchmark'
test = Test.new
n=1000*1000*100
Benchmark.bm do |b|
b.report { 1000000.times{ test.each_y{|x| #foo=x} } }
b.report { 1000000.times{ test.each_b{|x| #foo=x} } }
end
Here's the result:
user system total real
1.660000 0.000000 1.660000 ( 1.669462)
1.830000 0.000000 1.830000 ( 1.831754)
This means yield is marginally faster than &block what we already know btw.
UPDATE: This is IMO the best way to create an each method which also takes care of returning an enumerator
class Test
def each
if block_given?
#data.each{|x| yield(x)}
else
return #data.each
end
end
end
If you really do want to make your own #each method, and assuming you don't want to forward, you should return an Enumerator if no block is given
class MyArrayLikeClass
include Enumerable
def each(&block)
return enum_for(__method__) if block.nil?
#arr.each do |ob|
block.call(ob)
end
end
end
This will return an Enumerable object if no block is given, allowing Enumerable method chaining