Struct visible only within a class/ code file ruby - ruby

I want to use structs within my classes to save on memory, but I want my code to be easily used by others.Say I want to use a new Struct, name it "KE". I want this Struct to be only visible from within a class I, that would be use it. I.e., if anyone who uses my code defines elsewhere class/struct "KE", it won't override my "KE", and mine won't override theirs. Having it in the same code text file is good too.

You can put your code into a module which will act as a namespace, e.g.:
module Foo
KE = Struct.new(:name)
# remaining code
end
Within the Foo namespace you can refer to the struct just via KE, e.g.:
module Foo
# ...
def self.build_ke(name)
KE.new(name)
end
end
Foo.build_ke('abc')
#=> #<struct Foo::KE name="abc">
Outside of Foo however, you have to use the Foo:: prefix:
Foo::KE.new('def')
#=> #<struct Foo::KE name="def">
Other KE constants won't interfere with your KE:
KE = :top_level
module Bar
KE = :nested_in_bar
end
KE #=> :top_level
Bar::KE #=> :nested_in_bar
Foo::KE #=> Foo::KE # <- your struct
To "hide" the struct, you could make the constant private via private_constant:
module Foo
KE = Struct.new(:name)
private_constant :KE
def self.build_ke(name)
KE.new(name)
end
end
Foo.build_ke #=> #<struct Foo::KE name="abc">
Foo::KE #=> NameError: private constant Foo::KE referenced
Or you could use an anonymous struct by not assigning it to a constant in the first place, e.g. via a private class method:
module Foo
def self.ke_class
#ke_class ||= Struct.new(:name)
end
private_class_method :ke_class
def self.build_ke(name)
ke_class.new(name)
end
end
Foo.build_ke('abc') #=> #<struct name="abc">
Foo.ke_class #=> NoMethodError: private method `ke_class' called for Foo:Module

Related

How does an instance method declaration outside a class work in Ruby?

Well, the title of the question may seem improper, but I want to ask how does the following code actually work?
class Klass < Struct.new(:x, :y, :z)
end
public
def foo
bar
end
def inspect
"Can't override inspect -> #{self.class}"
end
private
def bar
[x, y, z]
end
def baz
send(:class)
end
obj = Klass.new(1, 2, 3)
p obj.foo # => [1, 2, 3]
p obj # => #<struct Klass x=1, y=2, z=3>
p Klass.ancestors # => [Klass, #<Class:0x0000563835a45600>, Struct, Enumerable, Object, Kernel, BasicObject]
Now, this looks like a very bad practice. But how does this actually work?
Methods defined outside any class are added to the Object class, which is one of Struct ancestors (that is, Struct inherits all Object methods).
Try this in a new irb session:
self.class
#=> Object
self.respond_to?(:foo)
#=> false
def foo
"foo"
end
#=> :foo
self.respond_to?(:foo)
#=> true
In the provided example, you create an instance of Klass (which include Struct and Object in its ancestors chain), so when a new Klass instance is created, it includes all Object's methods, including the new foo method.

Define default accessor of class instance

Is it possible to define a default accessor for an instance of my class?
I have a class:
class Foo
def initialize(a, b)
#a = a
#b = b
end
end
I want to create a new instance of this class:
foo = Foo.new(:a, :b)
# => #<Foo:0x00007f9e04c7b240 #a=:a, #b=:b>
Creating a new array returns a real value:
arr = Array.new(2, :bar)
# => [:bar, :bar]
How can I set a default accessor of my own class instance so that when I call foo, I get a real value instead of #<Foo:0x00007f9e04c7b240 #a=:a, #b=:b>?
When you see the output on the IRB console, all it's doing is calling inspect on the object. So, all you need to do is (like Array), define an inspect method for you custom object:
class Foo
def initialize(a, b)
#a = a
#b = b
end
def inspect
%["Real value" for Foo with #{#a} and #{#b}]
end
end
foo = Foo.new(:a, :b) # => "Real value" for Foo with a and b
What you see by default, is just the implementation of Object#inspect, so you could if you really wanted to, just override that for all objects (that don't have a custom implementation):
class Object
def inspect
"Custom Inspection of #{self.class.name}"
end
end
# Foo2 is the same as Foo just without the `inspect` method)
foo_2 = Foo2.new(:a, :b) # => Custom Inspection of Foo2
I'd avoid doing this for Object#inspect though, since people are used to and expect to see the default format and changing things might throw them off.

Avoid class pollution while including module

Is there a concise way to limit method visibility within the module while including it? In other words, I'd like to limit polluting the class with helper methods only used in the included module.
module Bar
def call
hide_me
end
private
# make this method only callable within this module
def hide_me
'visible'
end
end
class Foo
include Bar
def unrelated_method
hide_me
end
end
# that's ok
Foo.new.call #=> 'visible'
# that's not
Foo.new.unrelated_method #=> 'visible'
I'm ok with calling it via Bar.instance_method(:hide_me).bind(self).call, I just don't want to worry about accessing or redefining a helper method from some module.
You can wrap a class into the module and use private methods within the class, like so:
module Bar
def call
BarClass.new.call
end
class BarClass
def call
hide_me
end
private
def hide_me
puts "invisible"
end
end
end
class Foo
include Bar
def call_bar
call
end
def this_method_fails
hide_me
end
end
One can do what you want by including the module to the class and then undefining the unwanted included methods.
First construct a module.
module M
def cat
puts "meow"
end
def dog
puts "woof"
end
def owl
puts "who?"
end
private
def frog
puts "ribbit"
end
def pig
puts "oink"
end
end
Confirm the methods now exist.
M.instance_methods(false)
#=> [:cat, :dog, :owl]
M.private_instance_methods(false)
#=> [:frog, :pig]
Create the class, including the module M.
class C
def froggie
frog
end
def cat
puts "meow-meow"
end
include M
end
Check the instance methods.
C.instance_methods & [:cat, :dog, :owl, :froggie]
#=> [:cat, :froggie, :dog, :owl]
C.private_instance_methods & [:frog, :pig]
#=> [:frog, :pig]
and confirm :cat is owned by C and not by M.
C.instance_method(:cat).owner
#=> C
Now use the method Module#undef_method to undefine the unwanted methods from the module.
class C
[:cat, :owl, :pig].each { |m|
undef_method(m) unless instance_method(m).owner == self }
end
The unless... clause is needed so that the instance method :cat defined in C is not undefined.
Confirm the methods were undefined.
C.instance_methods & [[:cat, :dog, :owl, :froggie]
#=> [:cat, :froggie, :dog]
C.private_instance_methods & [:frog, :pig]
#=> [:frog]
Execute the methods.
c = C.new
c.cat
#=> "meow-meow"
c.dog
#=> "woof"
c.froggie
#=> "ribbit"

Behaviour of instance_eval

My understanding of instance_eval was that if I have module M then the following were equivalent:
module M
def foo
:foo
end
end
class C
class << self
include M
end
end
puts C.foo
equivalent to:
module M
def foo
:foo
end
end
class C
end
C.instance_eval do
include M
end
puts C.foo
However, the first example prints :foo and the second throws a NoMethodError? (Ruby 2.3.0)
In both cases above, if I had replaced:
include M
with:
def foo
:foo
end
ie directly defining the method rather than including a module then both cases would have resulted in a C.foo method being defined. Should I be surprised at this difference between include and defining the method directly?
Or does it ever even make sense to call include within the context of instance_eval? Should it only ever be called within a class_eval?
In each of these cases, what object are you calling include on? In your first example, you're calling include on C's singleton class:
class C
class << self
p self == C.singleton_class
include M
end
end
# => true
p C.foo
# => :foo
...so your include line is equivalent to C.singleton_class.include(M).
In your second example, however, you're calling include on C itself:
class C
end
C.instance_eval do
p self == C
include M
end
# => true
p C.foo
# => NoMethodError: undefined method `foo' for C:Class
p C.new.foo
# => :foo
...so you're doing the equivalent of C.include(M), which is the same as:
class C
p self == C
include M
end
# => true
p C.new.foo
# => :foo
What would work like you want would be to call instance_eval on C's singleton class:
class D
end
D.singleton_class.instance_eval do
p self == D.singleton_class
include M
end
# => true
p D.foo
# => :foo
Module#class_eval() is very different from Object#instance_eval(). The instance_eval() only changes self, while class_eval() changes both self and the current class.
Unlike in your example, you can alter class_instance vars using instance_eval though, because they are in the object scope as MyClass is a singleton instance of class Class.
class MyClass
#class_instance_var = 100
##class_var = 100
def self.disp
#class_instance_var
end
def self.class_var
##class_var
end
def some_inst_method
12
end
end
MyClass.instance_eval do
#class_instance_var = 500
def self.cls_method
##class_var = 200
'Class method added'
end
def inst_method
:inst
end
end
MyClass.disp
#=> 500
MyClass.cls_method
#=> 'Class method added'
MyClass.class_var
#=> 100
MyClass.new.inst_method
# undefined method `inst_method' for #<MyClass:0x0055d8e4baf320>
In simple language.
If you have a look in the upper class defn code as an interpreter, you notice that there are two scopes class scope and object scope. class vars and instance methods are accessible from object scope and does not fall under jurisdiction of instance_eval() so it skips such codes.
Why? because, as the name suggests, its supposed to alter the Class's instance(MyClass)'s properties not other object's properties like MyClass's any object's properties. Also, class variables don’t really belong to classes—they belong to class hierarchies.
If you want to open an object that is not a class, then you can
safely use instance_eval(). But, if you want to open a class definition and define methods with def or include some module, then class_eval() should be your pick.
By changing the current class, class_eval() effectively reopens the class, just like the class keyword does. And, this is what you are trying to achieve in this question.
MyClass.class_eval do
def inst_method
:inst
end
end
MyClass.new.inst_method
#=> :inst

Overwrite class method from another module

I want to modify the existing module A from a library:
module A
class << self
def foo
bar('Baz')
end
private
def bar(val)
val.upcase
end
end
end
A.foo
=> "BAZ"
module B
extend A
def self.bar(val)
val.downcase
end
end
B.foo # hoping for 'baz', but instead:
NoMethodError: undefined method `foo' for B:Module
Is there a way to re-use method .foo from A and only modify method .bar?
extend A doesn't work, because foo and bar are no instance methods of A, but of A's singleton class. To re-use those methods, you have to either create a copy of A as mudasobwa described, or you can use a refinement of A's singleton class like this:
module B
extend(refine(A.singleton_class) do
def bar(val)
val.downcase
end
end)
end
B.foo # => "baz"
You cannot use extend A.singleton_class as extend doesn't accept a class as argument. refine returns a module which is exactly what's needed.
Assuming you have A declared as above, the following code would do:
▶ B = A.clone
#⇒ B
▶ (class << B; self; end).send :define_method, :bar do |val|
val.downcase
end
▶ A.foo
#⇒ "BAZ"
▶ B.foo
#⇒ "baz"
There probably should be less tricky way, but I can’t yet figure it out.

Resources