Forgiving the contrived example, if I have...
class Condiment
def ketchup(quantity)
puts "adding #{quantity} of ketchup!"
end
end
class OverpricedStadiumSnack
def add
Condiment.new
end
end
hotdog = OverpricedStadiumSnack.new
... is there anyway to get access to the hotdog instantiated object from within Condiment#ketchup when calling hotdog.add.ketchup('tons!')??
So far the only solution I've found is to pass hotdog in explicitly, like so:
class Condiment
def ketchup(quantity, snack)
puts "adding #{quantity} of ketchup to your #{snack.type}!"
end
end
class OverpricedStadiumSnack
attr_accessor :type
def add
Condiment.new
end
end
hotdog = OverpricedStadiumSnack.new
hotdog.type = 'hotdog'
# call with
hotdog.add.ketchup('tons!', hotdog)
... but I would love to be able to do this without passing hotdog explicitly.
May be:
class Condiment
def initialize(snack)
#snack = snack
end
def ketchup(quantity)
puts "adding #{quantity} of ketchup! to your #{#snack.type}"
end
end
class OverpricedStadiumSnack
attr_accessor :type
def add
Condiment.new(self)
end
end
hotdog = OverpricedStadiumSnack.new
hotdog.type = 'hotdog'
hotdog.add.ketchup(1)
Related
I'm working to create a few Ruby builder objects, and thinking on how I could reuse some of Ruby's magic to reduce the logic of the builder to a single class/module. It's been ~10 years since my last dance with the language, so a bit rusty.
For example, I have this builder:
class Person
PROPERTIES = [:name, :age]
attr_accessor(*PROPERTIES)
def initialize(**kwargs)
kwargs.each do |k, v|
self.send("#{k}=", v) if self.respond_to?(k)
end
end
def build
output = {}
PROPERTIES.each do |prop|
if self.respond_to?(prop) and !self.send(prop).nil?
value = self.send(prop)
# if value itself is a builder, evalute it
output[prop] = value.respond_to?(:build) ? value.build : value
end
end
output
end
def method_missing(m, *args, &block)
if m.to_s.start_with?("set_")
mm = m.to_s.gsub("set_", "")
if PROPERTIES.include?(mm.to_sym)
self.send("#{mm}=", *args)
return self
end
end
end
end
Which can be used like so:
Person.new(name: "Joe").set_age(30).build
# => {name: "Joe", age: 30}
I would like to be able to refactor everything to a class and/or module so that I could create multiple such builders that'll only need to define attributes and inherit or include the rest (and possibly extend each other).
class BuilderBase
# define all/most relevant methods here for initialization,
# builder attributes and object construction
end
module BuilderHelper
# possibly throw some of the methods here for better scope access
end
class Person < BuilderBase
include BuilderHelper
PROPERTIES = [:name, :age, :email, :address]
attr_accessor(*PROPERTIES)
end
# Person.new(name: "Joe").set_age(30).set_email("joe#mail.com").set_address("NYC").build
class Server < BuilderBase
include BuilderHelper
PROPERTIES = [:cpu, :memory, :disk_space]
attr_accessor(*PROPERTIES)
end
# Server.new.set_cpu("i9").set_memory("32GB").set_disk_space("1TB").build
I've been able to get this far:
class BuilderBase
def initialize(**kwargs)
kwargs.each do |k, v|
self.send("#{k}=", v) if self.respond_to?(k)
end
end
end
class Person < BuilderBase
PROPERTIES = [:name, :age]
attr_accessor(*PROPERTIES)
def build
...
end
def method_missing(m, *args, &block)
...
end
end
Trying to extract method_missing and build into the base class or a module keeps throwing an error at me saying something like:
NameError: uninitialized constant BuilderHelper::PROPERTIES
OR
NameError: uninitialized constant BuilderBase::PROPERTIES
Essentially the neither the parent class nor the mixin are able to access the child class' attributes. For the parent this makes sense, but not sure why the mixin can't read the values inside the class it was included into. This being Ruby I'm sure there's some magical way to do this that I have missed.
Help appreciated - thanks!
I reduced your sample to the required parts and came up with:
module Mixin
def say_mixin
puts "Mixin: Value defined in #{self.class::VALUE}"
end
end
class Parent
def say_parent
puts "Parent: Value defined in #{self.class::VALUE}"
end
end
class Child < Parent
include Mixin
VALUE = "CHILD"
end
child = Child.new
child.say_mixin
child.say_parent
This is how you could access a CONSTANT that lives in the child/including class from the parent/included class.
But I don't see why you want to have this whole Builder thing in the first place. Would an OpenStruct not work for your case?
Interesting question. As mentioned by #Pascal, an OpenStruct might already do what you're looking for.
Still, it might be more concise to explicitly define the setter methods. It might also be clearer to replace the PROPERTIES constants by methods calls. And since I'd expect a build method to return a complete object and not just a Hash, I renamed it to to_h:
class BuilderBase
def self.properties(*ps)
ps.each do |property|
attr_reader property
define_method :"set_#{property}" do |value|
instance_variable_set(:"##{property}", value)
#hash[property] = value
self
end
end
end
def initialize(**kwargs)
#hash = {}
kwargs.each do |k, v|
self.send("set_#{k}", v) if self.respond_to?(k)
end
end
def to_h
#hash
end
end
class Person < BuilderBase
properties :name, :age, :email, :address
end
p Person.new(name: "Joe").set_age(30).set_email("joe#mail.com").set_address("NYC").to_h
# {:name=>"Joe", :age=>30, :email=>"joe#mail.com", :address=>"NYC"}
class Server < BuilderBase
properties :cpu, :memory, :disk_space
end
p Server.new.set_cpu("i9").set_memory("32GB").set_disk_space("1TB").to_h
# {:cpu=>"i9", :memory=>"32GB", :disk_space=>"1TB"}
I think no need to declare PROPERTIES, we can create a general builder like this:
class Builder
attr_reader :build
def initialize(clazz)
#build = clazz.new
end
def self.build(clazz, &block)
builder = Builder.new(clazz)
builder.instance_eval(&block)
builder.build
end
def set(attr, val)
#build.send("#{attr}=", val)
self
end
def method_missing(m, *args, &block)
if #build.respond_to?("#{m}=")
set(m, *args)
else
#build.send("#{m}", *args, &block)
end
self
end
def respond_to_missing?(method_name, include_private = false)
#build.respond_to?(method_name) || super
end
end
Using
class Test
attr_accessor :x, :y, :z
attr_reader :w, :u, :v
def set_w(val)
#w = val&.even? ? val : 0
end
def add_u(val)
#u = val if val&.odd?
end
end
test1 = Builder.build(Test) {
x 1
y 2
z 3
} # <Test:0x000055b6b0fb2888 #x=1, #y=2, #z=3>
test2 = Builder.new(Test).set(:x, 1988).set_w(6).add_u(2).build
# <Test:0x000055b6b0fb23b0 #x=1988, #w=6>
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"
I'm trying to create a method that passes the caller as the default last argument. According to this, I only need:
class A
def initialize(object = self)
# work with object
end
end
so that in:
class B
def initialize
A.new # self is a B instance here
end
end
self will be B rather than A;
However, this doesn't seem to work. Here's some test code:
class A
def self.test test, t=self
puts t
end
end
class B
def test test,t=self
puts t
end
end
class T
def a
A.test 'hey'
end
def b
B.new.test 'hey'
end
def self.a
A.test 'hey'
end
def self.b
B.new.test'hey'
end
end
and I get:
T.new.a # => A
T.new.b # => #<B:0x000000015fef00>
T.a # => A
T.b # => #<B:0x000000015fed98>
whereas I expect it to be T or #<T:0x000000015fdf08>. Is there a way to set the default last argument to the caller?
EDIT:
class Registry
class << self
def add(component, base=self)
self.send(component).update( base.to_s.split('::').last => base)
end
end
end
The idea is pretty simple, you would use it like this
class Asset_Manager
Registry.add :utilities
end
and you access it like:
include Registry.utilities 'Debugger'
I'm trying to de-couple classes by having a middle-man management type class that takes care of inter-class communications, auto-loading of missing classes and erroring when it doesn't exist, it works but I just want to be able to use the above rather than:
class Asset_Manager
Registry.add :utilities, self
end
It just feels cleaner, that and I wanted to know if such a thing was possible.
You can't escape the explicit self. But you can hide it with some ruby magic.
class Registry
def self.add(group, klass)
puts "registering #{klass} in #{group}"
end
end
module Registrable
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def register_in(group)
Registry.add(group, self)
end
end
end
class AssetManager
include Registrable
register_in :utilities
end
# >> registering AssetManager in utilities
In short, you can't.
Ruby resolves the default arguments in the context of the receiver. That is, the object before the . in a method call. What you called the receiver should be the caller, actually.
class A
def test1(value = a)
puts a
end
def test2(value = b)
puts b
end
def a
"a"
end
end
a = A.new
a.test1 #=> a
def a.b; "b" end
a.test2 #=> b
If I were you, I would use the extended (or included) hook, where both the extending class and the extended module can be accessed. You can program what ever logic you want based on the information.
module Registry
module Utilities
def self.extended(cls)
#puts cls
::Registry.send(component).update( cls.to_s.split('::').last => cls)
end
end
end
class Asset_Manager
extend Registry::Utilities
end
Hi I try to make my first game in ruby :)
I have two files:
#"game.rb" with code:
class Game
attr_accessor :imie, :klasa, :honor
def initialize(start_scena)
#start = start_scena
end
def name()
puts "Some text"
exit(0)
end
end
and second file
#"game_engine.rb"
require_relative 'game.rb'
class Start
def initialize
#game = Game.new(:name)
end
def play()
next_scena = #start
while true
puts "\n---------"
scena = method(next_scena)
next_scena = scena.call()
end
end
end
go = Start.new()
go.play()
The question is, how can I call class Game.name method from Start.play() class. The game goes deeper, and insted of 'exit(0)' it returns :symbol of another method from "Game" class that should work.
Make start readable for the Game class. DO NOT call exit(0) in your code unless it's really necessary. Instead, use some conditions to make sure the program runs to the end of script.
#"game.rb" with code:
class Game
attr_accessor :imie, :klasa, :honor
attr_reader :start
def initialize(start_scena)
#start = start_scena
end
def name()
puts "Some text"
:round2
end
def round2
puts "round2"
nil
end
end
Use instance#method(...) to get a bounded method to that instance.
#"game_engine.rb"
require_relative 'game.rb'
class Start
def initialize
#game = Game.new(:name)
end
def play()
next_scene = #game.start
while next_scene
puts "\n---------"
scene = #game.method(next_scene)
next_scene = scene.call()
end
end
end
go = Start.new()
go.play()
I'm trying to make a method similar to attr_reader but I can't seem to get the instance of the class that the method gets called in.
class Module
def modifiable_reader(*symbols)
# Right here is where it returns Klass instead of #<Klass:0x1df25e0 #readable="this">
mod = self
variables = symbols.collect { |sym| ("#" << sym.to_s).to_sym }
attr_reader *symbols
(class << ModifyMethods; self; end).instance_eval do
define_method(*symbols) do
mod.instance_variable_get(*variables)
end
end
end
end
class Object
module ModifyMethods; end
def modify(&block)
ModifyMethods.instance_eval(&block)
end
end
class Klass
modifiable_reader :readable
def initialize
#readable = "this"
end
end
my_klass = Klass.new
my_klass.modify do
puts "Readable: " << readable.to_s
end
I'm not sure what it is you're trying to do.
If it helps, the spell for attr_reader is something like this:
#!/usr/bin/ruby1.8
module Kernel
def my_attr_reader(symbol)
eval <<-EOS
def #{symbol}
##{symbol}
end
EOS
end
end
class Foo
my_attr_reader :foo
def initialize
#foo = 'foo'
end
end
p Foo.new.foo # => "foo"
What I can understand from your code is that you want to have the modify block to respond to the instance methods of Klass, that's as simple as:
class Klass
attr_reader :modifiable
alias_method :modify, :instance_eval
def initialize(m)
#modifiable = m
end
end
Klass.new('john').modify do
puts 'Readable %s' % modifiable
end
About this tidbit of code:
def modifiable_reader(*symbols)
# Right here is where it returns Klass instead of #<Klass:0x1df25e0 #readable="this">
mod = self
...
Probably this can give you a hint of what is going on:
Class.superclass # => Module
Klass.instance_of?(Class) # => true
Klass = Class.new do
def hello
'hello'
end
end
Klass.new.hello # => 'hello'
When you are adding methods to the Module class, you are also adding methods to the Class class, which will add an instance method to instances of Class (in this case your class Klass), at the end this means you are adding class methods on your Klass class