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]}
Related
I'd like my module to define new instance methods based on its including class' instance methods. But in the included hook, the class methods are not defined yet (as the module is included at the top of the class, before the class methods are defined):
module MyModule
def self.included(includer)
puts includer.instance_methods.include? :my_class_method # false <- Problem
end
end
class MyClass
include MyModule
def my_class_method
end
end
I want the users of the module to be free to include it at the top of their class.
Is there a way to make a module define additional methods to a class?
Note: I don't have to use the included hook if there is another way to achieve this.
There'a a method_added callback you could use:
module MyModule
def self.included(includer)
def includer.method_added(name)
puts "Method added #{name.inspect}"
end
end
end
class MyClass
include MyModule
def foo ; end
end
Output:
Method added :foo
If you want to track both, existing and future methods, you might need something like this:
module MyModule
def self.on_method(name)
puts "Method #{name.inspect}"
end
def self.included(includer)
includer.instance_methods(false).each do |name|
on_method(name)
end
def includer.method_added(name)
MyModule.on_method(name)
end
end
end
Example:
class MyClass
def foo ; end
include MyModule
def bar; end
end
# Method :foo
# Method :bar
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
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
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
Ruby library code
module Yaffle
module ActsAsYaffle
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def acts_as_yaffle(options = {})
cattr_accessor :yaffle_text_field
self.yaffle_text_field = (options[:yaffle_text_field] || :last_squawk).to_s
include Yaffle::ActsAsYaffle::LocalInstanceMethods
end
end
...
more code
...
end
end
ActiveRecord::Base.send(:include, ActiveRecord::Acts::Taggable)
Why does when i call the acts_as_yaffle in the model, its being used like
class Hickwall < ActiveRecord::Base
acts_as_yaffle
end
Does the ActiveRecord::Base.send(:include,...) included the ClassMethods as instance eventhough the base is extended (base.extend(ClassMethods) ?
The acts_as_yaffle was declared as a ClassMethods.
It is a class method. Consider this example:
class C
def my_instance_method
puts 'hello from instance'
end
def self.my_class_method
puts 'hello from class'
end
my_instance_method
# NoMethodError
my_class_method
# hello from class
end
inside class C ... end you can only call class methods, because there is no instance to send the method call to. The library code is written using a common pattern to put class methods inside a module. For better understanding, the example class I wrote above is equivalent to this:
module M
def my_instance_method
puts 'hello from instance'
end
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def my_class_method
puts 'hello from class'
end
end
end