I have a list of names and values I'm trying to read in and turn into classes so I'm using Class.new.
The end result I want is a number of classes that work as if defined like:
module MyMod
class AA < Base
def self.value
value1
end
end
class AB < Base
def self.value
value2
end
end
...
end
My current code looks like:
name = 'AA'
value = 'test'
MyMod.const_set name, Class.new(Base) do
???
end
Setting the name works great, but haven't figured out what I need in the block for get value in. Calling def doesn't work because the closure for value gets lost.
I have managed to get things working with:
temp = const_set name, Class.new(Base)
temp.define_singleton_method(:value) { value }
However, it seems like there should be a way to do it with the block of Class.new. Also, I'm really not sure define_singleton_method is actually putting the method in the right place. It works in my tests, but I'm not sure if the method is actually where I think it is or somewhere else up the call chain. I've tried various combinations of class_variable_set, attr_reader, class_eval, instance_eval, and others, but it got to a point where it was just guess and check. I think I still haven't quite wrapped my head around metaprogramming :-/
if i correctly understood your question, this should work for you:
class Base
end
class AA < Base
name = :Blah
klass = self.const_set name, Class.new(Base)
class << klass
def value
__method__
end
end
end
p AA::Blah.value
#=> :value
UPDATE: seems you want it defined in the block:
class Base
end
class AA < Base
name = :Blah
klass = Class.new(Base) do
class << self
def value
__method__
end
end
end
self.const_set name, klass
end
p AA::Blah.value
you trying this:
const_set name, Class.new(Base) do
...
end
it does not work cause the block is referring to const_set rather than to Class.new
If you prefer define_singleton_method over class << self:
class Base
end
class AA < Base
name = :Blah
klass = Class.new(Base) do
self.define_singleton_method :value do
__method__
end
end
self.const_set name, klass
end
And finally if you really want to define them at once, use brackets instead of do...end:
class Base
end
class AA < Base
name = :Blah
self.const_set name, Class.new(Base) {
self.define_singleton_method :value do
__method__
end
}
end
Here is a working demo
Related
I have a Ruby class with all static methods. I want to use these methods in another class. Passing just the class name works but wondering if its the right way to go or is there another way.
class Foo
IDENTIFIER = 'UYT78'
def self.some_data
DB.fetch_records
end
# and so forth
end
class Bar
IDENTIFIER = 'XXXXX'
def self.some_data
DB.fetch_records
end
# and so forth
end
class Reporter
def report
Foo.some_data.select {|x| x.id == Foo::identifier}
end
def other_report
Bar.some_data.select {|x| x.id == Foo::identifier}
end
end
Can Reporter be changed to accept Foo as argument to the methods so these methods can be re-used based on the argument being passed?
Reporter.report(Foo)
Reporter.report(Bar)
and report method looks like
def report(klass)
klass.some_data.select {|x| x.id == Foo::identifier}
end
This approach works but passing a class name like this as argument doesn't feel right? Or may be it is?
That's fine... generally it's referred to as duck-typing... as long as the class passed supports #some_data method, it doesn't really matter what specific class it is. If it looks like a duck, and quacks like a duck...
It's possible you may want to store the target class at initialization.
class Reporter
attr_accessor :klass
def initialize(klass)
self.klass = klass
end
def report
klass.some_data.select
end
end
my_foo_reporter = Reporter.new(Foo)
my_foo_reporter.report # calls some_date on Foo class
What you are trying to do is nice but rather than passing the class like that I would use ruby's require.
foo.rb
class Foo
IDENTIFIER = 'UYT78'.freeze
def self.some_data
'Foo'
end
end
bar.rb
class Bar
IDENTIFIER = 'XXXXX'.freeze
def self.some_data
'Bar'
end
end
reporter.rb
require './foo'
require './bar'
class Reporter
def report
p Foo.some_data
p Foo::IDENTIFIER
end
def other_report
p Bar.some_data
p Bar::IDENTIFIER
end
end
Reporter.new.report
puts '----------------'
Reporter.new.other_report
Which would return like below (Just and example)-
"Foo"
"UYT78"
----------------
"Bar"
"XXXXX"
In my opinion it's much cleaner.
Perhaps this is what you are looking for.
class Foo
IDENTIFIER = 'UYT78'
def self.some_data
['ABC21', 'UYT78']
end
end
class Bar
IDENTIFIER = 'XXXXX'
def self.some_data
['XXXXX', 'DEF38']
end
end
class Reporter
def report(klass)
k = Module.const_get(klass)
k.public_send(:some_data).select {|x| x == k::IDENTIFIER}
end
end
reporter = Reporter.new
reporter.report("Foo") #=> ["UYT78"]
reporter.report("Bar") #=> ["XXXXX"]
reporter.report(:Bar) #=> ["XXXXX"]
Let's say I have a couple subclasses that inherit from a base class like this:
class Base
def initialize
#name = self.class.name
#num1 = 10;
#num2 = 5;
end
def speak
puts "My name is #{name}"
end
end
class Sub1 < Base
end
class Sub2 < Base
end
class Sub3 < Base
end
Is it bad practice to declare the instance variable of #name to the name of the class? Would it be better to do something like this?
class Base
def initialize
#feet = 10;
#inches = 5;
end
def speak
puts "My name is #{name}"
end
end
class Sub1 < Base
def initialize
super()
#name = "Sub1"
end
end
class Sub2 < Base
def initialize
super()
#name = "Sub2"
end
end
class Sub3 < Base
def initialize
super()
#name = "Sub3"
end
end
If #name is always intended to be equal to the class name, you don't need it – just use self.class.name or myObject.class.name directly, or define an accessor method which returns it if you don't want to use .class.name from the outside. Initializing it manually would be bad practice since you'd always have to remember to do it in every subclass and changes to subclass names would need to be replicated to the initialization.
If #name is not always going to equal the class name in every subclass, then personally I'd still provide a default implementation where it is initialized from self.class.name and let subclasses override it as necessary.
The first method is more concise. What you probably should be doing is evaluating this in a lazy capacity:
def name
#name ||= self.class.to_s.split(/::/).last
end
That way you don't need to worry about assignment and any subclass can initialize if necessary with an override. Your speak method will continue to work without modification as presumably you had declared attr_reader :name for that to work in the first place.
I think it's quite pointless, look at this output:
class Base
def name
self.class.name
end
end
class Sub1 < Base
# This declaration is useless
def name
self.class.name
end
end
class Sub2 < Base
end
b = Base.new
b.name # "Base"
c = Sub1.new
c.name # "Sub1"
d = Sub2.new
d.name # "Sub2" (this function is inherited from base, but is called from Sub2 class context)
Next thing in ruby, you can use superclass method to get parent class name
Sub2.superclass.name # "Base"
I want to call instance_eval on this class:
class A
attr_reader :att
end
passing this method b:
class B
def b(*args)
att
end
end
but this is happening:
a = A.new
bb = B.new
a.instance_eval(&bb.method(:b)) # NameError: undefined local variable or method `att' for #<B:0x007fb39ad0d568>
When b is a block it works, but b as a method isn't working. How can I make it work?
It's not clear exactly what you goal is. You can easily share methods between classes by defining them in a module and including the module in each class
module ABCommon
def a
'a'
end
end
class A
include ABCommon
end
Anything = Hash
class B < Anything
include ABCommon
def b(*args)
a
end
def run
puts b
end
end
This answer does not use a real method as asked, but I didn't need to return a Proc or change A. This is a DSL, def_b should have a meaningful name to the domain, like configure, and it is more likely to be defined in a module or base class.
class B
class << self
def def_b(&block)
(#b_blocks ||= []) << block
end
def run
return if #b_blocks.nil?
a = A.new
#b_blocks.each { |block| a.instance_eval(&block) }
end
end
def_b do
a
end
end
And it accepts multiple definitions. It could be made accept only a single definition like this:
class B
class << self
def def_b(&block)
raise "b defined twice!" unless #b_block.nil?
#b_block = block
end
def run
A.new.instance_eval(&#b_block) unless #b_block.nil?
end
end
def_b do
a
end
end
Given the simple example here:
class Base
#tag = nil
def self.tag(v = nil)
return #tag unless v
#tag = v
end
end
class A < Base
tag :A
end
class B < Base
tag :B
end
class C < Base; end
puts "A: #{A.tag}"
puts "B: #{B.tag}"
puts "A: #{A.tag}"
puts "C: #{C.tag}"
which works as expected
A: A
B: B
A: A
C:
I want to create a module that base will extend to give the same functionality but with all the tag information specified by the class. Eg.
module Tester
def add_ident(v); ....; end
end
class Base
extend Tester
add_ident :tag
end
I've found i can do it with a straight eval, so:
def add_ident(v)
v = v.to_s
eval "def self.#{v}(t = nil); return ##{v} unless t; ##{v} = t; end"
end
but i really dislike using eval string in any language.
Is there a way that i can get this functionality without using eval? I've gone through every combination of define_method and instance_variable_get/set i can think of and i can't get it to work.
Ruby 1.9 without Rails.
You want to define a dynamic method on the singleton class of the class you're extending. The singleton class of a class can be accessed with expression like this: class << self; self end. To open the scope of a class's class, you can use class_eval. Putting all this together, you can write:
module Identification
def add_identifier(identifier)
(class << self; self end).class_eval do
define_method(identifier) do |*args|
value = args.first
if value
instance_variable_set("##{identifier}", value)
else
instance_variable_get("##{identifier}")
end
end
end
end
end
class A
extend Identification
add_identifier :tag
end
If you're using recent versions of Ruby, this approach can be replaced with Module#define_singleton_method:
module Identification
def add_identifier(identifier)
define_singleton_method(identifier) do |value = nil|
if value
instance_variable_set("##{identifier}", value)
else
instance_variable_get("##{identifier}")
end
end
end
end
I don't believe you want to use self.class.send(:define_method), as shown in another answer here; this has the unintended side effect of adding the dynamic method to all child classes of self.class, which in the case of A in my example is Class.
module Tester
def add_ident(var)
self.class.send(:define_method, var) do |val=nil|
return instance_variable_get("##{var}") unless val
instance_variable_set "##{var}", val
end
end
end
My favourite ruby book Metaprogramming Ruby solved these questions like the following way:
module AddIdent
def self.included(base)
base.extend ClassMethods # hook method
end
module ClassMethods
def add_ident(tag)
define_method "#{tag}=" do |value=nil|
instance_variable_set("##{tag}", value)
end
define_method tag do
instance_variable_get "##{tag}"
end
end
end
end
# And use it like this
class Base
include AddIdent
add_ident :tag
end
Bah isn't it always the way that once you get frustrated enough to post you then find the answer :)
The trick seems to be in (class << self; self; end) to give you the class instance without destroying the local scope. Referencing: How do I use define_method to create class methods?
def add_ident(v)
var_name = ('#' + v.to_s).to_sym
(class << self; self; end).send(:define_method, v) do |t = nil|
return instance_variable_get(var_name) unless t
instance_variable_set(var_name, t)
end
end
I'll accept better answers if them come along though.
Is there a better way to write this Expando class? The way it is written does not work.
I'm using Ruby 1.8.7
starting code quoted from https://gist.github.com/300462/3fdf51800768f2c7089a53726384350c890bc7c3
class Expando
def method_missing(method_id, *arguments)
if match = method_id.id2name.match(/(\w*)(\s*)(=)(\s*)(\.*)/)
puts match[1].to_sym # think this was supposed to be commented
self.class.class_eval{ attr_accessor match[1].to_sym }
instance_variable_set("#{match[1]}", match[5])
else
super.method_missing(method_id, *arguments)
end
end
end
person = Expando.new
person.name = "Michael"
person.surname = "Erasmus"
person.age = 29
The easiest way to write it is to not write it at all! :) See the OpenStruct class, included in the standard library:
require 'ostruct'
record = OpenStruct.new
record.name = "John Smith"
record.age = 70
record.pension = 300
If I was going to write it, though, I'd do it like this:
# Access properties via methods or Hash notation
class Expando
def initialize
#properties = {}
end
def method_missing( name, *args )
name = name.to_s
if name[-1] == ?=
#properties[name[0..-2]] = args.first
else
#properties[name]
end
end
def []( key )
#properties[key]
end
def []=( key,val )
#properties[key] = val
end
end
person = Expando.new
person.name = "Michael"
person['surname'] = "Erasmus"
puts "#{person['name']} #{person.surname}"
#=> Michael Erasmus
If you're just trying to get a working Expando for use, use OpenStruct instead. But if you're doing this for educational value, let's fix the bugs.
The arguments to method_missing
When you call person.name = "Michael" this is translated into a call to person.method_missing(:name=, "Michael"), so you don't need to pull the parameter out with a regular expression. The value you're assigning is a separate parameter. Hence,
if method_id.to_s[-1,1] == "=" #the last character, as a string
name=method_id.to_s[0...-1] #everything except the last character
#as a string
#We'll come back to that class_eval line in a minute
#We'll come back to the instance_variable_set line in a minute as well.
else
super.method_missing(method_id, *arguments)
end
instance_variable_set
Instance variable names all start with the # character. It's not just syntactic sugar, it's actually part of the name. So you need to use the following line to set the instance variable:
instance_variable_set("##{name}", arguments[0])
(Notice also how we pulled the value we're assigning out of the arguments array)
class_eval
self.class refers to the Expando class as a whole. If you define an attr_accessor on it, then every expando will have an accessor for that attribute. I don't think that's what you want.
Rather, you need to do it inside a class << self block (this is the singleton class or eigenclass of self). This operates inside the eigenclass for self.
So we would execute
class << self; attr_accessor name.to_sym ; end
However, the variable name isn't actually accessible inside there, so we're going to need to single out the singleton class first, then run class_eval. A common way to do this is to out this with its own method eigenclass So we define
def eigenclass
class << self; self; end
end
and then call self.eigenclass.class_eval { attr_accessor name.to_sym } instead)
The solution
Combine all this, and the final solution works out to
class Expando
def eigenclass
class << self; self; end
end
def method_missing(method_id, *arguments)
if method_id.to_s[-1,1] == "="
name=method_id.to_s[0...-1]
eigenclass.class_eval{ attr_accessor name.to_sym }
instance_variable_set("##{name}", arguments[0])
else
super.method_missing(method_id, *arguments)
end
end
end