从mixin访问类名

4
在我的Rails项目中,我将全局设置存储在一个字符串索引的哈希表中,其中每个类(模型)都有自己的"命名空间"用于其自己的设置。例如,News模型可能具有设置'news.stories_per_page'或'news.show_date'。

为了避免到处使用名称操作,我有一个mixin提供通用的类方法来访问这些设置。使用这个mixin,我可以使用如下代码访问'news.show_date':

News.setting :show_date
=> true

现在,问题来了。为了生成字符串“news.show_date”,我需要知道将我的模块混入的模型的类名。但是在类方法中,我无法访问该类名。
self.class
=> Class

这对我来说并不是很有帮助。在我的天真实现中,这导致所有模型都将其设置存储在“class.”命名空间下,这是不可接受的。

很抱歉我无法更清楚地陈述问题。我对Ruby还比较新,还没有完全理解它的对象模型。这个问题可能与在Ruby中似乎需要的混合类方法的修补有关。


1
有很多方法可以做到这一点(其中大部分与我不同)。你的硬性要求是什么?你必须使用一个使用字符串的单个哈希吗? - Phrogz
@Phrogz:硬性要求是能够将Ruby对象序列化到数据库中,并且可以按类(按模型)访问该数据库。我正在使用“rails-settings-cached”宝石来实现这一点,但可能有很多其他方法。我提出这个问题主要是因为我想了解背后的逻辑。 - jforberg
3个回答

5
一个类的名称就是类的 name
module Foo
  def whoami
    self.name
  end
end

class Bar
  extend Foo
end

p Bar.whoami #=> "Bar"

我不会创建一些字符串;我会为每个类创建一个新的设置哈希表:
module Settings
  def setting(name,value=:GIT_DA_VALUE)
    @_class_settings ||= {}  # Create a new hash on this object, if needed
    if value==:GIT_DA_VALUE
      @_class_settings[name]
    else
      @_class_settings[name] = value
    end
  end
end

class Foo
  extend Settings
end
class Bar
  extend Settings
end
Foo.setting(:a,42)

p Foo.setting(:a), #=> 42
  Foo.setting(:b), #=> nil
  Bar.setting(:a)  #=> nil  (showing that settings are per class)

否则我会通过类对象本身将单个全局哈希表(如果需要)索引化:
module Settings
  # A single two-level hash for all settings, indexed by the object
  # upon which the settings are applied; automatically creates
  # a new settings hash for each object when a new object is peeked at
  SETTINGS = Hash.new{ |h,obj| h[obj]={} }
  def setting(name,value=:GIT_DA_VALUE)
    if value==:GIT_DA_VALUE
      SETTINGS[self][name]
    else
      SETTINGS[self][name] = value
    end
  end
end

# Usage is the same as the above; settings are unique per class

+1 你的答案比我的更简单。也许有一个问题:#name返回的是一个字符串,而不是类。 - knut
@Phrogz:你认为从类方法调用的self.name与从实例方法调用的self.class.to_s相当吗? - jforberg
1
@jforberg 由于实例中的 self.class 就是一个类的 self,所以你的问题实际上是“对于一个类来说,to_sname 是一样的吗?”从描述、源代码和实验中可以看出,答案通常是“是的,但不总是”。 - Phrogz

2

不要使用self.class,而可以使用self.ancestors或更详细的self.ancestors.first代替:

module Mixin
  def setting(name)
    puts "call #{self.ancestors.first}.#{__method__} with #{name}"
  end
end

class A
  extend Mixin
end

A.setting :a  #-> call A.setting with a

使用 extend Mixin 代替通过单例类进行操作。 - Phrogz
@Phrogz 谢谢 - 我修改了我的答案。我相信有一种更简单的方法,但是我没有即时找到它。 - knut

0
一个解决方法是在每个类方法中实例化self,并在实例上调用class。这不是特别优美的解决方案,但似乎可以工作。
module SettingsMixin
  def self.included receiver
     receiver.extend ClassMethods
  end

  module ClassMethods
    def setting(key)
      class_name = self.new.class # => ClassThatMixesMeIn

      # Setting-fetching logic here...

    end
  end
end

ClassMethods 中的代码似乎在被从 ClassThatMixesMeIn 调用之前不会被解析。只有在调用时,它才会具有正确的值。


self.new.classself 是一样的。 - Phrogz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接