I have this function:
def file_parser (filename)
Enumerator.new do |yielder|
File.open(filename, "r:ISO-8859-1") do |file|
csv = CSV.new(file, :col_sep => "\t", :headers => true, :quote_char => "\x07")
csv.each do |row|
yielder.yield map_fields(clean_data(row.to_hash))
end
end
end
end
I can use it like this:
parser = file_parser("data.tab")
parser.each do { |data| do_profitable_things_with data }
Instead, I'd like to put it in its own class and use it like this:
parser = SpecialParser.new("data.tab")
parser.each do { |data| do_profitable_things_with data }
I've tried some things I didn't expect to work, like just returning the enumerator out of initialize(), and self = file_parser().
I've also tried super do |yielder|.
For some reason, the way to do this is not coming to me.
You can just include the Enumerable module into your class, and define an each function which calls yield.
You still get all the Enumerable methods like map, reduce, etc, for free.
class SpecialParser
include Enumerable
def initialize(n)
#n = n
end
def each
0.upto(#n) { |i| yield i }
end
end
sp = SpecialParser.new 4
sp.each { |i| p i }
p sp.map { |i| i }
Output:
0
1
2
3
4
[0, 1, 2, 3, 4]
Make file_parser a private method in SpecialParser.
Then set up the rest of the class like this:
class SpecialParser
include Enumerable # needed to provide the other Enumerable methods
def initialize(filename)
#filename = filename
#enum = file_parser(filename)
end
def each
#enum.each do |val|
yield val
end
end
end
EDIT:
If you want the other Enumerable method for free, you also have to include Enumerable in the class.
Related
GOAL: Values of an OpenStruct object should be printed as a hash rather than an object
POSSIBLE SOLUTION: Override getter of the OpenStruct class
MyOpenStruct overrides new, to_h and [] of OpenStruct.
class MyOpenStruct < OpenStruct
def initialize(object=nil)
#table = {}
#hash_table = {}
if object
object.each do |k,v|
if v.is_a?(Array)
other = Array.new()
v.each { |e| other.push(self.class.new(entry)) }
v = other
end
#table[k.to_sym] = (v.is_a?(Hash) ? self.class.new(v) : v)
#hash_table[k.to_sym] = v
new_ostruct_member(k)
end
end
end
def [](val)
#hash_table[val.to_sym]
end
end
But overriding [] is not making any difference. E.g.
irb(main):007:0> temp = MyOpenStruct.new({"name"=>"first", "place"=>{"animal"=>"thing"}})
=> #<MyOpenStruct name="first", place=#<MyOpenStruct animal="thing">>
irb(main):008:0> temp.name
=> "first"
irb(main):009:0> temp.place
=> #<MyOpenStruct animal="thing">
irb(main):010:0> temp["place"]
=> {"animal"=>"thing"}
irb(main):011:0> temp[:place]
=> {"animal"=>"thing"}
irb(main):012:0> temp
=> #<MyOpenStruct name="first", place=#<MyOpenStruct animal="thing">>
Only when I access the keys using [] a hash is returned!!
How can I correct this??
I don't believe create a nested OpenStruct makes sense if you are returning it as a Hash. That's the way OpenStruct works:
require 'ostruct'
struct = OpenStruct.new(name: 'first', place: { animal: 'thing' })
struct.place
# => {:animal=>"thing"}
struct.place[:animal]
# => "thing"
struct.place.animal
# => NoMethodError: undefined method `animal' for {:animal=>"thing"}:Hash
So, if you want to use the dot notation to get struct.place.animal, you need to create nested OpenStruct objects like you did.
But, as I said, you don't need to override the [] method. Using your class without override the [] I get this:
struct = MyOpenStruct.new(name: 'first', place: { animal: 'thing' })
# => #<MyOpenStruct name="first", place=#<MyOpenStruct animal="thing">>
struct.place
# => #<MyOpenStruct animal="thing">
struct.place.animal
# => "thing"
Anyway, if you really want to make the dot notation work as you asked, you can override the new_ostruct_member method, which is used internally to create dynamic attributes when setting the OpenStruct object.
You can try something like this, but I don't recommend:
class MyOpenStruct < OpenStruct
def initialize(object=nil)
#table = {}
#hash_table = {}
if object
object.each do |k,v|
if v.is_a?(Array)
other = Array.new()
v.each { |e| other.push(self.class.new(entry)) }
v = other
end
#table[k.to_sym] = (v.is_a?(Hash) ? self.class.new(v) : v)
#hash_table[k.to_sym] = v
new_ostruct_member(k)
end
end
end
def [](val)
#hash_table[val.to_sym]
end
protected
def new_ostruct_member(name)
name = name.to_sym
unless respond_to?(name)
# use the overrided `[]` method, to return a hash
define_singleton_method(name) { self[name] }
define_singleton_method("#{name}=") { |x| modifiable[name] = x }
end
name
end
end
struct = MyOpenStruct.new(name: 'first', place: { animal: 'thing' })
# => #<MyOpenStruct name="first", place=#<MyOpenStruct animal="thing">>
struct.place
# => {:animal=>"thing"}
struct.place[:animal]
# => "thing"
#Doguita's answer is correct in all respects. I just wanted to answer your question "If that's not possible then can I print a hash of the whole object temp?" Yes, you can. You just need to override to_h to recursively walk your keys and values and convert instances of MyOpenStruct to a Hash:
def to_h
#table.each_with_object({}) do |(key, val), table|
table[key] = to_h_convert(val)
end
end
private
def to_h_convert(val)
case val
when self.class
val.to_h
when Hash
val.each_with_object({}) do |(key, val), hsh|
hsh[key] = to_h_convert(val)
end
when Array
val.map {|item| to_h_convert(item) }
else
val
end
end
Lets say that I have the class:
class Car
attr_accessor :brand, :color
def initialize(br, col)
#brand = br
#color = col
end
end
first_car = Car.new 'Mercedes', 'Yellow'
second_car = Car.new 'Jaguar', 'Orange'
third_car = Car.new 'Bentley', 'Pink'
array_of_cars = [first_car, second_car]
So, the idea is to define a method to_proc in the class Array so this can happen:
array_of_cars.map(&[:brand, :color]) #=> [['Mercedes', 'Yellow'], ['Jaguar', 'Orange'], ['Bentley', 'Pink']]
or how could I somehow do something like this:
[:brand, :color].to_proc.call(first_car) #=> ['Mercedes', 'Yellow']
The standard Symbol#to_proc is, more or less, a e.send(self) call and you just want to send an array full of symbols to each element so just say exactly that with something like this:
class Array
def to_proc
proc { |e| self.map { |m| e.send(m) } }
end
end
I'd probably not patch Array for this, I'd just use a local lambda if I wanted something easier to read:
brand_and_color = lambda { |e| [:brand, :color].map { |s| e.send(s) } }
array_of_cars.map(&brand_and_color)
class Array
def to_proc(arr)
self.map do |elem|
arr.map { |attr| elem.send(attr) }
end
end
end
Like mu is too short mentioned, I wouldn't patch Array like this.
But, this will get the work done for you.
Invoke it as such:
array_of_cars.to_proc([:brand, :color])
Maybe use a normal method?
module ConvertInstanceToHash
def to_proc(obj, keys)
hash = {}
obj.instance_variables.each {|var| hash[var.to_s.delete("#")] = obj.instance_variable_get(var)}
hash = hash.inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo}
hash.values_at(*keys).compact
end
end
irb(main):009:0> first_car = Car.new 'Mercedes', 'Yellow'
=> #<Car:0x000000045fb870 #brand="Mercedes", #color="Yellow">
irb(main):010:0> to_proc(first_car, [:brand, :color])
=> ["Mercedes", "Yellow"]
I have the following class:
class Alphabet
attr_reader :letter_freqs, :statistic_letter
def initialize(lang)
#lang = lang
case lang
when :en
#alphabet = ('A'..'Z').to_a
#letter_freqs = { ... }
when :ru
#alphabet = ('А'..'Я').to_a.insert(6, 'Ё')
#letter_freqs = { ... }
...
end
#statistic_letter = #letter_freqs.max_by { |k, v| v }[0]
end
end
foo = Alphabet.new(:en)
The central member here is #alphabet.
I'd like to make it some sort of a container class to invoke Array methods directly like
foo[i]
foo.include?
instead of explicitly accessing #alphabet:
foo.alphabet[i]
foo.alphabet.include?
I know I could define a lot of methods like
def [](i)
#alphabet[i]
end
but I'm looking for a proper way of "inheriting" them.
You can use Forwardable (it is included in the Ruby standard library):
require 'forwardable'
class Alphabet
extend Forwardable
def_delegators :#alphabet, :[], :include?
def initialize
#alphabet = ('A'..'Z').to_a
end
end
foo = Alphabet.new
p foo[0] #=> "A"
p foo.include? 'ç' #=> false
If you wish to delegate all the methods not defined by your class you can use SimpleDelegator (also in the standard library); it lets you delegate all the methods that are not responded by the instance to an object specified by __setobj__:
require 'delegate'
class Alphabet < SimpleDelegator
def initialize
#alphabet = ('A'..'Z').to_a
__setobj__(#alphabet)
end
def index
'This is not #alphabet.index'
end
end
foo = Alphabet.new
p foo[0] #=> "A"
p foo.include? 'ç' #=> false
p foo.index #=> "This is not #alphabet.index"
When the delegate doesn't need to be dynamic you can arrange the master class to be a subclass of DelegateClass, passing the name of the class to be delegated as argument and calling super passing the object to be delegated in the #initialize method of the master class:
class Alphabet < DelegateClass(Array)
def initialize
#alphabet = ('A'..'Z').to_a
super(#alphabet)
end
More info about the delegation design pattern in Ruby here
You could extend the Forwardable module:
class Alphabet
require 'forwardable'
extend Forwardable
attr_accessor :alphabet
def initialize
#alphabet = [1,2,3]
end
def_delegator :#alphabet, :[], :include?
end
Then you can do:
alpha = Alphabet.new
alpha[1]==hey.alphabet[1]
=> true
Warning:
Don't try to delegate all methods (don't know if that's even possible) since they probably share some of the same method names such as class, which would probably make chaos.
In Ruby you can extend Objects like this.
class Array
def second
self[1]
end
end
[1, 2, 3, 4, 5].second
# => 2
Or if you want to inherit an Array.
class Foo < Array
def second
self[1]
end
end
[1, 2, 3, 4, 5].include? 2
# => true
[1, 2, 3, 4, 5].second
# => 2
If you have any further questions, comment and I will update the answer.
In the following code, the issue is that after calling method .find_name on an object type of LogsCollection, the returned object becomes a native array and does not remain type LogsCollection. I believe the correct approach might be to create a constructor/initializer that accepts an array and return a brand new object of the correct type. But I am not sure there is not a better way to accomplish this?
Can a Ruby-pro eyeball this code and suggest (at the code level) the best way to make the returned object from .find_name remain type LogsCollection (not array)?
class Log
attr_accessor :name, :expense_id, :is_excluded, :amount, :paid_to
def initialize(name, expense_id, is_excluded, amount, paid_to)
#name = name
#expense_id = expense_id
#is_excluded = is_excluded
#amount = amount
#paid_to = paid_to
end
end
class LogsCollection < Array
def names
collect do |i|
i.name
end
end
def find_name(name)
#name = name
self.select { |l| l.name == #name }
end
end
logs = LogsCollection.new
logs.push(Log.new('Smith', 1, false, 323.95, nil))
logs.push(Log.new('Jones', 1, false, 1000, nil))
logs = logs.find_name('Smith')
puts logs.count
unless logs.empty?
puts logs.first.name # works since this is a standard function in native array
puts logs.names # TODO: figure out why this fails (we lost custom class methods--LogsCollection def find_name returns _native_ array, not type LogsCollection)
end
Final code post-answer for anyone searching (note the removal of base class < array):
class Log
attr_accessor :name, :expense_id, :is_excluded, :amount, :paid_to
def initialize(name, expense_id, is_excluded, amount, paid_to)
#name = name
#expense_id = expense_id
#is_excluded = is_excluded
#amount = amount
#paid_to = paid_to
end
end
class LogsCollection
attr_reader :logs
def initialize(logs)
#logs = logs
end
def add(log)
#logs.push(log)
end
def names
#logs.collect { |l| l.name }
end
def find_name(name)
LogsCollection.new(#logs.select { |l| l.name == name })
end
end
logs = LogsCollection.new([])
logs.add(Log.new('Smith', 1, false, 323.95, nil))
logs.add(Log.new('Jones', 1, false, 1000, nil))
puts logs.names
puts '--- post .find_name ---'
puts logs.find_name('Smith').names
As you can see in the docs Enumerable#select with a block always returns an array. E.g.
{:a => 1, :b => 2, :c => 3}.select { |k,v | v > 1 }
=> [[:b, 2], [:c, 3]]
What you could do is have some sort of constructor for LogsCollection that wraps up a normal array as a LogsCollection object and call that in find_name.
As requested here's an example class (I'm at work and writing this while waiting for something to finish, it's completely untested):
class LogsCollection
attr_reader :logs
def initialize(logs)
#logs = logs
end
def names
#logs.collect { |i| i.name }
end
def find_name(n)
name = n
LogsCollection.new(#logs.select { |l| l.name == n })
end
# if we don't know a method, forward it to the #logs array
def method_missing(m, *args, &block)
#logs.send(m, args, block)
end
end
Use like
lc = LogsCollection.new
logs = lc.logs.find_name('Smith')
i want to do the following:
I want to declare the instance variables of a class iterating over a dictionary.
Let's assume that i have this hash
hash = {"key1" => "value1","key2" => "value2","key3" => "value3"}
and i want to have each key as instance variable of a class. I want to know if i could declare the variables iterating over that hash. Something like this:
class MyClass
def initialize()
hash = {"key1" => "value1","key2" => "value2","key3" => "value3"}
hash.each do |k,v|
#k = v
end
end
end
I know this doesn't work! I only put this piece of code to see if you could understand what i want more clearly.
Thanks!
class MyClass
def initialize()
hash = {"key1" => "value1","key2" => "value2","key3" => "value3"}
hash.each do |k,v|
instance_variable_set("##{k}",v)
# if you want accessors:
eigenclass = class<<self; self; end
eigenclass.class_eval do
attr_accessor k
end
end
end
end
The eigenclass is a special class belonging just to a single object, so methods defined there will be instance methods of that object but not belong to other instances of the object's normal class.
class MyClass
def initialize
# define a hash and then
hash.each do |k,v|
# attr_accessor k # optional
instance_variable_set(:"##{k}", v)
end
end
end
Chuck's answer is better than my last two attempts. The eigenclass is not self.class like I had thought; it took a better test than I had written to realize this.
Using my old code, I tested in the following manner and found that the class was indeed manipulated and not the instance:
a = MyClass.new :my_attr => 3
b = MyClass.new :my_other_attr => 4
puts "Common methods between a & b:"
c = (a.public_methods | b.public_methods).select { |v| a.respond_to?(v) && b.respond_to?(v) && !Object.respond_to?(v) }
c.each { |v| puts " #{v}" }
The output was:
Common methods between a & b:
my_other_attr=
my_attr
my_attr=
my_other_attr
This clearly disproves my presupposition. My apologies Chuck, you were right all along.
Older answer:
attr_accessor only works when evaluated in a class definition, not the initialization of an instance. Therefore, the only method to directly do what you want is to use instance_eval with a string:
class MyClass
def initialize(params)
#hash = {"key1" => "value1","key2" => "value2","key3" => "value3"}
params.each do |k,v|
instance_variable_set("##{k}", v)
instance_eval %{
def #{k}
instance_variable_get("##{k}")
end
def #{k}= (new_val)
instance_variable_set("##{k}", new_val)
end
}
end
end
end
To test this try:
c = MyClass.new :my_var => 1
puts c.my_var
http://facets.rubyforge.org/apidoc/api/more/classes/OpenStructable.html
OpensStructable is a mixin module
which can provide OpenStruct behavior
to any class or object. OpenStructable
allows extention of data objects with
arbitrary attributes.