Always working with Rails but now that I am coding completely without framework I am missing a lot of methods that I am not able to write myself. So I need a little hint here.
So I've got this code
class Foo
initialize(k)
(1..k).each do |b|
Boo.new(b)
end
end
class Boo
initialize(a)
#a = a
end
end
Foo.new(10)
#and now I want to access one of the Boo's with a certain a (in Rails Boo.find_by_a(2) e.g.)
How do I write a method that uses a certain value "a" to get the specific Boo back with #a = a?
Update:
Okay I added this methot to Boo
def self.all
ObjectSpace.each_object(self).to_a
end
And now I get the right object by:
boo = Boo.all.select{ |b| b.a == 2 }.first
But if I want this to be a method, where would i put it?
DO NOT RELY ON ObjectSpace for this functionality when the GC collects you will lose these objects because Ruby sees them as unneeded.
This is the general concept of what you are intending to do
class Foo
attr_reader :boos
def initialize(k)
#boos = (1..k).map {|b| Boo.new(b) }
end
def find_boo_with(x)
#boos.find {|b| b.a == x }
end
end
class Boo
attr_reader :a
def initialize(a)
#a = a
end
end
Then you can do this
f = Foo.new(10)
f.boos
#=> [#<Boo:0x256f428 #a=1>,
#<Boo:0x256f3b0 #a=2>,
#<Boo:0x256f368 #a=3>,
#<Boo:0x256f338 #a=4>,
#<Boo:0x256f2f0 #a=5>,
#<Boo:0x256f2d8 #a=6>,
#<Boo:0x256f2a8 #a=7>,
#<Boo:0x256f290 #a=8>,
#<Boo:0x256f278 #a=9>,
#<Boo:0x256f260 #a=10>]
f.find_boo_with(9)
#=> #<Boo:0x256f278 #a=9>
I hope this helps but you really should look into something like RubyMonk, or TryRuby to get a better understanding of how PORO (Plain Old Ruby Objects) work.
You could also do it this way if you really wanted your find methodology just know that no Boo in this case will ever be collected as they will all be stored in a class instance variable called #all_boos which could casue memory issues depending on what a Boo is and how many of them you are creating.
class Boo
class << self
def all_boos
#all_boos ||= []
end
def find(x)
#all_boos.find {|boo| boo.a == x }
end
alias_method :all, :all_boos
end
attr_reader :a
def initialize(a)
#a = a
self.class.all_boos << self
end
end
Then you can do
10.times {|i| Boo.new(i) }
Boo.all
#=> [#<Boo:0x256f428 #a=1>,
#<Boo:0x256f3b0 #a=2>,
#<Boo:0x256f368 #a=3>,
#<Boo:0x256f338 #a=4>,
#<Boo:0x256f2f0 #a=5>,
#<Boo:0x256f2d8 #a=6>,
#<Boo:0x256f2a8 #a=7>,
#<Boo:0x256f290 #a=8>,
#<Boo:0x256f278 #a=9>,
#<Boo:0x256f260 #a=10>]
Boo.find(9)
#=> #<Boo:0x256f278 #a=9>
If I did understand what you need then I think that you can use ObjectSpace to achieve this. This is the sample code that illustrates the use:
class Foo
def initialize(k)
(1..k).each do |b|
Boo.new(b)
end
end
end
class Boo
attr_reader :a
def initialize(a)
#a = a
end
def self.find(value)
ObjectSpace.each_object(Boo).select { |b| b.a == value }.first
end
end
Foo.new(10)
Boo.find(2).a
Let me know if this is the solution you were looking for.
Not sure I 100% understand your question but using attr_reader in Boo will allow you to access a as if it were a function.
class Foo
def initialize(k)
(1..k).each do |b|
Boo.new(b)
end
end
end
class Boo
attr_reader :a
def initialize(a)
#a = a
end
end
Foo.new(10)
As for the other part of your question, here is a good article explaining how dynamic finders work
Related
I want to know if a method on a class gets redefined.
use case: in version 1 of ruby-clock, defining a method on an object was part of the API. In version 2, doing so will break behavior, and a different API should be used.
what I ended up doing: https://github.com/jjb/ruby-clock/pull/28/files
The best would be if I could use some sort of metaprogramming to notice when it happens.:
# this is example code which does not work
class MyClass
def my_method
puts "hello"
end
# this is not real! it's an example of what i hope is somehow possible
def self.method_defined(m, *args, &block)
if :my_method == m
raise "you should not redefine my_method!"
end
end
end
Also acceptable would be if I could check back later and see if it has changed.
# this is example code which does not work
class MyClass
def my_method
puts "hello"
end
##my_method_object_id = get_method_object_id(:my_method)
end
# later, when app initialization is done, check if the user redefined the method
# this is not real! it's an example of what i hope is somehow possible
def self.method_defined(m, *args, &block)
if MyClass.get_method_object_id(:my_method) != MyClass.my_method_object_id
raise "you should not redefine my_method!"
end
end
n.b. that MyClass.method(:my_method) is a real thing in ruby, but it always produces a new object.
This sounds pretty much like XY problem. I don't think you should go this way - if you find yourself fighting with the Ruby object model, you most probably picked the wrong tool for the job.
But for the sake of completeness... There are a couple of callbacks in Module that can help with the ideas as crazy as this one. In particular, there is Module#method_added which is called each time some instance method is defined. So we can do smth. like this (very dirty example):
class C
#foo_counter = 0
def self.method_added(name)
if name == :foo
#foo_counter += 1
if #foo_counter > 1
raise "Don't redefine foo!"
end
end
end
def foo
"foo"
end
end
now if one tries to open C and redefine foo the following happens (pry session as an example):
pry(main)> class C
pry(main)* def foo
pry(main)* "bar"
pry(main)* end
pry(main)* end
RuntimeError: Don't redefine foo!
So you can do what you want to (to some extent - it's Ruby, so what stops one from redefining the annoying hook first? :)), but please don't. You'll get nothing but troubles...
You could use the callback BasicObject#singleton_method_added.
class C
#class_methods = []
def self.singleton_method_added(m)
puts "Method #{m} was just redefined" if #class_methods.include?(m)
#class_methods << m
end
def self.c
puts 'hi'
end
end
C.c
#=> "hi"
class C
def self.c
puts 'ho'
end
end
Method c was just redefined
C.c #=> "ho"
I am working on a project of context-oriented programming in ruby. And I come to this problem:
Suppose that I have a class Klass:
class Klass
def my_method
proceed
end
end
I also have a proc stored inside a variable impl. And impl contains { puts "it works!" }.
From somewhere outside Klass, I would like to define a method called proceed inside the method my_method. So that if a call Klass.new.my_method, I get the result "it works".
So the final result should be something like that:
class Klass
def my_method
def proceed
puts "it works!"
end
proceed
end
end
Or if you have any other idea to make the call of proceed inside my_method working, it's also good. But the proceed of another method (let's say my_method_2) isn't the same as my_method.
In fact, the proceed of my_method represent an old version of my_method. And the proceed of my_method_2 represent an old version of my_method_2.
Thanks for your help
Disclaimer: you are doing it wrong!
There must be more robust, elegant and rubyish way to achieve what you want. If you still want to abuse metaprogramming, here you go:
class Klass
def self.proceeds
#proceeds ||= {}
end
def def_proceed
self.class.proceeds[caller.first[/`.*?'/]] = Proc.new
end
def proceed *args
self.class.proceeds[caller.first[/`.*?'/]].(*args)
end
def m_1
def_proceed { puts 1 }
proceed
end
def m_2
def_proceed { puts 2 }
proceed
end
end
inst = Klass.new
inst.m_1
#⇒ 1
inst.m_2
#⇒ 2
What you in fact need, is Module#prepend and call super from there.
One way of doing that is to construct a hash whose keys are the names of the methods calling proceed and whose values are procs that represent the implementations of proceed for each method calling it.
class Klass
singleton_class.send(:attr_reader, :proceeds)
#proceeds = {}
def my_method1(*args)
proceed(__method__,*args)
end
def my_method2(*args)
proceed(__method__,*args)
end
def proceed(m, *args)
self.class.proceeds[m].call(*args)
end
end
def define_proceed(m, &block)
Klass.proceeds[m] = Proc.new &block
end
define_proceed(:my_method1) { |*arr| arr.sum }
define_proceed(:my_method2) { |a,b| "%s-%s" % [a,b] }
k = Klass.new
k.my_method1(1,2,3) #=> 6
k.my_method2("cat", "dog") #=> "cat-dog"
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?
When writing big classes with loads of instance variables, writing ==, eql? and hash methods is a big hassle. Is there a way to make a "template class" to automate this process? Or with any other ways.
Example:
class Template
def ==(other)
//Some way of comparing all of self's instance variables to other's.
end
def eql?(other)
self == other
end
def hash
//Some way of XORing the instance variables of self
end
end
class Test < Example
attr_reader :foo
attr_reader :bar
def initialize(foo, bar)
#foo = foo
#bar = bar
end
end
a = Test.new "123", "456"
b = Test.new "123", "456"
a == b
> true
Test = Struct.new(:foo, :bar)
a = Test.new "123", "456"
b = Test.new "123", "456"
a == b
# => true
You could define your fields so that you are able to reflect on them later on. Assuming all instance variables always exist and you want to use all of them in similar fashion, Ruby already provides you with enough reflection to pull it off, about this way
class MyFineClass
attr_reader :foo, :bar # and loads of stuff
def initialize(...)
# tons of those ivars initialized
end
def ==(other)
other.is_a?(self.class) &&
# this assumes all instance variables to have attr_readers
instance_variables.all? { |ivar| send(ivar) == other.send(ivar) }
# ...and if they don't, you need instance_variable_get, like this
#instance_variables.all? { |ivar| instance_variable_get(ivar) == other.instance_variable_get(ivar) }
end
end
In case you want more control over how the fields should be treated, you could add a notion of "field" and a little metaprogramming
class MyFineClass
#fields = []
def self.fields; #fields; end
def self.fields(field_name)
attr_reader field_name
#fields << field_name
end
field :foo
field :bar
# and loads more
def initialize(...)
# again, tons of those ivars initialized
end
def ==(other)
other.is_a?(self.class) && self.class.fields.all? { |f| send(f) == other.send(f) }
end
end
Next you would of course pull fields stuff and == to separate module and include that to MyFineClass. Got the idea? Develop that module a little further and it might start looking a little like certain bits in ActiveModel or DataMapper. :-)
Ruby Facets provides the Equitable module, which provides almost exactly what you're looking for.
Your example would become:
class Test
include Equitable(:foo, :bar)
end
If you don't want to use the whole gem, you can grab the source - it's pretty light.
Just for fun, again, but is it possible to take a block that contains method definitions and add those to an object, somehow? The following doesn't work (I never expected it to), but just so you get the idea of what I'm playing around with.
I do know that I can reopen a class with class << existing_object and add methods that way, but is there a way for code to pass that information in a block?
I guess I'm trying to borrow a little Java thinking here.
def new(cls)
obj = cls.new
class << obj
yield
end
obj
end
class Cat
def meow
puts "Meow"
end
end
cat = new(Cat) {
def purr
puts "Prrrr..."
end
}
cat.meow
# => Meow
# Not working
cat.purr
# => Prrrr...
EDIT | Here's the working version of the above, based on edgerunner's answer:
def new(cls, &block)
obj = cls.new
obj.instance_eval(&block)
obj
end
class Cat
def meow
puts "Meow"
end
end
cat = new(Cat) {
def purr
puts "Prrrr..."
end
}
cat.meow
# => Meow
cat.purr
# => Prrrr...
You can use class_eval(also aliased as module_eval) or instance_eval to evaluate a block in the context of a class/module or an object instance respectively.
class Cat
def meow
puts "Meow"
end
end
Cat.module_eval do
def purr
puts "Purr"
end
end
kitty = Cat.new
kitty.meow #=> Meow
kitty.purr #=> Purr
kitty.instance_eval do
def purr
puts "Purrrrrrrrrr!"
end
end
kitty.purr #=> Purrrrrrrrrr!
Yes
I suspect you thought of this and were looking for some other way, but just in case...
class A
def initialize
yield self
end
end
o = A.new do |o|
class << o
def purr
puts 'purr...'
end
end
end
o.purr
=> purr...
For the record, this isn't the usual way to dynamically add a method. Typically, a dynamic method starts life as a block itself, see, for example, *Module#define_method*.