jforberg
jforberg

Reputation: 6752

Access class name from mixin

In my Rails project, I store global settings in a string-indexed hash where each class (model) has a "namespace" for its own settings. For example, the News model might have the settings 'news.stories_per_page' or 'news.show_date'.

To avoid having to name-mangle everywhere, I have a mixin which provides general class methods for accessing these settings. Using this mixin, I could access 'news.show_date' with code like:

News.setting :show_date
=> true

Now, here's the problem. In order to generate the string 'news.show_date', I need to know the class name of the model that mixes my module in. But within a class method,

self.class
=> Class

which is not very helpful to me. In my naïve implemetation, this caused all models to store their settings under the 'class.' namespace, which is unacceptable.

I apologise for not being able to state the problem more clearly. I'm somewhat new to Ruby and have not understood its object model fully. The problem may have something to do with the kludge which seems to be required in Ruby to mix in class methods.

Upvotes: 4

Views: 1720

Answers (3)

Phrogz
Phrogz

Reputation: 303234

The name of a class is the name of a class:

module Foo
  def whoami
    self.name
  end
end

class Bar
  extend Foo
end

p Bar.whoami #=> "Bar"

I would not create some string; I would either create a new hash of settings per class:

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)

...or else I would index a single global hash (if required) by the class object itself:

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

Upvotes: 5

knut
knut

Reputation: 27855

Instead using self.class you may use self.ancestors or more detailed 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

Upvotes: 2

jforberg
jforberg

Reputation: 6752

One workaround is to instantiate self within each of the class methods and call class on the instance. It's not a particularly beautiful solution, but it seems to work.

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

The code within ClassMethods is not parsed (or so it seems) until it is called from ClassThatMixesMeIn. It will then have the correct value.

Upvotes: 0

Related Questions