I find myself writing code like the following a lot:
module SomeModule
module ClassMethods
def some_attribute
#some_attribute
end
def some_attribute=(val)
#some_attribute = val
end
end
def self.included(other)
other.extend ClassMethods
end
end
class MyClass
include SomeModule
self.some_attribute = "a value"
end
Is there a shorthand for the two class methods defined above? Something like attr_accessor but for class methods.
[EDIT]
Based on sawa's answer the self.included method can be changed to:
def self.included(other)
other.singleton_class.class_eval{attr_accessor :some_attribute}
end
You can try this:
module SomeModule
def self.included(other)
class << other
attr_accessor :some_attribute
end
end
end
class MyClass
include SomeModule
self.some_attribute = "a value"
end
Note: Don't forget the self before calling some_attribute=. You need the explicit receiver on writter methods or Ruby will think you're trying to assign a variable somewhere =P
Hope it helps !
class MyClass
singleton_class.class_eval{attr_accessor :some_attribute}
end
It can also be done like this:
module SomeModule
attr_accessor :some_attribute
end
class MyClass
extend SomeModule
#some_attribute = "life is grand"
end
MyClass.some_attribute #=> "life is grand"
But why not just the usual way?
class MyClass
class << self
attr_accessor :some_attribute
end
end
MyClass.some_attribute = "life is grand"
MyClass.some_attribute #=> "life is grand"
Related
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 have this module
module MyModule
def self.foo()
puts "A"
end
end
And this mixin class
class ParentClass
include MyModule
...other code
end
Can I overwrite the foo method in a child class like this or is there a better way?
class ChildClass < ParentClass
def self.foo()
puts "B"
end
... other code
end
You cannot include class methods directly:
module MyModule
def self.foo() puts "A" end
end
class ParentClass
include MyModule
end
ParentClass.methods.include?(:foo)
#=> false
ParentClass.instance_methods.include?(:foo)
#=> false
Instead, use Object#extend, which converts instance methods in MyModule to class methods in ParentClass:
module MyModule
def foo() puts "A" end
end
class ParentClass
extend MyModule
end
ParentClass.methods.include?(:foo)
#=> true
ParentClass.foo
A
class ChildClass < ParentClass
def self.foo() puts "B" end
end
ChildClass.foo
B
So, you may ask, what's the point of having class methods in a module? They can be present when the module is not used as a mixin, or has a combination of instance and class methods. The module's class methods are simply helper functions:
module A
def self.say
puts "It's a cat."
end
def say
puts "It's a dog."
end
end
class B
include A
end
A.say
It's a cat
B.new.say
It's a dog.
It is my impression that most modules that contain class methods tend to contain no instance methods; that is, they are not used as mix-ins. Readers are encouraged to correct me if I am wrong about that.
A common way to include some instance methods and extend others is to use the callback Module#included:
module A
def a; end
def self.included(klass)
klass.extend B
end
module B
def b; end
end
end
class C
include A
end
C.instance_methods.include?(:a)
#=> true
C.methods.include?(:b)
#=> true
attr_accessor does not work on the following code. The error says "undefined method 'things' for Parent:Class (NoMethodError)":
class Parent
##things = []
attr_accessor :things
end
Parent.things << :car
p Parent.things
However the following code works
class Parent
##things = []
def self.things
##things
end
def things
##things
end
end
Parent.things << :car
p Parent.things
attr_accessor defines accessor methods for an instance. If you want class level auto-generated accessors you could use it on the metaclass
class Parent
#things = []
class << self
attr_accessor :things
end
end
Parent.things #=> []
Parent.things << :car
Parent.things #=> [:car]
but note that this creates a class level instance variable not a class variable. This is likely what you want anyway, as class variables behave differently than you might expect when dealing w/ inheritance. See "Class and Instance Variables In Ruby".
attr_accessor generates accessors for instance variables. Class variables in Ruby are a very different thing, and they are usually not what you want. What you probably want here is a class instance variable. You can use attr_accessor with class instance variables like so:
class Something
class << self
attr_accessor :things
end
end
Then you can write Something.things = 12 and it will work.
Just some clarification: class variables won't be accessible using attr_accessor. It's all about instance variables:
class SomeClass
class << self
attr_accessor :things
end
#things = []
end
because in Ruby, class is an instance of the class "Class" (God, I love to say that) and attr_accessor sets accessor methods for instance variables.
This is probably the simplest way.
class Parent
def self.things
##things ||= []
end
end
Parent.things << :car
p Parent.things
Аlso note that a singleton method is a method only for a single object. In Ruby, a Class is also an object, so it too can have singleton methods! So be aware of when you might be calling them.
Example:
class SomeClass
class << self
def test
end
end
end
test_obj = SomeClass.new
def test_obj.test_2
end
class << test_obj
def test_3
end
end
puts "Singleton methods of SomeClass"
puts SomeClass.singleton_methods
puts '------------------------------------------'
puts "Singleton methods of test_obj"
puts test_obj.singleton_methods
Singleton methods of SomeClass
test
Singleton methods of test_obj
test_2
test_3
Parent.class_variable_get(:##things)
That would be the built-in way. In most cases this should be sufficient I think. No need to have a class variable accessor in the instance.
class Parent
#things = []
singleton_class.send(:attr_accessor, :things)
end
This pattern is most useful when you are defining accessors dynamically or creating them inside a method:
class Foo
def self.add_accessor(name)
singleton_class.send(:attr_accessor, name)
end
end
Foo.add_accessor :things
Foo.things = [:car]
Foo.things # => [:car]
I have a bunch of classes with similiar logic like this
class ApiWrapper
class << self
attr_accessor :app_id, :app_key
def configure
yield self
end
end
end
I want to extract this logic to a module similar to Ruby Struct class to be able to do something like this
class ApiWrapper
include Configurable.instance :app_id, :app_key
end
How can I do this?
From documentation
fred = Module.new do
def meth1
"hello"
end
def meth2
"bye"
end
end
I'm trying to make a method similar to attr_reader but I can't seem to get the instance of the class that the method gets called in.
class Module
def modifiable_reader(*symbols)
# Right here is where it returns Klass instead of #<Klass:0x1df25e0 #readable="this">
mod = self
variables = symbols.collect { |sym| ("#" << sym.to_s).to_sym }
attr_reader *symbols
(class << ModifyMethods; self; end).instance_eval do
define_method(*symbols) do
mod.instance_variable_get(*variables)
end
end
end
end
class Object
module ModifyMethods; end
def modify(&block)
ModifyMethods.instance_eval(&block)
end
end
class Klass
modifiable_reader :readable
def initialize
#readable = "this"
end
end
my_klass = Klass.new
my_klass.modify do
puts "Readable: " << readable.to_s
end
I'm not sure what it is you're trying to do.
If it helps, the spell for attr_reader is something like this:
#!/usr/bin/ruby1.8
module Kernel
def my_attr_reader(symbol)
eval <<-EOS
def #{symbol}
##{symbol}
end
EOS
end
end
class Foo
my_attr_reader :foo
def initialize
#foo = 'foo'
end
end
p Foo.new.foo # => "foo"
What I can understand from your code is that you want to have the modify block to respond to the instance methods of Klass, that's as simple as:
class Klass
attr_reader :modifiable
alias_method :modify, :instance_eval
def initialize(m)
#modifiable = m
end
end
Klass.new('john').modify do
puts 'Readable %s' % modifiable
end
About this tidbit of code:
def modifiable_reader(*symbols)
# Right here is where it returns Klass instead of #<Klass:0x1df25e0 #readable="this">
mod = self
...
Probably this can give you a hint of what is going on:
Class.superclass # => Module
Klass.instance_of?(Class) # => true
Klass = Class.new do
def hello
'hello'
end
end
Klass.new.hello # => 'hello'
When you are adding methods to the Module class, you are also adding methods to the Class class, which will add an instance method to instances of Class (in this case your class Klass), at the end this means you are adding class methods on your Klass class