Creating class methods from a module

Given a simple example:

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: BA: A C: 

I want to create a module that will expand to provide the same functionality, but with all the tag information specified by the class. For instance.

 module Tester def add_ident(v); ....; end end class Base extend Tester add_ident :tag end 

I found that I can do this with direct 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 any way to get this functionality without using eval? I went through every combination of define_method and instance_variable_get / set that I can think of, and I can't get it to work.

Ruby 1.9 without Rails.

+4
source share
4 answers

You want to define a dynamic method for the singleton class of the class you are extending. You can get an expression for a singleton class of a class as follows: class << self; self end class << self; self end . To open a class of a class, you can use class_eval . Combining all this, 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 are using the latest 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 think you want to use self.class.send(:define_method) as shown in another answer here; this has an unintended side effect of adding a dynamic method to all child classes of self.class , which in case of A in my example is Class .

+2
source
 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 
+1
source

My favorite ruby ​​book Metaprogramming Ruby solved the following questions as follows:

 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 
+1
source

Bach, this is not always the case, as soon as you are upset enough to post a message, then find the answer :)

It seems that the trick is in (class <self, self; end) to give you an instance of the class without destroying the local area. Link: How to 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 will accept the best answers if they come, though.

0
source

Source: https://habr.com/ru/post/1438534/


All Articles