If I were to grab a field out of a database that has a built in wrapper, I'll receive the data as an array of objects, with each object having a series of instances that can be called upon. So for example:
class DataWrapper
attr_reader :foo, :bar
end
And the data is returned like [#data1, #data2, #data3, etc] where #data[1-3] are all instances of DataWrapper.
So what if I receive that data before I know what context it's going to be used in, and how I want to format it. What is the best (non-Rails) way to format the data given specific contexts, such as
case :xml
#data1.to_xml.foo #foo = 4
case :web_table
#data1.to_web_table.foo #foo = "four"
It sounds like you have an array of objects of BaseClass, and you want to later mutate the instances to be some sub-class of BaseClass. You cannot do this. What you can do instead are create modules for each "subclass" representation, and mix them into the the instances on demand.
If you have an array of objects and you want to modify the instances to mix in a particular module:
array.each{ |o| o.extend(MyModule) }
For example:
Nib = Struct.new :val do
def to_s
value # Must be implemented by instance/subclass
end
end
module Nib::Precise
def value; "%.1f" % val; end
end
module Nib::Rough
def value; val.round.to_s; end
end
module Nib::Ballpark
def value; ((val/10).round * 10).to_s; end
end
nibs = [ Nib.new(33.7), Nib.new(29.1) ]
nibs.each{ |n| n.extend(Nib::Precise) }
p nibs.join(", ") #=> "33.7, 29.1"
nibs.each{ |n| n.extend(Nib::Rough) }
p nibs.join(", ") #=> "34, 29"
nibs.each{ |n| n.extend(Nib::Ballpark) }
p nibs.join(", ") #=> "30, 30"
The "formatting" methods should probably be last. Eg
case :xml
#data1.foo.to_xml #foo = 4
case :web_table
#data1.foo.to_web_table #foo = "four"
....
You'll need to mixin the formatting methods to the base classes or to Object.
The formatting methods can examine the class of the parent class and decide how to convert the value.
Related
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.
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")
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
class A
attr_accessor :dab
....
end
Now I have an array of instances of A, say
arr = [A.new, A.new, A.new]
And now I want to set a value to all instances of class A present in the array arr. Is there a shortcut in ruby/rails to do this?
In addition, I do have A inherited from ActiveRecord::Base
And my actual need is:
A.find_all_by_some_condition.all.dabs = 2
So, all found objects will have dab set to 2.
Is there shortcut for this?
To get the items of class A from an array you can use select/find_all
arr.select { |el| el.class == A } or arr.select { |el| A === el }
To achieve your actual result though you are looking to assign a value to several objects, not their corresponding class. class A does not define the actual objects it just defines the blueprint that the objects use when getting created. So finding a way to assign a value of all instances of A is not what you are after (although I might have missed the point of what you were asking for)
To assign a value to an array of object this works:
A.find_all_by_some_condition.each { |a| a.dab = 2 }
Perhaps you want to save them after that, now arr.each(&:save) might come in handy. Go look up the ampersand if you don't know it already. Very useful.
You can't do that directly by default, however you could build something like that using Ruby's method_missing.
Two solutions:
Solution 1 - Use a wrapper class
We'll call this class MArray for multi-assign-array.
class MArray
def initialize(inner_array)
#inner = inner_array
end
def method_missing(meth, value)
# Check if assignement, and if it is then run mass-assign
if meth.to_s =~ /^\w+=$/
#inner.each { |itm| itm.send(meth, value) }
else
raise ArgumentError, "MArray: not an assignment"
end
end
end
We also need to add support for MArray in Array, so that the wrapping will take place. We'll call the method mas for "mass-assignment":
class Array
def mas
# Wrap MArray around self
MArray.new(self)
end
end
Usage is simple:
Blob = Struct.new(:dab)
arr = [Blob.new] * 3
arr.mas.dab = 123
arr
=> [#<struct Blob dab=123>, #<struct Blob dab=123>, #<struct Blob dab=123>]
Solution 2 - Create mass-assignment support directly into Array
This is a bit more "dangerous" since we directly modify method_missing in Array. It could create some strange side-effects (for example if method_missing has already been redefined by some other library, or you accidentally call a mass-assign while you didn't mean to).
It works by trying to detect assignments with plural words (words ending with s), and then triggering the mass-assignment:
class Array
def method_missing(meth, *args, &block)
# Check for plural assignment, and as an added safety check also
# see if all elements in the array support the assignment:
if meth.to_s =~ /^(\w+)s=$/ &&
self.all? { |itm| itm.respond_to?("#{$1}=") }
self.each { |itm| itm.send("#{$1}=", *args) }
else
super
end
end
end
Usage then becomes even shorter than with MArray:
Blob = Struct.new(:dab)
arr = [Blob.new] * 3
arr.dabs = 123
arr
=> [#<struct Blob dab=123>, #<struct Blob dab=123>, #<struct Blob dab=123>]
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