A more concise way of assigning instance variables from initialize? - ruby

I have a few classes I'm using to contain data (and a few methods on it) in Ruby. For instance:
class Foo
def initialize(bar, biz, baz)
#bar=bar
#biz=biz
#baz=baz
end
end
Is there a less repetitive way of propagating these initialization arguments into instance variables?

One liner, but I find this can obscure things:
#bar,#biz,#baz = bar,biz,baz

class Foo
def initialize(*args)
raise ArgumentError unless args.length == 3
#bar, #biz, #baz = args
end
end

One quick way is to use a Struct:
class Foo < Struct.new(:bar, :biz, :baz)
# custom methods go here
end
Struct.new will return a class with the initializer and accessors set up for you (other than that, it's just a normal class). If you don't need any custom methods, you can also define a struct inline (e.g. Foo = Struct.new(:bar, :biz, :baz)).

Related

How to namespace constants inside anonymous class definitions?

When creating anonymous classes through Class.new, they don't seem to have their own namespace for constants:
klass1 = Class.new do
FOO = "foo"
end
klass2 = Class.new do
FOO = "bar"
end
This gives warning: already initialized constant FOO and looks like it's right:
> klass1.const_get(:FOO)
"bar"
> klass2.const_get(:FOO)
"bar"
> FOO
"bar"
I was going to use this approach in a simple DSL for defining addons for an application, something like this:
class App
class AddonBase
attr_reader :session
def initialize(session)
#session = session
end
end
def self.addons
#addons ||= {}
end
def self.addon(name, &block)
addons[name] = Class.new(AddonBase, &block)
end
end
This works fine for simple add-ons but if defining constants, they will be under Object:: instead of becoming addons[name]::CONSTANT:
App.addon "addon1" do
PATH="/var/run/foo"
def execute
File.touch(PATH)
end
end
App.addon "addon2" do
PATH="/etc/app/config"
def execute
File.unlink(PATH)
end
end
# warning: already initialized constant PATH
The constants could be anything and the add-ons could even define their own utility subclasses, so it's not just about replacing PATH with a function.
Is there some way to work around this?
When creating anonymous classes through Class.new, they don't seem to have their own namespace for constants
They do, you can use const_set to define constants in anonymous classes:
klass1 = Class.new do
const_set(:FOO, 'foo')
end
klass2 = Class.new do
const_set(:FOO, 'bar')
end
klass1::FOO #=> "foo"
klass2::FOO #=> "bar"
Or via self::
klass1 = Class.new do
self::FOO = 'foo'
end
klass2 = Class.new do
self::FOO = 'bar'
end
klass1::FOO #=> "foo"
klass2::FOO #=> "bar"
When creating anonymous classes through Class.new, they don't seem to have their own namespace for constants
Sure, by the definition of the word “anonymous.” Compare two following snippets:
class C1; puts "|#{name}|"; end
#⇒ |C1|
C2 = Class.new { puts "|#{name}|" }
#⇒ ||
Unless assigned to the constant, the class has no name and hence all constants defined inside go to Object namespace. That said, the warning here is actually pointing out to error and Object::FOO = "bar" overrides Object::FOO = "foo" constant.
That said, one cannot use constants in this scenario. Use class-level instance variables instead, or construct unique constant names manually (I would advise avoiding polluting Object class with a bunch of unrelated constants, though.)
Actually the problem is how to define a class using a proc including constant definitions. As has already been said it is not possible the way you did it, since the proc gets class_eval'd and that doesn't allow to define constants.
I suggest another approach. Can you use modules instead of procs to define new addons mixing a module into a class?
Example:
module AddonModule
FOO = "foo"
end
klass = Class.new
klass.include AddonModule
klass::FOO # => "foo"
Usage in your DSL:
def self.addon(name, addon_module)
addon = Class.new(AddonBase)
addon.include addon_module
addons[name] = addon
end

Why are setter methods not used in initialization?

Recently I've been reading "Practical Object Oriented Design in Ruby", and I noticed one of the best practices was to use accessor methods instead of directly grabbing the #instance_variable. For example:
class Foo
attr_accessor :bar
def initialize(my_argument)
#bar = my_argument
end
# bad
# def lorem_ipsum
# #bar * 999
# end
# good
def lorem_ipsum
bar * 999
end
end
It makes sense to keep things DRY, and, in case I need to process #bar somehow before actually grabbing its value. However, I noticed that the initialize method sets the value of the #bar instance variable directly:
class Foo
attr_accessor :bar
def initialize(my_argument)
#bar = my_argument #<-- why isn't self.bar = my_argument used here?
end
Is there a reason for this? Shouldn't the setter method be used instead of directly using the = operator to set the value of the instance variable?
You're right, it would make much more sense to do
class Foo
attr_accessor :bar
def initialize(my_argument)
self.bar = my_argument
end
end
Arguments differ as to whether you should respect encapsulation within the object itself or not, but if you believe in that, then, yes, you should do this.
The initializer SETS the value upon initialization. The accessor lets you access (read/write) via the symbol after the object is already instantiated.
This post might help you understand:
What is attr_accessor in Ruby?
Actually, the setter can be used in initialize the same as in other methods, but setter cannot be used without a receiver.
I think you can use a_foo.bar= or self.bar=, but cannot use bar= without a receiver, because, in the later case, bar will be treated as a local variable rather than a setter method:
class Song
attr_accessor :name
def initialize(name)
self.name = name
end
def test_setter1(value)
#name = value
end
def test_setter2(value)
name = value #name is local variable
end
end
s = Song.new("Mike")
p s
s.test_setter1("John")
p s
s.test_setter2("Rosy")
p s
This results in:
#<Song:0x23a50b8 #name="Mike">
#<Song:0x23a50b8 #name="John">
#<Song:0x23a50b8 #name="John">
While you can use the setter in the initialization as shown in #uncutstone's answer, you cannot use it as you've proposed in the comment in your code.
The problem is that Ruby would interpret:
bar = my_argument
as an assignment to the bar local variable rather than an invocation of the bar= method.
This is discussed rather extensively in "Why do Ruby setters need "self." qualification within the class?".

Dynamically define named classes in Ruby

I am writing an internal DSL in Ruby. For this, I need to programmatically create named classes and nested classes. What is the best way to do so? I recon that there are two ways to do so:
Use Class.new to create an anonymous class, then use define_method to add methods to it, and finally call const_set to add them as named constants to some namespace.
Use some sort of eval
I've tested the first way and it worked, but being new to Ruby, I am not sure that putting classes as constants is the right way.
Are there other, better ways? If not, which of the above is preferable?
If you want to create a class with a dynamic name, you'll have to do almost exactly what you said. However, you do not need to use define_method. You can just pass a block to Class.new in which you initialize the class. This is semantically identical to the contents of class/end.
Remember with const_set, to be conscientious of the receiver (self) in that scope. If you want the class defined globally you will need to call const_set on the TopLevel module (which varies in name and detail by Ruby).
a_new_class = Class.new(Object) do
attr_accessor :x
def initialize(x)
print #{self.class} initialized with #{x}"
#x = x
end
end
SomeModule.const_set("ClassName", a_new_class)
c = ClassName.new(10)
...
You don't really need to use const_set. The return value of Class.new can be assigned to
a constant and the block of Class.new is class_eval.
class Ancestor; end
SomeClass = Class.new(Ancestor) do
def initialize(var)
print "#{self.class} initialized with #{var}"
end
end
=> SomeClass
SomeClass.new("foo")
# SomeClass initialized with foo=> #<SomeClass:0x668b68>
Should be like this
a_new_class = Class.new(Object) do
attr_accessor :x
def initialize(x)
#x = x
end
end
SomeModule = Module.new
SomeModule.const_set("ClassName", a_new_class)
c = SomeModule::ClassName.new(10)

Is there a better way of doing class_eval() to extract class variables, in Ruby?

I personally don't have anything against this, apart from the fact that's is long, but what really bothers me is the word eval.
I do a lot of stuff in JavaScript and I run from anything resembling eval like it's the devil, I also don't fancy the fact that the parameter is a string (again, probably because it's eval).
I know I could write my own method to fix the method-name-length problem, my 'method name issue' and the parameter-being-a-string thingy, but what I really want to know is: Is there a better, shorter, fancier, yet native, way of doing class_eval to extract class variables?
Side note: I know about the existence of class_variable_get() and class_variables(), but they don't really look appealing to me; horribly long, aren't they?
EDIT: Updated the question to be more specific.
Thanks!
Use class_variable_get, but only if you must
class_variable_get is the better way, other than the fact that it is not "appealing" to you. If you are reaching inside a class and breaking encapsulation, perhaps it is appropriate to have this extra barrier to indicate that you're doing something wrong.
Create accessor methods for the variables you want to access
If these are your classes, and accessing the variables doesn't break encapsulation, then you should create class accessor methods for them to make it easier and prettier:
class Foo
def self.bar
##bar
end
end
p Foo.bar
If this is your class, however, are you sure that you need class variables? If you don't understand the implications (see below), you may actually be wanting instance variables of the class itself:
class Foo
class << self
attr_accessor :bar
end
end
Foo.bar = 42
p Foo.bar
The behavior of class variables
Class variables appear to newcomers like the right way to store information at a class level, mostly because of the name. They are also convenient because you can use the same syntax to read and write them whether you are in a method of the class or an instance method. However, class variables are shared between a class and all its subclasses.
For example, consider the following code:
class Rectangle
def self.instances
##instances ||= []
end
def initialize
(##instances ||= []) << self
end
end
class Square < Rectangle
def initialize
super
end
end
2.times{ Rectangle.new }
p Rectangle.instances
#=> [#<Rectangle:0x25c7808>, #<Rectangle:0x25c77d8>]
Square.new
p Square.instances
#=> [#<Rectangle:0x25c7808>, #<Rectangle:0x25c77d8>, #<Square:0x25c76d0>]
Ack! Rectangles are not squares! Here's a better way to do the same thing:
class Rectangle
def self.instances
#instances ||= []
end
def initialize
self.class.instances << self
end
end
class Square < Rectangle
def initialize
super
end
end
2.times{ Rectangle.new }
p Rectangle.instances
#=> [#<Rectangle:0x25c7808>, #<Rectangle:0x25c77d8>]
2.times{ Square.new }
p Square.instances
#=> [#<Square:0x25c76d0>, #<Square:0x25c76b8>]
By creating an instance variable and accesor methods on the class itself—which happens to be an instance of the Class class, similar to MyClass = Class.new—all instances of the class (and outsiders) have a common, clean location to read/write information that is not shared between other classes.
Note that explicitly tracking every instance created will prevent garbage collection on 'unused' instances. Use code like the above carefully.
Using class_eval in a cleaner manner
Finally, if you're going to use class_eval, note that it also has a block form that doesn't have to parse and lex the string to evaluate it:
Foo.class_eval('##bar') # ugh
Foo.class_eval{ ##bar } # yum

How do I call a super class method

I have two classes A, and B. Class B overrides the foo method of class A. Class B has a bar method where I want to call the foo method of the super class. What is the syntax for such a call?
class A
def foo
"hello"
end
end
class B < A
def foo
super + " world"
end
def bar
# how to call the `foo` method of the super class?
# something similar to
super.foo
end
end
For class methods I can call the methods up the inheritance chain by explicitly prefixing the class name. I wonder if there is a similar idiom for instance methods.
class P
def self.x
"x"
end
end
class Q < P
def self.x
super + " x"
end
def self.y
P.x
end
end
Edit
My use case is general. For a specific case I know I can use alias technique. This is a common feature in Java or C++, so I am curious to know if it is possible to do this without adding extra code.
In Ruby 2.2, you can use Method#super_method now
For example:
class B < A
def foo
super + " world"
end
def bar
method(:foo).super_method.call
end
end
Ref: https://bugs.ruby-lang.org/issues/9781#change-48164 and https://www.ruby-forum.com/topic/5356938
You can do:
def bar
self.class.superclass.instance_method(:foo).bind(self).call
end
In this particular case you can just alias :bar :foo before def foo in class B to rename the old foo to bar, but of course you can alias to any name you like and call it from that. This question has some alternative ways to do it further down the inheritance tree.
You can alias old_foo foo before redefining it to keep the old implementation around under a new name. (Technically it is possible to take a superclass's implementation and bind it to an instance of a subclass, but it's hacky, not at all idiomatic and probably pretty slow in most implementation to boot.)
Based on #Sony's answer.
In case when you want to call the method method on some my_object and it's already overriden somewhere several classes higher (like for the Net::HTTPRequest#method), instead of doing .superclass.superclass.superclass use the:
Object.instance_method(:method).bind(my_object)
Like this:
p Object.instance_method(:method).bind(request).call(:basic_auth).source_location

Resources