Breaking reopened class - ruby

In the example below I am reopening the Module class and setting instance variables. Can this possibly "break" the class if it already uses those instance variables for something else and how can this be avoided?
class Module
def fields
#fields ||= []
end
def foo name
fields << name
end
end
So far I haven't run into any issues with something similar to the above. However my next example shows how this could be a problem.
class Foo
def bar
#test = 1
end
def print
puts #test
end
end
class Foo
def oops
#test = 2
end
end
obj = Foo.new
obj.bar
obj.print #=> 1
# method that we added later sets instance variable
obj.oops #=> 2
obj.print
This worries me.

Can this possibly "break" the class if it already uses those instance
variables for something else and how can this be avoided?
1.9.3-p545 :003 > Module.new.instance_variables
=> []
1.9.3-p545 :004 > Module.new.instance_variable_defined? "#fields" => false

Here is an example what will it change and what won't
module Helper
#fields = '#fields defined in Helper'
def self.print_helper_fields
p #fields
end
def print
p #fields
end
end
class A
include Helper
attr_accessor :fields
end
class Module
def foo
#fields = '#fields changed in Module#foo'
end
end
a = A.new
a.fields = [1, 2, 3]
a.print # => [1, 2, 3]
Helper.print_helper_fields # => '#fields defined in Helper'
Helper.foo
a.print # => [1, 2, 3]
Helper.print_helper_fields # => '#fields changed in Module#foo'

Related

Extend return value of class instance method

I have a class that have an instance method, that returns a hash. I can't change the code of that class directly, but I can extend it with modules. I need to add some new keys to the returning hash of the method. Something like this:
class Processor
def process
{ a: 1 }
end
end
module ProcessorCustom
def process
super.merge(b: 2) # Not works :(
end
end
Processor.send :include, ProcessorCustom
processor = Processor.new
processor.process # returns { a: 1 }, not { a: 1, b: 2 }
How can I do that? Thanks.
You could call prepend instead of include:
Processor.prepend(ProcessorCustom)
processor = Processor.new
processor.process
#=> {:a=>1, :b=>2}
prepend and include result in different ancestor order:
module A; end
module B; end
module C; end
B.ancestors #=> [B]
B.include(C)
B.ancestors #=> [B, C]
B.prepend(A)
B.ancestors #=> [A, B, C]
Alternatives
Depending on your use-case, you could also extend a specific instance: (this doesn't affect other instances)
processor = Processor.new
processor.extend(ProcessorCustom)
processor.process
#=> {:a=>1, :b=>2}
Or use SimpleDelegator to implement a decorator pattern:
require 'delegate'
class ProcessorCustom < SimpleDelegator
def process
super.merge(b: 2)
end
end
processor = ProcessorCustom.new(Processor.new)
processor.process #=> {:a=>1, :b=>2}
I think the first option to consider would be the one requiring the least work by the reader to comprehend; and in Object Oriented software that would be a subclass to specialize the behavior of the superclass. I would deviate from this if, and only if, there were a compelling reason to do so.
How about this?:
#!/usr/bin/env ruby
class Processor
def foo
{ x: 3 }
end
end
class MyProcessor < Processor
def foo
super.merge({ y: 7 })
end
end
p MyProcessor.new.foo # outputs: {:x=>3, :y=>7}
I think it's better to create a proxy than to pollute the original class.
class Proxy
def initialize(target)
#target = target
end
# add some syntactic sugar
singleton_class.class_eval { alias [] new }
def process
#target.process.merge!(b: 2)
end
end
Proxy[Processor.new].process #=> {a: 1, b: 2}
You can even create your own dynamic proxy.
class DynamicProxy < BasicObject
def initialize(target)
#target = target
end
# again, add some syntactic sugar
singleton_class.class_eval { alias [] new }
def method_missing(name, *args, &block)
super unless #target.respond_to?(name)
# Do something before calling the target method
result = #target.send(name, *args, &block)
# Do something after calling the target method
end
def respond_to_missing?(name, include_private = false)
#target.respond_to?(name, include_private)
end
end
To hide the detail of creating Processor instance, you can make a simple factory to handle the creation.
module ProcessorFactory
def self.create
DynamicProxy[Processor.new]
end
end
Then you can do your job
ProcessorFactory.create.process

Delegate attribute methods to the parent object

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.

How to Make a Ruby Class act Like a Hash with Setter

For academic reasons, I'd like to make an instance of Ruby class act like a hash.
GOALS
Initialize MyClass instance with a hash # success
Request values from instance of myClass, like a hash # success
Then set properties as a hash # fail
Although some discussion exists, I tried what's out there (1, 2) with no success. Let me know what I'm doing wrong. Thanks!
class MyClass
attr_accessor :my_hash
def initialize(hash={})
#my_hash = hash
end
def [](key)
my_hash[key]
end
def set_prop(key, value)
myhash[key] = value
end
end
test = myClass.new({:a => 3}) #=> #<MyClass:0x007f96ca943898 #my_hash={:a=>3}>
test[:a] #=> 3
test[:b] = 4 #=> NameError: undefined local variable or method `myhash' for #<MyClass:0x007f96ca9d0ef0 #my_hash={:a=>3}>
You declared set_prop, but you're using []= in tests. Did you mean to get this?
class MyClass
attr_accessor :my_hash
def initialize(hash={})
#my_hash = hash
end
def [](key)
my_hash[key]
end
def []=(key, value)
my_hash[key] = value
end
end
test = MyClass.new({:a => 3}) # success
test[:a] # success
test[:b] = 4 # success
test.my_hash # => {:a=>3, :b=>4}
module HashizeModel
def [](key)
sym_key = to_sym_key(key)
self.instance_variable_get(sym_key)
end
def []=(key, value)
sym_key = to_sym_key(key)
self.instance_variable_set(sym_key, value)
end
private
def to_sym_key(key)
if key.is_a? Symbol
return ('#'+key.to_s).to_sym
else
return ('#'+key.to_s.delete('#')).to_sym
end
end
end
You should write it as test = MyClass.new({:a => 3}) and the below code should work.
class MyClass
attr_accessor :my_hash
def initialize(hash={})
#my_hash = hash
end
def [](key)
#my_hash[key]
end
def []=(key,val)
#my_hash[key]=val
end
def set_prop(key, value)
#myhash[key] = value
end
end
test = MyClass.new({:a => 3})
test[:a]
test[:b]= 4
test.my_hash # => {:a=>3, :b=>4}

Method overriding in Ruby

I have code that looks like this:
module A
def b(a)
a+1
end
end
class B
include A
end
I would like to write a method in the class B that looks sort of like this
class B
def b(a)
if a==2 # are you sure? same result as the old method
3
else
A.b(a)
end
end
end
How do I go about doing this in Ruby?
You want the super function, which invokes the 'previous' definition of the function:
module A
def b(a)
p 'A.b'
end
end
class B
include A
def b(a)
if a == 2
p 'B.b'
else
super(a) # Or even just `super`
end
end
end
b = B.new
b.b(2) # "B.b"
b.b(5) # "A.b"
class B
alias old_b b # one way to memorize the old method b , using super below is also possible
def b(a)
if a==2
'3' # returning a string here, so you can see that it works
else
old_b(a) # or call super here
end
end
end
ruby-1.9.2-p0 > x = B.new
=> #<B:0x00000001029c88>
ruby-1.9.2-p0 >
ruby-1.9.2-p0 > x.b(1)
=> 2
ruby-1.9.2-p0 > x.b(2)
=> "3"
ruby-1.9.2-p0 > x.b(3)
=> 4

Class returning an array

I have a class like:
class Configuration
def self.files
##files ||= Array.new
end
end
Now instead of doing this:
irb(main):001:0> Configuration.files
=> [file, file, file]
I would like to be able to do this:
irb(main):001:0> Configuration
=> [file, file, file]
But I can't figure out how, any ideas?
#glenn jackman:
I was thinking of adding extra methods to a 'Configuration' constant that's a hash. So if I had...
Configuration = Hash.new
Configuration[:foo] = 'bar'
I wanted to be able to save this Configuration hash constant to be able to dump to and load from a YAML file I wanted to be able to use...
Configuration.load
Configuration.save
I wanted the Configuration class to look like
class Configuration
def self.save
open('config.yml', 'w') {|f| YAML.dump( self , f)}
end
def self.load
open('config.yml') {|f| YAML.load(f)}
end
end
You could possibly get the effect you are looking for by adding a method to the top level object which implicitly calls Configuration.files, but you can't really make a reference to a class invoke a method on it. You can alias the method to something shorter, but you will need to call something.
irb's top level just calls 'inspect' on the result, so by overriding it you can customize what you see in irb:
$ irb
>> class Configuration
>> def self.files
>> ##files ||= Array.new
>> end
>> def self.inspect
>> ##files.inspect
>> end
>> end
=> nil
>> Configuration.files << 1 << 2 << 3
=> [1, 2, 3]
>> Configuration
=> [1, 2, 3]
You could do something like this:
class Configuration
(class << self; self; end).module_eval do
def files
['foo','bar','baz']
end
def to_s
files
end
end
end
This would define the Configuration.files method and say that the to string conversion would return the result of that method. But I am really not sure why you would want to do this. It seems quite wrong.
wpc#wpc-laptop:~$ irb
irb(main):001:0> 'a'
=> a
irb(main):002:0> def A
irb(main):003:1> end
=> nil
irb(main):004:0> A
NameError: uninitialized constant A
from (irb):4
from :0
irb(main):005:0> class A
irb(main):006:1> end
=> nil
irb(main):007:0> A
=> A
irb(main):008:0> class A
irb(main):009:1> (class ['g']
irb(main):012:3> end
irb(main):013:2> def to_s
irb(main):014:3> g
irb(main):015:3> end
irb(main):016:2> end
irb(main):017:1> end
=> nil
irb(main):018:0> A
=> g
irb(main):019:0> class B
irb(main):020:1> class def to_s
irb(main):022:3> 'g'
irb(main):023:3> end
irb(main):024:2> end
irb(main):025:1> end
=> nil
irb(main):026:0> B
=> g
irb(main):027:0> class B
irb(main):028:1> class def files
irb(main):030:3> ['a','b','c']
irb(main):031:3> end
irb(main):032:2> def to_s
irb(main):033:3> files
irb(main):034:3> end
irb(main):035:2> end
irb(main):036:1> end
=> nil
irb(main):037:0> B
=> abc
The right solution is to using the class' method of to_s, but what it return is only the string; and then you can set the inspect for using. See the follows' detail for help.
class A
class<<self
def to_s
......
end
end
end

Resources