Given
class Bird
def self.bird_ancestors
ancestors.first(ancestors.find_index(Bird)+1)
end
end
class Duck < Bird
end
class FeatheredDuck < Duck
end
FeatheredDuck.bird_ancestors => [FeatheredDuck,Duck,Bird]
Duck.bird_ancestors => [Duck,Bird]
Bird.bird_ancestors => [Bird]
How can I reference the Bird within Bird without having it be explicit? I know self and __class__ doesnt work.
Inside a class method, self refers to the current class object:
class Bird
def self.foo
self
end
end
p Bird.foo # => "Bird"
This will do it:
class Bird
def self.bird_ancestors
ancestors.take_while { |c| c.respond_to? __method__ }
end
end
class Duck < Bird
end
class FeatheredDuck < Duck
end
FeatheredDuck.bird_ancestors #=> [FeatheredDuck, Duck, Bird]
Duck.bird_ancestors #=> [Duck, Bird]
Bird.bird_ancestors #=> [Bird]
select also works, but take_while (suggested by #Aditya) is better because it stops searching ancestors once false is returned from the block.
you can do something like this:
class Bird
def self.bird_ancestors
class_name = method(__method__).owner.to_s.gsub(/#<Class:|>/,'')
ancestors.first(ancestors.map{|x| x.to_s}.find_index(class_name)+1)
end
end
(__method__ is the current method)
Related
I am struggling a bit with subclassing and class variables. I was expecting the class variables set by a class method call of the subclass to be specific to that subclass, but I see them being set in the super class.
For example, if I run the following code:
class Obj
##can_say = []
def self.says word
##can_say.push(word)
end
def self.listens_to calling
define_method calling do
"I say #{##can_say}"
end
end
end
class Dog < Obj
says "woof"
says "bark bark"
listens_to "goodboy"
end
class Cat < Obj
says "meaow"
says "purr"
listens_to "lord"
end
cat = Cat.new
dog = Dog.new
puts("Dog: #{dog.goodboy}")
puts("Cat: #{cat.lord}")
I get:
ruby/test$ ruby test.rb
Dog: I say ["woof", "bark bark", "meaow", "purr"]
Cat: I say ["woof", "bark bark", "meaow", "purr"]
I was expecting:
ruby/test$ ruby test.rb
Dog: I say ["woof", "bark bark"]
Cat: I say ["meaow", "purr"]
Is there a way to achieve that?
I kind of found a way:
class Obj
class << self
attr_accessor :can_say
end
def self.says word
if #can_say.nil?
#can_say = Array.new
end
#can_say.push(word)
end
def self.listens_to calling
define_method calling do
"I say #{self.class.can_say}"
end
end
end
class Dog < Obj
says "woof"
says "bark bark"
listens_to "goodboy"
end
class Cat < Obj
says "meaow"
says "purr"
listens_to "lord"
end
cat = Cat.new
dog = Dog.new
puts("Dog: #{dog.goodboy}")
puts("Cat: #{cat.lord}")
Now if I can somehow get rid of:
if #can_say.nil?
#can_say = Array.new
end
How can I dynamically and easily insert code into the beginning of each method of a class and subclasses without actually inserting it manually? I want something like a macros.
class C1
def m1
#i_am = __method__
end
def m2
#i_am = __method__
end
end
This is one of the examples where I want to avoid repetition.
I initially misinterpreted the question (but have left my original answer after the horizontal line below). I believe the following may be what you are looking for.
class C1
[:m1, :m2].each do |m|
define_method(m) do |name|
#i_am = __method__
puts "I'm #{name} from method #{#i_am}"
end
end
end
C1.instance_methods(false)
#=> [:m1, :m2]
c1 = C1.new
#=> #<C1:0x007f94a10c0b60>
c1.m1 "Bob"
# I'm Bob from method m1
c1.m2 "Lucy"
# I'm Lucy from method m2
My original solution follows.
class C1
def add_code_to_beginning(meth)
meth = meth.to_sym
self.class.send(:alias_method, "old_#{meth}".to_sym, meth)
self.class.send(:define_method, meth) do
yield
send("old_#{meth}".to_sym)
end
end
end
Module#alias_method
and Module#define_method are private; hence the need to use send.
c = C1.new
#=> #<C1:0x007ff5e3023650>
C1.instance_methods(false)
#=> [:m1, :m2, :add_code_to_beginning]
c.add_code_to_beginning(:m1) do
puts "hiya"
end
C1.instance_methods(false)
#=> [:m1, :m2, :add_code_to_beginning, :old_m1]
c.m1
# hiya
#=> :m1
You can use a rails like class decorators to that. The piece of code below is rendering a method called before_action defined in the Base class of the ActiveRecord module. The Test class is inherited from the ActiveRecord. The define_method is used if we want to call something explicitly from the Base class.
module ActiveRecord
class Base
def self.before_action(name)
puts "#{name}"
puts "inside before_action of class Base"
define_method(name) do
puts "Base: rendering code from Base class"
end
end
end
end
class Test < ActiveRecord::Base
before_action :hola
def render()
puts "inside render of class Test"
end
end
test = Test.new
test.render
test.hola
It has the output
hola
inside before_action of class Base
inside render of class Test
Base: rendering code from Base class
So, before running the render method it runs the before_action method in the Base class. It can be applied to all other methods in the Test class. This is a way of representing macros in ruby.
Assuming that the function is the same, you could create a module and include it in your classes.
Example:
module MyModule
def test_method
puts "abc"
end
end
class MyClass
include MyModule
def my_method
puts "my method"
end
end
inst = MyClass.new
inst.test_method # => should print "abc"
inst.my_method # => should print "my method"
Sandi Metz says in SOLID OOPS concepts from GORUCO that presence of if..else blocks in Ruby can be considered to be a deviation from Open-Close Principle. What all methods can be used to avoid not-urgent if..else conditions? I tried the following code:
class Fun
def park(s=String.new)
puts s
end
def park(i=Fixnum.new)
i=i+2
end
end
and found out that function overloading does not work in Ruby. What are other methods through which the code can be made to obey OCP?
I could have simply gone for:
class Fun
def park(i)
i=i+2 if i.class==1.class
puts i if i.class=="asd".class
end
end
but this is in violation to OCP.
With your current example, and wanting to avoid type detection, I would use Ruby's capability to re-open classes to add functionality you need to Integer and String:
class Integer
def park
puts self + 2
end
end
class String
def park
puts self
end
end
This would work more cleanly when altering your own classes. But maybe it doesn't fit your conceptual model (it depends what Fun represents, and why it can take those two different classes in a single method).
An equivalent but keeping your Fun class might be:
class Fun
def park_fixnum i
puts i + 2
end
def park_string s
puts s
end
def park param
send("park_#{param.class.to_s.downcase}", param)
end
end
As an opinion, I am not sure you will gain much writing Ruby in this way. The principles you are learning may be good ones (I don't know), but applying them forcefully "against the grain" of the language may create less readable code, regardless of whether it meets a well-intentioned design.
So what I would probably do in practice is this:
class Fun
def park param
case param
when Integer
puts param + 2
when String
puts param
end
end
end
This does not meet your principles, but is idiomatic Ruby and slightly easier to read and maintain than an if block (where the conditions could be far more complex so take longer for a human to parse).
You could just create handled classes for Fun like so
class Fun
def park(obj)
#parker ||= Object.const_get("#{obj.class}Park").new(obj)
#parker.park
rescue NameError => e
raise ArgumentError, "expected String or Fixnum but recieved #{obj.class.name}"
end
end
class Park
def initialize(p)
#park = p
end
def park
#park
end
end
class FixnumPark < Park
def park
#park += 2
end
end
class StringPark < Park
end
Then things like this will work
f = Fun.new
f.park("string")
#=> "string"
f.instance_variable_get("#parker")
#=> #<StringPark:0x1e04b48 #park="string">
f = Fun.new
f.park(2)
#=> 4
f.instance_variable_get("#parker")
#=> #<FixnumPark:0x1e04b48 #park=4>
f.park(22)
#=> 6 because the instance is already loaded and 4 + 2 = 6
Fun.new.park(12.3)
#=> ArgumentError: expected String or Fixnum but received Float
You could do something like this:
class Parent
attr_reader :s
def initialize(s='')
#s = s
end
def park
puts s
end
end
class Child1 < Parent
attr_reader :x
def initialize(s, x)
super(s)
#x = x
end
def park
puts x
end
end
class Child2 < Parent
attr_reader :y
def initialize(s, y)
super(s)
#y = y
end
def park
puts y
end
end
objects = [
Parent.new('hello'),
Child1.new('goodbye', 1),
Child2.new('adios', 2),
]
objects.each do |obj|
obj.park
end
--output:--
hello
1
2
Or, maybe I overlooked one of your twists:
class Parent
attr_reader :x
def initialize(s='')
#x = s
end
def park
puts x
end
end
class Child1 < Parent
def initialize(x)
super
end
def park
x + 2
end
end
class Child2 < Parent
def initialize(x)
super
end
def park
x * 2
end
end
objects = [
Parent.new('hello'),
Child1.new(2),
Child2.new(100),
]
results = objects.map do |obj|
obj.park
end
p results
--output:--
hello
[nil, 4, 200]
And another example using blocks, which are like anonymous functions. You can pass in the desired behavior to park() as a function:
class Function
attr_reader :block
def initialize(&park)
#block = park
end
def park
raise "Not implemented"
end
end
class StringFunction < Function
def initialize(&park)
super
end
def park
block.call
end
end
class AdditionFunction < Function
def initialize(&park)
super
end
def park
block.call 1
end
end
class DogFunction < Function
class Dog
def bark
puts 'woof, woof'
end
end
def initialize(&park)
super
end
def park
block.call Dog.new
end
end
objects = [
StringFunction.new {puts 'hello'},
AdditionFunction.new {|i| i+2},
DogFunction.new {|dog| dog.bark},
]
results = objects.map do |obj|
obj.park
end
p results
--output:--
hello
woof, woof
[nil, 3, nil]
Look at the is_a? method
def park(i)
i.is_a?(Fixnum) ? (i + 2) : i
end
But even better not to check a type, but use duck typing:
def park(i)
i.respond_to?(:+) ? (i + 2) : i
end
UPD: After reading comments. Yes, both examples above don't solve the OCP problem. That is how I would do it:
class Fun
# The method doesn't know how to pluck data. But it knows a guy
# who knows the trick
def pluck(i)
return __pluck_string__(i) if i.is_a? String
__pluck_fixnum__(i) if i.is_a? Fixnum
end
private
# Every method is responsible for plucking data in some special way
# Only one cause of possible changes for each of them
def __pluck_string__(i)
puts i
end
def __pluck_fixnum__(i)
i + 2
end
end
I understand or equal to operation in ruby but can you explain what
you have done with:
Object.const_get("#{obj.class}Park").new(obj)
In ruby, something that starts with a capital letter is a constant. Here is a simpler example of how const_get() works:
class Dog
def bark
puts 'woof'
end
end
dog_class = Object.const_get("Dog")
dog_class.new.bark
--output:--
woof
Of course, you can also pass arguments to dog_class.new:
class Dog
attr_reader :name
def initialize(name)
#name = name
end
def bark
puts "#{name} says woof!"
end
end
dog_class = Object.const_get("Dog")
dog_class.new('Ralph').bark
--output:--
Ralph says woof!
And the following line is just a variation of the above:
Object.const_get("#{obj.class}Park").new(obj)
If obj = 'hello', the first portion:
Object.const_get("#{obj.class}Park")
is equivalent to:
Object.const_get("#{String}Park")
And when the String class object is interpolated into a string, it is simply converted to the string "String", giving you:
Object.const_get("StringPark")
And that line retrieves the StringPark class, giving you:
Object.const_get("StringPark")
|
V
StringPark
Then, adding the second portion of the original line gives you:
StringPark.new(obj)
And because obj = 'hello', that is equivalent to:
StringPark.new('hello')
Capice?
I came across some interesting Ruby code today.
class MeetingReminderJob < Struct.new(:user, :meeting)
def perform
send_reminder(user, meeting)
end
end
What is the purpose of the < Struct.new(:user, :meeting)?
Struct is a ruby class, it created a Class object that contains attributes and accessors, you don't need define a class explicitly. In the api ,you can find more details : http://www.ruby-doc.org/core-1.9.3/Struct.html.
In your case , it create a class that contains 2 attributes named "user" and "meeting", then class MeetingReminderJob inherits it.
Here's another example:
class Animal
def greet
puts "Hi. I'm an animal"
end
end
def get_class
return Animal
end
class Dog < get_class
def warn
puts "Woof."
end
end
Dog.new.greet
Dog.new.warn
--output:--
Hi. I'm an animal
Woof.
And another:
class Dog < Class.new { def greet; puts "Hi"; end }
def warn
puts "Woof."
end
end
Dog.new.greet
Dog.new.warn
--output:--
Hi
Woof.
For example:
class Animal
def make_noise
print NOISE
end
end
class Dog < Animal
NOISE = "bark"
end
d = Dog.new
d.make_noise # I want this to print "bark"
How do I accomplish the above? Currently it says
uninitialized constant Animal::NOISE
I think that you don't really want a constant; I think that you want an instance variable on the class:
class Animal
#noise = "whaargarble"
class << self
attr_accessor :noise
end
def make_noise
puts self.class.noise
end
end
class Dog < Animal
#noise = "bark"
end
a = Animal.new
d = Dog.new
a.make_noise #=> "whaargarble"
d.make_noise #=> "bark"
Dog.noise = "WOOF"
d.make_noise #=> "WOOF"
a.make_noise #=> "whaargarble"
However, if you are sure that you want a constant:
class Animal
def make_noise
puts self.class::NOISE
# or self.class.const_get(:NOISE)
end
end
one way to do it without class instance variables:
class Animal
def make_noise
print self.class::NOISE
end
end
class Dog < Animal
NOISE = "bark"
end
d = Dog.new
d.make_noise # prints bark
I think you have the wrong concept here. Classes in Ruby are similar to classes in Java, Smalltalk, C#, ... and all are templates for their instances. So the class defines the structure and the behavior if its instances, and the parts of the structure and behavior of the instances of its subclasses but not vice versae.
So direct access from a superclass to a constant in a subclass is not possible at all, and that is a good thing. See below how to fix it. For your classes defined, the following things are true:
class Animal defines the method make_noise.
instances of class Animal may call the method make_noise.
class Dogdefines the constant NOISE with its value.
instances of Dog and the class Dog itself may use the constant NOISE.
What is not possible:
Instances of Animal or the class Animal itself have access to constants of the class Dog.
You may fix that by the following change:
class Animal
def make_noise
print Dog::NOISE
end
end
But this is bad style, because now, your superclass (which is an abstraction about Dog and other animals) knows now something that belongs to Dog.
A better solution would be:
Define an abstract method in class Animal which defines that make_noise should be defined. See the answer https://stackoverflow.com/a/6792499/41540.
Define in your concrete classes the method again, but now with the reference to the constant.
If you're doing this to configure your sub classes so that the base class has access to the constants then you can create a DSL for them like this:
module KlassConfig
def attr_config(attribute)
define_singleton_method(attribute) do |*args|
method_name = "config_#{attribute}"
define_singleton_method method_name do
args.first
end
define_method method_name do
args.first
end
end
end
end
class Animal
extend KlassConfig
attr_config :noise
def make_noise
puts config_noise
end
end
class Dog < Animal
noise 'bark'
end
This way is a bit more performant as on each method call you don't have to introspect the class to reach back (or is it forward?) for the constant.
If you want it the Object-Oriented Way (TM), then I guess you want:
class Animal
# abstract animals cannot make a noise
end
class Dog < Animal
def make_noise
print "bark"
end
end
class Cat < Animal
def make_noise
print "meow"
end
end
d = Dog.new
d.make_noise # prints bark
c = Cat.new
c.make_noise # prints meow
If you want to refactor to prevent duplicating the code for print:
class Animal
def make_noise
print noise
end
end
class Dog < Animal
def noise
"bark"
end
end
class Cat < Animal
def noise
if friendly
"meow"
else
"hiss"
end
end
end
d = Dog.new
d.make_noise # prints bark
c = Cat.new
c.make_noise # prints meow or hiss