Reputation: 6752
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
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
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
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