Defining a function to act upon an array - ruby

In Ruby, we define class member functions like this
class Dog
def bark
puts "woof"
end
end
What I want to know and have been thoroughly unsuccessful in googling is, how and where does one define methods to act upon object-arrays in Ruby?
I wish to be able to do something like this:
dogs = [Dog.new, Dog.new, Dog.new]
dogs.some_function
How and where do I define some_function?
Note: I am not after solutions to a specific problem, more so the steps in how to define such a function in general.

To make all your dogs bark, you should use each on the array:
dogs.each(&:bark)
which is equivalent to
dogs.each { |dog| dog.bark }
Very rarely you need to define a method on an array, in which case it will be available on all arrays, containing anything. To do that, you need to declare it inside the Array class, by declaring it again:
class Array
def some_function
# do something...
end
end
And then you could run:
dogs = [Dog.new, Dog.new, Dog.new]
dogs.some_function
and also
numbers = [1, 2, 3, 4]
numbers.some_function

You could also create a class that inherits from Array such as
class DogWalker < Array
def some_function
self.each(&:bark)
end
end
class Dog
def bark
puts "woof"
end
end
d = DogWalker.new([Dog.new,Dog.new,Dog.new])
d.some_function
#woof
#woof
#woof
#=> [#<Dog:0x2a5a2e8>, #<Dog:0x2a5a2d0>, #<Dog:0x2a5a2b8>]
This means that you can only call this method on an instance of DogWalker (as well as all the methods available to Array) but does not alter Array itself. Giving you better control of the objects included in it. e.g.
class DogWalker < Array
def initialize(args)
raise ArgumentError.new("Must be an Array of Dogs") unless args.is_a?(Array) && args.all?{|e| e.is_a?(Dog)}
super
end
end
d = DogWalker.new([Dog.new,Dog.new,Dog.new])
#=>[#<Dog:0x2a5a2e8>, #<Dog:0x2a5a2d0>, #<Dog:0x2a5a2b8>]
d = DogWaler.new([Dog.new,12])
#=>ArgumentError: Must be an Array of Dogs
Array.new([1,2,3])
#=>[1,2,3]
As #UriAgassi pointed out very rarely do you need to alter base classes especially if you are designing extended functionality that does not need to/cannot apply across object types. Here is an example:
class Array
def some_function
self.map(&:bark)
end
end
class Dog
def bark
"woof"
end
end
d = [Dog.new,Dog.new]
d.some_function
#=> ["woof","woof"]
d = [1,2,3,4]
d.some_function
#=>NoMethodError: undefined method `bark' for 1:Fixnum
Since Array can accept any object type the Array is fine but the some_function method requires an object that responds_to bark which will raise in the event the object cannot perform the task requested.

In your example 'dogs' is the instance of Array class.
If you want to extend Array class you can add method to it like this:
class Array
def some_function
end
end

Related

Strange output from class statement

I am trying to place 3 values into each index of an array. The values are for the class, assignment, and grade. They come from a class object called Grade_Log.
I am running into an issue with the output.
class Grade_Log
attr_accessor :which_class, :assignment_type, :grade
def initialize(which_class, assignment_type, grade)
#which_class = which_class
#assignment_type = assignment_type
#grade = grade
end
end
#values are assigned to add_class, add_assignment, and add_grade
grade_index[grade_index_tally] = Grade_Log.new(add_class,add_assignment,add_grade)
puts grade_index[grade_index_tally]
I would like for it to output, for example, "PHYSICS, HOMEWORK, 95", but instead I am getting the following. #<Grade_Log:0x0000000002baaa20>
If you want to be able to puts an instance of your Grade_Log class, you'll need to define to_s for your class. Right now, it's likely using the default to_s method.
I think you'd want something like this:
class Grade_Log
... # your code
def to_s
"#{which_class}, #{assignment_type}, #{grade}"
end
end
Which is basically the same thing as:
def to_s
[which_class, assignment_type, grade].join(', ')
end
When you instantiate a Grade_Log object, the initialize method is called and the parameters are assigned to attributes:
tmp = GradeLog.new("PHYSICS", "HOMEWORK", 95)
If you simply puts the variable you get the object information:
puts tmp #=> #<Grade_Log:0x007fc27213f3a8>
To access the attributes you should do
puts tmp.which_class #=> PHYSICS
puts tmp.assignment_type #=> HOMEWORK
puts tmp.grade #=> 95
A fast way to see the object content is the inspect method:
puts tmp.inspect #=> #<Grade_Log:0x007fd7e6917358 #which_class="PHYSICS", #assignment_type="HOMEWORK", #grade=95>
which is the same as using Kernel#p:
p tmp #=> #<Grade_Log:0x007fd7e6917358 #which_class="PHYSICS", #assignment_type="HOMEWORK", #grade=95>
Another option is to access the instance variables, Object.html#instance_variables and Object.html#instance_variable_get:
tmp.instance_variables.map { |v| tmp.instance_variable_get(v) } #=> ["PHYSICS", "HOMEWORK", 95]
Or define a custom method inside your class, to get the array of values, for example (no need to edit the method when a new attribute is added):
def attributes_values
instance_variables.map { |v| instance_variable_get(v) }
end
So you can call just
tmp.attributes_values #=> ["PHYSICS", "HOMEWORK", 95]
Finally, if you want format the output as a string you could define a to_s method to override the default Object#to_s method, to call puts tmp.

Creating custom methods

What is best practice / syntax for trying to extract internal methods within a class?
class Foo
def initialize
end
def get_value
array = (API CALL TO GET ARRAY)
array.array_lookup("Bar")
end
def array_lookup(query)
self.each do |hash|
if hash[:key] == query
p hash[:value]
end
end
end
end
foo = Foo.new
foo.get_value #=> : undefined method `array_lookup' for #<Array:0x007fd3a49a2ca0 (NoMethodError)
The error message is telling me that my array object doesn't know how to respond to my method which makes sense in that I have an array that doesn't have this method, though I'm wondering how to fix this and similar uses. Do I overwrite the array class? Do I change my self.syntax?
array_lookup is Foo's method. So inside Foo class, you can call it by
array_lookup("Bar")
(without array.)
How about something like this? You turn your custom object into a subclass of Array so you get the array methods like #each. Actually, come to think of it, a better implementation might include mixing in the Enumerable module into your custom class (thinking composition over inheritance).
class Foo < Array
# More robust to change in application if you allow passing
# the query into this method. Just a suggestion.
def get_value(query)
request_data
lookup(query)
end
protected
def request_data
# API call to get data, assume this is array with contents
data = []
# Set contents of this object to contents of returned array
replace(data)
end
def lookup(query)
each do |hash|
if hash[:key] == query
puts hash[:value]
end
end
end
end
foo = Foo.new
foo.get_value("BAR")

Where do methods defined at the top-level exist?

I am learning some metaprogramming and I was stuck trying to find a method. Let's say I have the following class:
class MyClass
def self.my_method
end
def your_method
end
end
With the following code I can search for each method in the object space:
type = Class
name = /^my_method$/
result = ObjectSpace.each_object(type).select do |o|
o.instance_methods(false).grep(name).size > 0 || o.methods.grep(name).size > 0
end
p result
And it finds it showing the following output:
[MyClass]
As the searcher code also searches for instance methods, it shows the same output when looking for your_method.
Even with if I add a singleton method to an object:
mc = MyClass.new
def mc.our_method
end
Just changing the searcher like this:
type = Object
name = /^our_method$/
result = ObjectSpace.each_object(type).select do |o|
o.methods.grep(name).size > 0
end
p result
It also finds it:
[#<MyClass:0x8f86760>]
The question is, how do I find a method defined in the top level object? This method:
def hidden
end
Besides, which is the current class when defining a method like this?
Which is the current class when defining a method like this?
We can easily figure out what object we’re in by inspecting self in this top level scope:
self #=> main
self.class #=> Object
So we’re not in a Class, but an instance of Object which is dubbed “main”.
How do I find a method defined in the top level object?
This is where it gets interesting. The top-level scope object in Ruby behaves specially, but it’s relatively easy to discover where a method here defined lives:
def foo; :bar; end
method(:foo).owner #=> Object
Object.new.foo #=> NoMethodError: private method `foo' called
Object.new.send(:foo) #=> :bar
So methods defined at the top-level are made (private*) instance methods of Object. The reason your ”searcher” cannot find it is because these methods are private, and neither methods nor instance_methods include private methods, instead you need private_methods and private_instance_methods:
Object.instance_methods.include?(:foo) #=> false
Object.private_instance_methods.include?(:foo) #=> true
* Note that Pry (at least v0.10.1) alters this to make methods defined at top-level in its REPL public.
If you had this:
def my_method() end
class A
def self.my_method() end
end
class B < A
def my_method() end
end
class C
def my_method() end
end
and wanted to find methods named 'my_method' that you've created, you could do this:
ObjectSpace.each_object(Class).select do |o|
o.instance_methods(false).include?(:my_method)
end
#=> [C, B]
ObjectSpace.each_object(Class).select do |o|
o.methods(false).include?(:my_method)
end
#=> [A]
ObjectSpace.each_object(Class).select do |o|
o.private_instance_methods(false).include?(:my_method)
end
#=> [Object]

Accessing the name of an anonymous class in superclass' self.inherited

I would like to access a class' name in its superclass MySuperclass' self.inherited method. It works fine for concrete classes as defined by class Foo < MySuperclass; end but it fails when using anonymous classes. I tend to avoid creating (class-)constants in tests; I would like it to work with anonymous classes.
Given the following code:
class MySuperclass
def self.inherited(subclass)
super
# work with subclass' name
end
end
klass = Class.new(MySuperclass) do
def self.name
'FooBar'
end
end
klass#name will still be nil when MySuperclass.inherited is called as that will be before Class.new yields to its block and defines its methods.
I understand a class gets its name when it's assigned to a constant, but is there a way to set Class#name "early" without creating a constant?
I prepared a more verbose code example with failing tests to illustrate what's expected.
Probably #yield has taken place after the ::inherited is called, I saw the similar behaviour with class definition. However, you can avoid it by using ::klass singleton method instead of ::inherited callback.
def self.klass
#klass ||= (self.name || self.to_s).gsub(/Builder\z/, '')
end
I am trying to understand the benefit of being able to refer to an anonymous class by a name you have assigned to it after it has been created. I thought I might be able to move the conversation along by providing some code that you could look at and then tell us what you'd like to do differently:
class MySuperclass
def self.inherited(subclass)
# Create a class method for the subclass
subclass.instance_eval do
def sub_class() puts "sub_class here" end
end
# Create an instance method for the subclass
subclass.class_eval do
def sub_instance() puts "sub_instance here" end
end
end
end
klass = Class.new(MySuperclass) do
def self.name=(name)
#name = Object.const_set(name, self)
end
def self.name
#name
end
end
klass.sub_class #=> "sub_class here"
klass.new.sub_instance #=> "sub_instance here"
klass.name = 'Fido' #=> "Fido"
kn = klass.name #=> Fido
kn.sub_class #=> "sub_class here"
kn.new.sub_instance #=> "sub_instance here"
klass.name = 'Woof' #=> "Woof"
kn = klass.name #=> Fido (cannot change)
There is no way in pure Ruby to set a class name without assigning it to a constant.
If you're using MRI and want to write yourself a very small C extension, it would look something like this:
VALUE
force_class_name (VALUE klass, VALUE symbol_name)
{
rb_name_class(klass, SYM2ID(symbol_name));
return klass;
}
void
Init_my_extension ()
{
rb_define_method(rb_cClass, "force_class_name", force_class_name, 1);
}
This is a very heavy approach to the problem. Even if it works it won't be guaranteed to work across various versions of ruby, since it relies on the non-API C function rb_name_class. I'm also not sure what the behavior will be once Ruby gets around to running its own class-naming hooks afterward.
The code snippet for your use case would look like this:
require 'my_extension'
class MySuperclass
def self.inherited(subclass)
super
subclass.force_class_name(:FooBar)
# work with subclass' name
end
end

How do I add 'each' method to Ruby object (or should I extend Array)?

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

Resources