I have a class that wraps other instances to provide additional functionality (a presenter) and want to now have subclasses that provide the same functionality. So it would be something like this:
class BasePresenter
attr_accessor :base_object
def initialize(base_object)
self.base_object = base_object
end
end
class WalrusPresenter < BasePresenter
end
And I want to be able to do this:
BasePresenter(:bubbles)
#=> <# BasePresenter #base_object=:bubbles >
WalrusPresenter(:frank)
#=> <# WalrusPresenter #base_object=:frank >
Update
I think the functional differences are outside of the scope of the question, but they seem to be a sticking point so I'll add them.
I am not trying to delegate .new.
BasePresenter.new takes a single argument and wraps it in a presenter. BasePresenter() takes an object and:
If it is already a presenter, return it
If it is an array of objects, map over them and create a new presenter
If it is a single object, wrap it in a presenter and return it.
This is closer to ActiveSupport's Array#wrap, but I think the parenthetical syntax is rather communicative of its functionality so I'd like to use that if possible. The inheritance part is what is tripping me up; defining it once one the base class so it is useable on all children.
BasePresenter(:bubbles)
#=> <# BasePresenter #base_object=:bubbles >
BasePresenter([:waldorf, :statler])
#=> [ <# BasePresenter #base_object=:waldorf >, <# BasePresenter #base_object=:staler> ]
WalrusPresenter.new(:frank)
#=> <# WalrusPresenter #base_object=:frank >
WalrusPresenter([:harold, :richard])
#=> [ <# WalrusPresenter #base_object=:harold >, <# WalrusPresenter #base_object=:richard > ]
WalrusPresenter(WalrusPresenter(WalrusPresenter(:frank)))
#=> <# WalrusPresenter #base_object=:frank >
I might be missing the point of your question, but to me it looks like you just forgot to use new to create instances of your classes:
BasePresenter.new(:bubbles)
# => #<BasePresenter:0x00000001ae33b8 #base_object=:bubbles>
WalrusPresenter.new(:frank)
# => #<WalrusPresenter:0x00000001ae7878 #base_object=:frank>
Update: Mischa has already responded to your comment in about the same way I would. Kernel#Array tries calling to_ary on its argument, then tries calling to_a on it if that fails, then creates an Array with the argument as its sole element if that fails too.
It's not clear what behavior you want; it seems like you just want to get around using new, which of course you can do, but it would be silly:
def BasePresenter(base)
BasePresenter.new(base)
end
def WalrusPresenter(base)
WalrusPresenter.new(base)
end
You could do some metaprogramming to avoid repeating yourself when creating the wrappers. But I fail to see the reason you want to avoid new, and without a very good reason to do so, I'd have to recommend against it. Ruby uses new to instantiate objects.
Update 2: Thanks for clarifying what you're looking for. Here's the first thing that came to mind for me. You can definitely clean it up a bit, but something like this should get you started. (wrap doesn't need to be in BasePresenter at all.) Anyway, here you go:
class BasePresenter
attr_accessor :base_object
def initialize(base_object)
self.base_object = base_object
end
def self.wrap klass
Object.class_eval do
define_method klass.to_s do |object|
case object
when BasePresenter
object
when Array
object.map{|base| klass.new(base)}
else
klass.new(object)
end
end
end
end
BasePresenter.wrap BasePresenter
def self.inherited child_klass
BasePresenter.wrap child_klass
end
end
class WalrusPresenter < BasePresenter
end
This seems to do what you want:
BasePresenter(:bubbles)
# => #<BasePresenter:0x00000001db05a0 #base_object=:bubbles>
BasePresenter([:waldorf, :statler])
# => [#<BasePresenter:0x00000001da7c98 #base_object=:waldorf>,
#<BasePresenter:0x00000001da7c70 #base_object=:statler>]
WalrusPresenter.new(:frank)
# => #<WalrusPresenter:0x00000001da4070 #base_object=:frank>
WalrusPresenter([:harold, :richard])
# => [#<WalrusPresenter:0x00000001d773e0 #base_object=:harold>,
#<WalrusPresenter:0x00000001d773b8 #base_object=:richard>]
WalrusPresenter(WalrusPresenter(WalrusPresenter(:frank)))
# => #<WalrusPresenter:0x00000001d6c760 #base_object=:frank>
First, I don't think what you're trying to do is super wise - there are probably better ways to accomplish what you want. But if you really want to create a new operator against a class by adding parenthesis handling, the code below does the job I think..
I'm copying the gnarly class handling code from this great blog post by CodeErr.
Here is my solution for your problem using his code:
class Object
alias_method :orig_method_missing, :method_missing
def method_missing(m, *a, &b)
klass = begin
(self.is_a?(Module) ? self : self.class).const_get(m)
rescue NameError
end
return klass.send(:parens, *a, &b) if klass.respond_to? :parens
orig_method_missing m, *a, &b
end
end
class X
##class_arg = {}
def self.class_arg
##class_arg[self.name]
end
def self.parens(*args)
##class_arg[self.name] = args[0]
Class.new(self).class_eval do
define_method :initialize do
super(*args)
end
self
end
end
end
class Y < X
end
class Z < X
end
Z(:bubbles)
Y(:frank)
puts Z.class_arg # "bubbles"
puts Y.class_arg # "frank"
My solution:
class BasePresenter
attr_accessor :base_object
def initialize(base_object)
self.base_object = base_object
end
Object.class_eval do
def BasePresenter base_object
BasePresenter.new base_object
end
end
def self.inherited child_klass
Object.class_eval do
define_method child_klass.to_s.to_sym do |base_object|
child_klass.new base_object
end
end
end
end
class WalrusPresenter < BasePresenter
end
class Test < WalrusPresenter; end
puts BasePresenter(:bubbles)
puts WalrusPresenter(:frank)
puts Test(:test)
Related
I'm trying to wrap my head around delegation vs. inheritance so I'm manually delegating a version of Array. One of the specific reasons I read to do this is because when you use things like enumerables, your returned value on the inherited methods reverts back to the parent class (i.e. Array). So I did this:
module PeepData
# A list of Peeps
class Peeps
include Enumerable
def initialize(list = [])
#list = list
end
def [](index)
#list[index]
end
def each(...)
#list.each(...)
end
def reverse
Peeps.new(#list.reverse)
end
def last
#list.last
end
def join(...)
#list.join(...)
end
def from_csv(csv_table)
#list = []
csv_table.each { |row| #list << Peep.new(row.to_h) }
end
def include(field, value)
Peeps.new(select { |row| row[field] == value })
end
def exclude(field, value)
Peeps.new(select { |row| row[field] != value })
end
def count_by_field(field)
result = {}
#list.each do |row|
result[row[field]] = result[row[field]].to_i + 1
end
result
end
protected
attr_reader :list
end
end
When I instantiate this, my include and exclude function great and return a Peeps class but when using an enumerable like select, it returns Array, which prevents me from chaining further Peeps specific methods after the select. This is exactly what I'm trying to avoid with learning about delegation.
p = Peeps.new
p.from_csv(csv_generated_array_of_hashes)
p.select(&:certified?).class
returns Array
If I override select, wrapping it in Peeps.new(), I get a "SystemStackError: stack level too deep". It seems to be recursively burying the list deeper into the list during the select enumeration.
def select(...)
Peeps.new(#list.select(...))
end
Any help and THANKS!
I would recommend using both Forwardable and Enumerable. Use Forwardable to delegate the each method to your list (to satisfy the Enumerable interface requirement), and also forward any Array methods you might want to include that are not part of the Enumerable module, such as size. I would also suggest not overriding the behavior of select as it is supposed to return an array and would at the very least lead to confusion. I would suggest something like the subset provided below to implement the behavior you are looking for.
require 'forwardable'
class Peeps
include Enumerable
extend Forwardable
def_delegators :#list, :each, :size
def initialize(list = [])
#list = list
end
def subset(&block)
selected = #list.select(&block)
Peeps.new(selected)
end
protected
attr_reader :list
end
Example usage:
peeps = Peeps.new([:a,:b,:c])
subset = peeps.subset {|s| s != :b}
puts subset.class
peeps.each do |peep|
puts peep
end
puts peeps.size
puts subset.size
produces:
Peeps
a
b
c
3
2
I think that if Peeps#select will return an Array, then it is OK to include Enumerable. But, you want Peeps#select to return a Peeps. I don't think you should include Enumerable. It's misleading to claim to be an Enumerable if you don't conform to its interface. This is just my opinion. There is no clear consensus on this in the ecosystem. See "Examples from the ecosystem" below.
If we accept that we cannot include Enumerable, here's the first implementation that comes to my mind.
require 'minitest/autorun'
class Peeps
ARRAY_METHODS = %i[flat_map map reject select]
ELEMENT_METHODS = %i[first include? last]
def initialize(list)
#list = list
end
def inspect
#list.join(', ')
end
def method_missing(mth, *args, &block)
if ARRAY_METHODS.include?(mth)
self.class.new(#list.send(mth, *args, &block))
elsif ELEMENT_METHODS.include?(mth)
#list.send(mth, *args, &block)
else
super
end
end
end
class PeepsTest < Minitest::Test
def test_first
assert_equal('alice', Peeps.new(%w[alice bob charlie]).first)
end
def test_include?
assert Peeps.new(%w[alice bob charlie]).include?('bob')
end
def test_select
peeps = Peeps.new(%w[alice bob charlie]).select { |i| i < 'c' }
assert_instance_of(Peeps, peeps)
assert_equal('alice, bob', peeps.inspect)
end
end
I don't normally use method_missing, but it seemed convenient.
Examples from the ecosystem
There doesn't seem to be a consensus on how strictly to follow interfaces.
ActionController::Parameters used to inherit Hash. Inheritance ceased in Rails 5.1.
ActiveSupport::HashWithIndifferentAccess still inherits Hash.
As mentioned in the other answer, this isn't really proper usage of Enumerable. That said, you could still include Enumerable and use some meta-programming to override the methods that you want to be peep-chainable:
module PeepData
class Peeps
include Enumerable
PEEP_CHAINABLES = [:map, :select]
PEEP_CHAINABLES.each do |method_name|
define_method(method_name) do |&block|
self.class.new(super(&block))
end
end
# solution for select without meta-programming looks like this:
# def select
# Peeps.new(super)
# end
end
end
Just so you know, this really has nothing to do with inheritance vs delegation. If Peeps extended Array, you would have the exact same issue, and the exact solution above would still work.
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
Some code that I had that used attr_accessor_with_default in a rails model is now giving me a deprecation warning, telling me to "Use Ruby instead!"
So, thinking that maybe there was a new bit in ruby 1.9.2 that made attr_accessor handle defaults, I googled it, but I don't see that. I did see a bunch of methods to override attr_accessor to handle defaults though.
Is that what they mean when they tell me to "Use Ruby?" Or am I supposed to write full getters/setters now? Or is there some new way I can't find?
This apidock page suggests to just do it in the initialize method.
class Something
attr_accessor :pancakes
def initialize
#pancakes = true
super
end
end
Don't forget to call super especially when using ActiveRecord or similar.
attr_accessor :pancakes
def after_initialize
return unless new_record?
self.pancakes = 11
end
This ensures that the value is initialized to some default for new record only.
Since you probably know your data quite well, it can be quite acceptable to assume nil is not a valid value.
This means you can do away with an after_initialize, as this will be executed for every object you create. As several people have pointed out, this is (potentially) disastrous for performance. Also, inlining the method as in the example is deprecated in Rails 3.1 anyway.
To 'use Ruby instead' I would take this approach:
attr_writer :pancakes
def pancakes
return 12 if #pancakes.nil?
#pancakes
end
So trim down the Ruby magic just a little bit and write your own getter. After all this does exactly what you are trying to accomplish, and it's nice and simple enough for anyone to wrap his/her head around.
This is an ooooold question, but the general problem still crops up - and I found myself here.
The other answers are varied and interesting, but I found problems with all of them when initializing arrays (especially as I wanted to be able to use them at a class level before initialize was called on the instance). I had success with:
attr_writer :pancakes
def pancakes
#pancakes ||= []
end
If you use = instead of ||= you will find that the << operator fails for adding the first element to the array. (An anonymous array is created, a value is assigned to it, but it's never assigned back to #pancakes.)
For example:
obj.pancakes
#=> []
obj.pancakes << 'foo'
#=> ['foo']
obj.pancakes
#=> []
#???#!%$##%FRAK!!!
As this is quite a subtle problem and could cause a few head scratches, I thought it was worth mentioning here.
This pattern will need to be altered for a bool, for example if you want to default to false:
attr_writer :pancakes
def pancakes
#pancakes.nil? ? #pancakes = false : #pancakes
end
Although you could argue that the assignment isn't strictly necessary when dealing with a bool.
There's nothing magical in 1.9.2 for initializing instance variables that you set up with attr_accessor. But there is the after_initialize callback:
The after_initialize callback will be called whenever an Active Record object is instantiated, either by directly using new or when a record is loaded from the database. It can be useful to avoid the need to directly override your Active Record initialize method.
So:
attr_accessor :pancakes
after_initialize :init
protected
def init
#pancakes = 11
end
This is safer than something like this:
def pancakes
#pancakes ||= 11
end
because nil or false might be perfectly valid values after initialization and assuming that they're not can cause some interesting bugs.
I'm wondering if just using Rails implementation would work for you:
http://apidock.com/rails/Module/attr_accessor_with_default
def attr_accessor_with_default(sym, default = nil, &block)
raise 'Default value or block required' unless !default.nil? || block
define_method(sym, block_given? ? block : Proc.new { default })
module_eval( def #{sym}=(value) # def age=(value) class << self; attr_reader :#{sym} end # class << self; attr_reader :age end ##{sym} = value # #age = value end # end, __FILE__, __LINE__ + 1)
end
You can specify default values for instances of any class (not only ActiveRecords) after applying patch to Module:
class Zaloop
attr_accessor var1: :default_value, var2: 2
def initialize
self.initialize_default_values
end
end
puts Zaloop.new.var1 # :default_value
Patch for module:
Module.module_eval do
alias _original_attr_accessor attr_accessor
def attr_accessor(*args)
attr_names = extract_default_values args
_original_attr_accessor *attr_names
end
alias _original_attr_reader attr_reader
def attr_reader(*args)
attr_names = extract_default_values args
_original_attr_reader *attr_names
end
def extract_default_values(args)
#default_values ||= {}
attr_names = []
args.map do |arg|
if arg.is_a? Hash
arg.each do |key, value|
define_default_initializer if #default_values.empty?
#default_values[key] = value
attr_names << key
end
else
attr_names << arg
end
end
attr_names
end
def define_default_initializer
default_values = #default_values
self.send :define_method, :initialize_default_values do
default_values.each do |key, value|
instance_variable_set("##{key}".to_sym, value)
end
end
end
def initialize_default_values
# Helper for autocomplete and syntax highlighters
end
end
Suppose I've got a class:
class MyClass
def my_method
# cool stuff
end
alias :my_method2 :method
end
And now I want to get all the aliases for method my_method without comparison with all the object methods.
I'm not sure how to do it without using comparisons. However, if you remove Object.methods you can limit the comparisons made:
def aliased?(x)
(methods - Object.methods).each do |m|
next if m.to_s == x.to_s
return true if method(m.to_sym) == method(x.to_sym)
end
false
end
A bit of a hack, but seems to work in 1.9.2 (but does not in 1.8 etc.):
def is_alias obj, meth
obj.method(meth).inspect =~ /#<Method:\s+(\w+)#(.+)>/
$2 != meth.to_s
end
MyClass.instance_methods(false) will give you just the instance methods defined for your class. This is a good way to get rid of any ancestor methods. In your case, the only methods that should show up are my_method and my_method2.
I am having a bit trouble to understand when "super" can be called and when not. In the below example the super method leads to a no superclass error.
class Bacterium
def eats
puts "Nam"
end
end
class Bacterium
def eats
super # -> no superclass error
puts "Yam"
end
end
b = Bacterium.new
b.eats
But this works:
class Fixnum
def times
super # -> works
puts "done"
end
end
5.times { |i| puts i.to_s }
Is 5 not just also an instance of Fixnum. And am I not redefining an existing method like in the Bacterium example above?
No, not really. Fixnum inherits from Integer class, and you are in fact overriding Integer#times, so super works, as it calls implementation from the parent.
In order to achieve something similar when monkeypatching, you should alias method before redefining it, and there call it by alias.
class Bacterium
alias_method :eats_original, :eats
def eats
eats_original # -> "Nam"
puts "Yam"
end
end
Class reopening is not a form of inheritance and super is of no use there.
Just as Mladen said, and you can check that with Class#superclass:
irb> Fixnum.superclass
=> Integer
And does Integer implement #times?:
irb> Integer.instance_methods.grep /times/
=> [:times]
Yes it does.
So, in a simplified way, we can say, that super invokes the method you are in of a superclass. In your case the superclass of a Bacterium is Object, which doesn't implement #eats.
I said this is very simplified, because look at this example:
module One
def hi
" World" << super()
end
end
module Two
def hi
"Hello" << super()
end
end
class SayHi
def hi
"!!!"
end
end
h = SayHi.new
h.extend(One)
h.extend(Two)
puts h.hi
#=> Hello World!!
Don't take to serious what I wrote here, it is actually tip of the iceberg of the Ruby object model, which is important to understand (I am still learning it) - then you will get most, or all of those concepts.
Use some Google-fu for "Ruby object model"...