OK、ローカル変数の割り当てでは、ローカル変数リストへのローカル変数シンボルの追加よりも少し遅れて割り当てが発生する可能性があるなどの問題があります。ConstMagicErsatz
しかし、これが私がすぐに使えるRuby定数マジックに似たものを実装するために使用した私のモジュールです:
a = Class.new
a.name #=> nil - anonymous
ABC = a # constant magic at work
a.name #=> "ABC"
ここでの利点は、ABC = Class.new(name: "ABC")と書く必要がないことです。名前には、「魔法のように」割り当てられます。これはStructクラスでも機能します。
Koko = Struct.new
Koko.name #=> "Koko"
しかし、他のクラスはありません。だからここにConstMagicErsatz
あなたがすることができる私の行きます
class MySettings < SettingsLogic
include ConstMagicErsatz
end
ABC = MySettings.new
ABC.name #=> "ABC"
と同様
a = MySettings.new name: "ABC"
a.name #=> "ABC"
ここに行きます:
module ConstMagicErsatz
def self.included receiver
receiver.class_variable_set :@@instances, Hash.new
receiver.class_variable_set :@@nameless_instances, Array.new
receiver.extend ConstMagicClassMethods
end
# The receiver class will obtain #name pseudo getter method.
def name
self.class.const_magic
name_string = self.class.instances[ self ].to_s
name_string.nil? ? nil : name_string.demodulize
end
# The receiver class will obtain #name setter method
def name= ɴ
self.class.const_magic
self.class.instances[ self ] = ɴ.to_s
end
module ConstMagicClassMethods
# #new method will consume either:
# 1. any parameter named :name or :ɴ from among the named parameters,
# or,
# 2. the first parameter from among the ordered parameters,
# and invoke #new of the receiver class with the remaining arguments.
def new( *args, &block )
oo = args.extract_options!
# consume :name named argument if it was supplied
ɴς = if oo[:name] then oo.delete( :name ).to_s
elsif oo[:ɴ] then oo.delete( :ɴ ).to_s
else nil end
# but do not consume the first ordered argument
# and call #new method of the receiver class with the remaining args:
instance = super *args, oo, &block
# having obtained the instance, attach the name to it
instances.merge!( instance => ɴς )
return instance
end
# The method will search the namespace for constants to which the objects
# of the receiver class, that are so far nameless, are assigned, and name
# them by the first such constant found. The method returns the number of
# remaining nameless instances.
def const_magic
self.nameless_instances =
class_variable_get( :@@instances ).select{ |key, val| val.null? }.keys
return 0 if nameless_instances.size == 0
catch :no_nameless_instances do search_namespace_and_subspaces Object end
return nameless_instances.size
end # def const_magic
# @@instances getter and setter for the target class
def instances; const_magic; class_variable_get :@@instances end
def instances= val; class_variable_set :@@instances, val end
# @@nameless_instances getter for the target class
def nameless_instances; class_variable_get :@@nameless_instances end
def nameless_instances= val; class_variable_set :@@nameless_instances, val end
private
# Checks all the constants in some module's namespace, recursivy
def search_namespace_and_subspaces( ɱodule, occupied = [] )
occupied << ɱodule.object_id # mark the module "occupied"
# Get all the constants of ɱodule namespace (in reverse - more effic.)
const_symbols = ɱodule.constants( false ).reverse
# check contents of these constant for wanted objects
const_symbols.each do |sym|
# puts "#{ɱodule}::#{sym}" # DEBUG
# get the constant contents
obj = ɱodule.const_get( sym ) rescue nil
# is it a wanted object?
if nameless_instances.map( &:object_id ).include? obj.object_id then
class_variable_get( :@@instances )[ obj ] = ɱodule.name + "::#{sym}"
nameless_instances.delete obj
# and stop working in case there are no more unnamed instances
throw :no_nameless_instances if nameless_instances.empty?
end
end
# and recursively descend into the subspaces
const_symbols.each do |sym|
obj = ɱodule.const_get sym rescue nil # get the const value
search_namespace_and_subspaces( obj, occupied ) unless
occupied.include? obj.object_id if obj.kind_of? Module
end
end
end # module ConstMagicClassMethods
end # module ConstMagicErsatz
上記のコードは、#nameメソッドが呼び出されるたびに、指定されたインスタンスを参照する定数を見つけることを目的として、Ruby名前空間全体の自動検索を実装しています。
定数を使用する唯一の制約は、それを大文字にする必要があるということです。もちろん、必要なのは、オブジェクトがすでに作成されて定数に割り当てられた後で、オブジェクトのメタクラスを変更することです。繰り返しになりますが、フックがないため、新しいオブジェクトがその目的のために最初に使用されるときなど、これを行う機会を見つける必要があります。だから、持っている
ABC = MySettings.new
次に、MySettingsインスタンスが最初に使用されたときに、他のことを行う前に、そのメタクラスにパッチを適用します。
class MySettings
def do_something_useful
# before doing it
instance_name = self.name
singleton_class.class_exec { source "#{instance_name}.yml" }
end
# do other useful things
end