Singleton vs class variable. What is the difference and what to choose? - ruby

I want to add configuration to my module. I saw that some projects (geocoder for example) use Singleton class for this purpose. I noticed that writing configuration into a class variable will have the same effect
Are there any differences?
Will be class variable a safe solution in the multithread app?
See the code example below:
# using class variable
module MyApp
class << self
attr_accessor :config
def configure(config)
self.config = config
end
end
end
# using Singleton
module M
def self.config
Configuration.instance.data
end
def self.configure(config)
Configuration.instance.data = config
end
class Configuration
include Singleton
attr_accessor :data
# other methods
end
end
# or use class variable in the Configuration class itself
# in case if we need additional methods for configuration
module M
def self.config
Configuration.data
end
def self.configure(config)
Configuration.data = config
end
class Configuration
class << self
attr_accessor :data
end
# other methods
end
end

Related

How to best abstract class variables in Ruby

I've got a class such as
# this has been simplified for the example
class MyClass
##private_attributes = []
def self.private_attributes(*args)
##private_attributes = args
end
def private_attributes
#private_attributes ||= ##private_attributes
end
end
It works great. I've this ##private_attributes set at class level which's then used at instance level in multiple ways.
I want to abstract this logic somewhere else to simplify my class, something like that
class MyClass
include PrivateAttributes
end
When I create the module PrivateAttributes, however I shape it, the ##private_attributes isn't understood at MyClass level.
I tried many things but here's the latest code attempt
module PrivateAttributes
include ProcessAttributes
def self.included(base)
base.extend(ClassMethods)
base.include(InstanceMethods)
end
module ClassMethods
##private_attributes = []
def private_attributes(*args)
##private_attributes = args
end
end
module InstanceMethods
def private_attributes
#private_attributes ||= process_attributes_from(##private_attributes)
end
def private_attributes?
instance_options[:scope] == :private
end
end
end
It crashes with this error
NameError:
uninitialized class variable ##private_attributes in PrivateAttributes::InstanceMethods
Did you mean? private_constant
In short, the ##private_attributes isn't transferred throughout the code, but looks like it stays at the module level.
What's the best way to abstract this logic from my original class ?
Working solution
An easy workaround is to use mattr_accessor on the class level or anything similar to communicate our data around. I preferred to write down my own methods in this case:
module PrivateAttributes
include ProcessAttributes
def self.included(base)
base.extend(ClassMethods)
base.include(InstanceMethods)
end
module ClassMethods
##private_attributes_memory = []
def private_attributes(*args)
##private_attributes_memory = args
end
def private_attributes_memory
##private_attributes_memory
end
end
module InstanceMethods
def private_attributes
#private_attributes ||= process_attributes_from private_attributes_memory
end
# you can add diverse methods here
# which will be used in MyClass once included
private
def private_attributes_memory
self.class.private_attributes_memory
end
end
end

How can I use class instance variables in an extended module in Ruby?

I want to create a DSL which stores various blocks, and then can call them. I want this to be a reusable module that I can include in multiple classes.
I figured out a way to do this using class variables, but rubocop complains and says that I should use class instance variables instead. I can't figure out a way to do this though. Is it possible?
module MyModule
def self.included(base)
base.extend(ClassMethods)
end
def run_fixers
ClassMethods.class_variable_get(:##fixers).each(&:call)
end
module ClassMethods
def fix(_name, &block)
##fixers ||= []
##fixers << block
end
end
end
class MyClass
include MyModule
def initialize
run_fixers
end
fix 'test' do
puts 'testing'
end
end
Rubocop complains about ##class_variable style. Class variables usage can be refactored into using instance variable of a class:
class SomeClass
#some_variable ||= []
end
instead of
class SomeClass
##some_variable ||= []
end
You can define instance variable of a class in any place where self equals to the class object. For example, in a class method:
module MyModule
def self.included(base)
base.extend(ClassMethods)
end
def run_fixers
ClassMethods.fixers.each(&:call)
end
module ClassMethods
def self.fixers
#fixers ||= []
end
def fix(_name, &block)
ClassMethods.fixers << block
end
end
end

Configure a class with a block

I have a base module that has some logic in it and it gets included inside various classes. Now I need a configure block that sets some configuration how the class should behave.
I tried the following code:
class MyConfig
attr_accessor :foo, :bar
end
module BaseModule
def self.included base
base.include InstanceMethods
base.extend ClassMethods
end
module ClassMethods
attr_reader :config
def configure &block
#config = MyConfig.new
block.call(#config)
end
end
module InstanceMethods
def do_something
puts self.class.config.inspect
end
end
end
class MyClass1
include BaseModule
configure do |config|
config.foo = "foo"
end
end
class MyClass2
include BaseModule
configure do |config|
config.bar = "bar"
end
end
MyClass1.new.do_something
#<MyConfig:0x007fa052877ea0 #foo="foo">
MyClass2.new.do_something
#<MyConfig:0x007fa052877ce8 #bar="bar">
I'm unsure if an instance variable #config for a module / class is the best way to configure a class. Is this the right way, or are there any better solutions?
Is it good to also use base.extend ClassMethods when the module only gets included with include BaseModule? A developer could expect that only instance methods get included, but as a side effect there are also class methods extended.
Update
I've added a MyConfig class, from which a new instance is created. This has the benefit that you can use config.foo = "foo".
I've changed your code a bit to use an approach both me and a lot of popular gems (like devise) use for configuration. Hope you'll like it :)
module BaseModule
def self.included base
base.include InstanceMethods
base.extend ClassMethods
end
module ClassMethods
mattr_accessor :foo
##foo = 'initial value'
def configure &block
block.call(self)
end
end
module InstanceMethods
def do_something
puts foo
end
end
end
class MyClass1
include BaseModule
configure do |klass|
klass.foo = "foo"
end
end
class MyClass2
include BaseModule
configure do |klass|
klass.foo = "bar"
end
end
MyClass1.new.do_something
# => "foo"
MyClass2.new.do_something
# => "bar"
The changes are:
use mattr_accessor - it creates both getters and setters for your method, you can opt out some of them if you wish, more info here
let configure method return self, this way you can configure your class in a simple, readable, rails-like way
Example:
Kid.config do |k|
k.be_kind = false
k.munch_loudly = true
k.age = 12
end

Ruby execute self.included(base) method on sub classes without explicit include declaration?

I have a number of classes that inherit from one superclass.
The superclass as a module defined. Inside of a module is a self.included(base) method that sets some instance variables.
So something like this:
module MyModule
def self.included(base)
base.instance_variable_set("#my_instance_variable", {})
end
end
class MySuperClass
include MyModule
end
class ClassA < MySuperClass
end
class ClassB < MySuperClass
end
Unless I explicitly include MyModule in ClassA and ClassB then my instance variable will not get set in these two classes.
Is there a way of making sure the modules self.included(base) method is executed in each sub class without the need to explicitly include the module? Since it's already included in the superclass.
Class instance variable are private to the class. Inherited classes can't access them directly. There is a couple of ways here.
1. Define accessor
module MyModule
def self.included(base)
base.instance_variable_set :#my_instance_variable, {}
base.extend ClassMethods
end
module ClassMethods
def my_instance_variable
# self is ClassA here, so we need to call superclass
superclass.instance_variable_get :#my_instance_variable # => {}
end
end
end
class MySuperClass
include MyModule
end
class ClassA < MySuperClass; end
ClassA.my_instance_variable # => {}
2. More metaprogrammatic
But there's a hook that gets called every time when a class is inherited from you. You can set variables at this point. Check this out.
module MyModule
def self.included(base)
base.extend ClassMethods
end
module ClassMethods
# make it a class method. It will be called on target classes later.
def enhance klass
klass.instance_variable_set("#my_instance_variable", {})
end
end
end
class MySuperClass
include MyModule
# this hook will get called on 'class ClassA < MySuperClass`
def self.inherited klass
# set class instance var on child class
enhance klass
end
end
class ClassA < MySuperClass; end
ClassA.instance_variable_get '#my_instance_variable' # => {}
NOTE: in this sample each inherited class gets its own class instance var (and base class doesn't get one). This might be more appropriate for your case, might be not.

Setting new class variables inside a module

I have a plugin I have been working on that adds publishing to ActiveRecord classes. I extend my classes with my publisher like so:
class Note < ActiveRecord::Base
# ...
publishable :related_attributes => [:taggings]
end
My publisher is structured like:
module Publisher
def self.included(base)
base.send(:extend, ClassMethods)
##publishing_options = [] # does not seem to be available
end
module ClassMethods
def publishable options={}
include InstanceMethods
##publishing_options = options
# does not work as class_variable_set is a private method
# self.class_variable_set(:##publishing_options, options)
# results in: uninitialized class variable ##publishing_options in Publisher::ClassMethods
puts "##publishing_options: #{##publishing_options.inspect}"
# ...
end
# ...
end
module InstanceMethods
# results in: uninitialized class variable ##publishing_options in Publisher::InstanceMethods
def related_attributes
##publishing_options[:related_attributes]
end
# ...
end
end
Any ideas on how to pass options to publishable and have them available as a class variable?
I am presuming that you want one set of publishing_options per class. In that case you just want to prefix your variable with a single #. Remember the class itself is an instance of the class Class so when you are in the context of a class method you actually want to set an instance variable on your class. Something like the following:
module Publishable
module ClassMethods
def publishable(options)
#publishing_options = options
end
def publishing_options
#publishing_options
end
end
def self.included(base)
base.extend(ClassMethods)
end
end
Then if ActiveRecord::Base is extended as follows:
ActiveRecord::Base.send :include, Publishable
You can do:
class Note < ActiveRecord::Base
publishable :related_attributes => [:taggings]
end
class Other < ActiveRecord::Base
publishable :related_attributes => [:other]
end
Note.publishing_options
=> {:related_attributes=>[:taggings]}
Other.publishing_options
=> {:related_attributes=>[:other]}

Resources