Ruby print out only the class methods (and not inherited methods) - ruby

I wish to only print out the methods that are declared in the class. This does the trick:
p (
Float.instance_methods -
Object.instance_methods -
Numeric.instance_methods
).count
p (
Float.instance_methods -
Object.instance_methods -
Numeric.instance_methods
)
STDOUT:
12
[:+, :-, :*, :/, :**, :to_f, :to_i, :nan?, :infinite?, :finite?, :to_r, :rationalize]
But isn't there an easier way to get these methods?
bonus info
p Float.methods.count # 117
p Float.instance_methods.count # 126
p Float.class.methods.count # 120

methods(regular=true) → array
Returns a list of the names of public
and protected methods of obj. This will include all the methods
accessible in obj's ancestors. If the optional parameter is false, it
returns an array of obj's public and protected singleton methods, the
array will not include methods in modules included in obj.
- https://ruby-doc.org/core-2.7.1/Object.html#method-i-methods
Module#instance_methods has the exact same signature.
module Bannable
def ban!
#banned = true
end
end
class Parent
include Bannable
def a_parent_instance_method
end
def self.a_parent_class_method
end
end
class Child < Parent
def a_child_instance_method
end
def self.a_child_class_method
end
end
irb(main):024:0> Child.methods(false)
=> [:a_child_class_method]
irb(main):025:0> Child.instance_methods(false)
=> [:a_child_instance_method]

Related

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

replacing static variable in the cod

class Numeric
##currencies = {'yen' => 0.013, 'euro' => 1.292, 'rupee' => 0.019}
def method_missing(method_id, *args, &block)
singular_currency = method_id.to_s.gsub(/s$/, '')
if ##currencies.has_key?(singular_currency)
self * ##currencies[singular_currency]
else
super
end
end
end
puts 3.yen
# Output is
# 0.039
My question is why wouldn't this code work if we replaced ##currencies with an instant variable #currencies and added attr_reader :currencies
Something like this
class Numeric
#currencies = {'yen' => 0.013, 'euro' => 1.292, 'rupee' => 0.019}
attr_accessor :currencies
def method_missing(method_id, *args, &block)
singular_currency = method_id.to_s.gsub(/s$/, '')
if #currencies.has_key?(singular_currency)
self * #currencies[singular_currency]
else
super
end
end
end
puts 3.yen
# Output
# method_missing': undefined method `has_key?' for nil:NilClass (NoMethodError)
# from Untitled.rb:15:in `<main>'
Isn't 3 already an instant of a class Numeric, therefore, the setter currencies should be able to work and return the proper hash combo?
EDIT: so method_missing is a static method then? Why isn't it defined with self.method_missing?
Setting #currencies within the context of a class declaration sets an instance variable on the class itself:
Numeric.instance_variable_get(:#currencies)
#=> {"yen"=>0.013, "euro"=>1.292, "rupee"=>0.019}
On the other hand, #currencies within the method_missing class and currencies accessor refers to the #currencies variable on a particular instance of numeric, which isn't defined:
Numeric.new.instance_variable_get(:#currencies)
#=> nil
You could fix this by defining the accessor on the class itself, and calling that accessor in the method_missing method:
class Numeric
#currencies = {'yen' => 0.013, 'euro' => 1.292, 'rupee' => 0.019}
class << self
attr_accessor :currencies
end
def method_missing(method_id, *args, &block)
singular_currency = method_id.to_s.gsub(/s$/, '')
if self.class.currencies.has_key?(singular_currency)
self * self.class.currencies[singular_currency]
else
super
end
end
end
Numeric.currencies
#=> {"yen"=>0.013, "euro"=>1.292, "rupee"=>0.019}
There's still a problem with this approach though. Even though the currencies accessor now refers to an instance variable on the class (and not an instance variable on a particular instance of that class as was the case before), #currencies is still only set on the Numeric class, not any of its subclasses:
Fixnum.currencies
#=> nil
To fix this, you can either modify the attribute accessor to automatically provide a default value for each individual class (so Fixnum and Float would each have their own separate #currencies variable), or go back to using class variables, like this:
class Numeric
##currencies = {'yen' => 0.013, 'euro' => 1.292, 'rupee' => 0.019}
def self.currencies
##currencies
end
def self.currencies= new_currencies
##currencies = new_currencies
end
def method_missing(method_id, *args, &block)
singular_currency = method_id.to_s.gsub(/s$/, '')
if ##currencies.has_key?(singular_currency)
self * ##currencies[singular_currency]
else
super
end
end
end
Numeric.currencies
#=> {"yen"=>0.013, "euro"=>1.292, "rupee"=>0.019}
Fixnum.currencies
#=> {"yen"=>0.013, "euro"=>1.292, "rupee"=>0.019}
1.yen
#=> 0.013
Numeric.currencies['bitcoin'] = 394.03
#=> 394.03
5.bitcoin
#=> 1970.1499999999999
You could do this:
class Numeric
#currencies = {'yen' => 0.013, 'euro' => 1.292, 'rupee' => 0.019}
class << self
attr_reader :currencies
end
def method_missing(method_id, *args, &block)
singular_currency = method_id.to_s.gsub(/s$/, '')
if Numeric.currencies.key?(singular_currency)
self * Numeric.currencies[singular_currency]
else
super
end
end
end
The lines:
class << self
attr_reader :currencies
end
create a read accessor for the class instance variable #currencies.
You may be inclined to write (as I did initially):
if self.class.currencies.key?(singular_currency)
but that doesn't work, because method_missing is invoked on 3, an instance of Fixnum, a subclass of Numeric. Recall that subclasses don't have direct access to their ancestor's class methods. That's why we need to explicitly identify Numeric as the receiver.
As:
Fixnum.ancestors => [Fixnum, Integer, Numeric,...]
we see that:
if self.class.superclass.superclass.currencies.key?(singular_currency)
would work with Fixnum's, but not with other subclasses of Numeric: Bignum and Float.
Instance variable are only available on instances of a class. Class variables are available to the entire class.
In other words, the #currencies.has_key? in your code is undefined because it can't see the #currencies on line 2.
The problem is that you're initializing #currencies outside of a method. Any code that isn't inside a method is evaluated in the context of the class object, not an instance of the class.
Take a look at this example class:
class Foo
#bar = "baz"
def self.bar; #bar; end
def bar; #bar; end
end
Now let's look at the result of those two methods we defined.
Foo.bar # => "baz"
Foo.new.bar # => nil
This means that #bar belongs to the Foo class, not an instance of Foo.
Your problem can be solved by initializing #currency in a method, typically initialize:
class Numeric
def initialize
#currency = ...
end
end

Dynamic method chain?

How can I call a nested hash of methods names on an object?
For example, given the following hash:
hash = {:a => {:b => {:c => :d}}}
I would like to create a method that, given the above hash, does the equivalent of the following:
object.send(:a).send(:b).send(:c).send(:d)
The idea is that I need to get a specific attribute from an unknown association (unknown to this method, but known to the programmer).
I would like to be able to specify a method chain to retrieve that attribute in the form of a nested hash. For example:
hash = {:manufacturer => {:addresses => {:first => :postal_code}}}
car.execute_method_hash(hash)
=> 90210
I'd use an array instead of a hash, because a hash allows inconsistencies (what if there is more than one key in a (sub)hash?).
object = Thing.new
object.call_methods [:a, :b, :c, :d]
Using an array, the following works:
# This is just a dummy class to allow introspection into what's happening
# Every method call returns self and puts the methods name.
class Thing
def method_missing(m, *args, &block)
puts m
self
end
end
# extend Object to introduce the call_methods method
class Object
def call_methods(methods)
methods.inject(self) do |obj, method|
obj.send method
end
end
end
Within call_methods we use inject in the array of symbols, so that we send every symbol to the result of the method execution that was returned by the previous method send. The result of the last send is automatically returned by inject.
There's a much simpler way.
class Object
def your_method
attributes = %w(thingy another.sub_thingy such.attribute.many.method.wow)
object = Object.find(...)
all_the_things << attributes.map{ |attr| object.send_chain(attr.split('.')) }
end
def send_chain(methods)
methods.inject(self, :try)
end
end
There is no predefined method, but you can define your own method for that:
class Object
def send_chain(chain)
k = chain.keys.first
v = chain.fetch(k)
r = send(k)
if v.kind_of?(Hash)
r.send_chain(v)
else
r.send(v)
end
end
end
class A
def a
B.new
end
end
class B
def b
C.new
end
end
class C
def c
D.new
end
end
class D
def d
12345
end
end
chain = { a: { b: { c: :d } } }
a = A.new
puts a.send_chain(chain) # 12345
Tested with http://ideone.com/mQpQmp

Delegate attribute methods to the parent object

I have the following class:
class Alphabet
attr_reader :letter_freqs, :statistic_letter
def initialize(lang)
#lang = lang
case lang
when :en
#alphabet = ('A'..'Z').to_a
#letter_freqs = { ... }
when :ru
#alphabet = ('А'..'Я').to_a.insert(6, 'Ё')
#letter_freqs = { ... }
...
end
#statistic_letter = #letter_freqs.max_by { |k, v| v }[0]
end
end
foo = Alphabet.new(:en)
The central member here is #alphabet.
I'd like to make it some sort of a container class to invoke Array methods directly like
foo[i]
foo.include?
instead of explicitly accessing #alphabet:
foo.alphabet[i]
foo.alphabet.include?
I know I could define a lot of methods like
def [](i)
#alphabet[i]
end
but I'm looking for a proper way of "inheriting" them.
You can use Forwardable (it is included in the Ruby standard library):
require 'forwardable'
class Alphabet
extend Forwardable
def_delegators :#alphabet, :[], :include?
def initialize
#alphabet = ('A'..'Z').to_a
end
end
foo = Alphabet.new
p foo[0] #=> "A"
p foo.include? 'ç' #=> false
If you wish to delegate all the methods not defined by your class you can use SimpleDelegator (also in the standard library); it lets you delegate all the methods that are not responded by the instance to an object specified by __setobj__:
require 'delegate'
class Alphabet < SimpleDelegator
def initialize
#alphabet = ('A'..'Z').to_a
__setobj__(#alphabet)
end
def index
'This is not #alphabet.index'
end
end
foo = Alphabet.new
p foo[0] #=> "A"
p foo.include? 'ç' #=> false
p foo.index #=> "This is not #alphabet.index"
When the delegate doesn't need to be dynamic you can arrange the master class to be a subclass of DelegateClass, passing the name of the class to be delegated as argument and calling super passing the object to be delegated in the #initialize method of the master class:
class Alphabet < DelegateClass(Array)
def initialize
#alphabet = ('A'..'Z').to_a
super(#alphabet)
end
More info about the delegation design pattern in Ruby here
You could extend the Forwardable module:
class Alphabet
require 'forwardable'
extend Forwardable
attr_accessor :alphabet
def initialize
#alphabet = [1,2,3]
end
def_delegator :#alphabet, :[], :include?
end
Then you can do:
alpha = Alphabet.new
alpha[1]==hey.alphabet[1]
=> true
Warning:
Don't try to delegate all methods (don't know if that's even possible) since they probably share some of the same method names such as class, which would probably make chaos.
In Ruby you can extend Objects like this.
class Array
def second
self[1]
end
end
[1, 2, 3, 4, 5].second
# => 2
Or if you want to inherit an Array.
class Foo < Array
def second
self[1]
end
end
[1, 2, 3, 4, 5].include? 2
# => true
[1, 2, 3, 4, 5].second
# => 2
If you have any further questions, comment and I will update the answer.

Resources