I'm reviewing a ruby codebase at the moment which makes use of metaprogramming. This means that I can't always grep for the names of methods to find out where they're defined.
Is there a way to use ruby reflection to resolve which module or class contains the definition of the method?
For example, if I'm reviewing class A
which has a long chain of classes and modules that are included in it's definition. Given an instance a = A.new(a)
, is there some functionality in ruby I can use to retrieve the class/module that defines a.some_method
?
CodePudding user response:
In Ruby, you can get access to a Method
reflective proxy object that represents a method using the Object#method
method:
foo = []
method = foo.method(:find)
#=> #<Method: Array(Enumerable)#find(*)>
You can ask the method about its owner using the Method#owner
method:
method.owner
#=> Enumerable
It is not really possible to distinguish attributes programmatically. An attribute is more about how a method is used than how it is defined.
You can get the parameter list of a method using the Method#parameters
method, but that does not get you all the way there. Not having parameters is a necessary but not sufficient condition for an attribute.
Example:
class Foo
def bar; 42 end # attribute
def baz; puts 42 end # not an attribute
end
foo = Foo.new
foo.method(:bar).parameters
#=> []
foo.method(:baz).parameters
#=> []
You can try every method of Method
or UnboundMethod
you can think of, you can try every meta programming trick there is, you simply will not be able to find anything which distinguishes bar
being an attribute from baz
not being an attribute.
Even worse, methods like Array#length
, which clearly are attributes are typically not called attributes. So, even if there were a way to programmatically distinguish attributes from other parameter-less methods, you still wouldn't be able to determine whether or not something is thought of as an attribute. Note that in Rubinius, for example, Array#length
is clearly even implemented as an attribute, but it would still typically not be thought of as one.
CodePudding user response:
Not a standard ruby way, but pry
gem has a handy ls
command, where you could pass your instance as parameter and it will display every method defined for the instance and the classes where those are defined.
For example, checking the methods of an integer:
pry(main)(deve)> ls 1
Comparable#methods: between?
Numeric#methods:
@ as_json coerce days exabyte gigabyte html_safe? in_milliseconds megabytes petabyte pretty_print real? second terabyte week
abs2 blank? conj duplicable? exabytes gigabytes i kilobyte minute petabytes pretty_print_cycle rect seconds terabytes weeks
angle byte conjugate encode_json fortnight hour imag kilobytes minutes phase quo rectangular singleton_method_added to_c
arg bytes day eql? fortnights hours imaginary megabyte nonzero? polar real remainder step to_formatted_s
RQRCode::CoreExtensions::Integer::Bitwise#methods: rszf
Integer#methods:
ceil denominator floor gcdlcm lcm months next ord ordinalize rationalize times to_d to_int to_r upto years
chr downto gcd integer? month multiple_of? numerator ordinal pred round to_bn to_i to_json_with_active_support_encoder truncate year
JSON::Ext::Generator::GeneratorMethods::Fixnum#methods: to_json
Fixnum#methods:
% * -@ < <= == > >> ^ bit_length div even? inspect modulo paragraph sentence size to_csv to_f to_s words |
& ** - / << <=> === >= [] abs dclone divmod fdiv magnitude odd? paragraphs sentences succ to_default_s to_msgpack word zero? ~
a string:
pry(main)(deve)> ls 'asdf'
Comparable#methods: < <= > >= between?
JSON::Ext::Generator::GeneratorMethods::String#methods: to_json_raw to_json_raw_object to_json_without_active_support_encoder
String#methods:
% blueish codepoints each_line greenish issjis next pale rstrip! strip to_datetime tr_s
* bold colorize empty? gsub isutf8 next! parameterize safe_constantize strip! to_f tr_s!
bullet_class_name colorized? encode gsub! kconv oct parse_csv scan strip_heredoc to_i trueish?
<< bytes concat encode! hash last on_black partition scanf sub to_json truncate
<=> bytesize constantize encode_json hex length on_blue pathmap scrub sub! to_json_with_active_support_encoder truncate_words
== byteslice count encoding hide light_black on_cyan pluralize scrub! succ to_msgpack uncolorize
=== camelcase crypt end_with? html_safe light_blue on_green prepend setbyte succ! to_r underline
=~ camelize cyan ends_with? humanize light_cyan on_light_black purple shellescape sum to_s underscore
[] capitalize cyanish eql? in_time_zone light_green on_light_blue purpleish shellsplit swap to_str unicode_normalize
[]= capitalize! dasherize exclude? include? light_magenta on_light_cyan red singularize swapcase to_sym unicode_normalize!
acts_like_string? casecmp deconstantize ext indent light_red on_light_green redish size swapcase! to_time unicode_normalized?
as_json center delete first indent! light_white on_light_magenta remove slice tableize toeuc unpack
ascii_only? chars delete! force_encoding index light_yellow on_light_red remove! slice! titlecase tojis upcase
at chomp demodulize foreign_key inquiry lines on_light_white replace split titleize tolocale upcase!
b chomp! downcase freeze insert ljust on_light_yellow reverse squeeze to tosjis upto
black chop downcase! from inspect lstrip on_magenta reverse! squeeze! to_builder toutf16 valid_encoding?
blank? chop! dump getbyte intern lstrip! on_red rindex squish to_c toutf32 white
blink chr each_byte gray is_utf8? magenta on_white rjust squish! to_csv toutf8 whiteish
block_scanf classify each_char grayish iseuc match on_yellow rpartition start_with? to_d tr yellow
blue clear each_codepoint green isjis mb_chars ord rstrip starts_with? to_date tr! yellowish
or any other instance you have in mind.