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?
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.
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.
Related
I changed how I was doing things but at one point I needed to append a string to the end of an array of strings and I wanted it guaranteed to be lexicologically after all of the other strings. For integers this would be MAXINT or some similar constant.
In my particular case, I'm using Ruby. I want some Ω such that "Ω" > "s" for all s.
There's no such string satisfying that behavior. Such a string would have to be an infinite sequence of the largest Unicode codepoint, and strings in Ruby cannot be infinite.
However, we can always make a class for which we control the comparison operator.
class OmegaType
include Comparable
def <=>(other)
if other.is_a? OmegaType
0
else
1
end
end
end
Omega = OmegaType.new
And then, if we really want to, we can monkeypatch String to agree with our assessment.
class String
alias old_cmp <=>
def <=>(other)
if other.is_a? OmegaType
-1
else
self.old_cmp other
end
end
end
Now, we find
puts(Omega > 'a') # true
puts('a' < Omega) # true
But is this good practice? Not really. It's probably best to go through whatever list of strings your actual program uses and just pick the "maximum" string to be your "after" value. Or pick something that works in your use case. For instance, if all the strings you're dealing with are alphanumeric, then pick a Unicode character larger than all alphanumerics.
A follow up on the question How to create a random time between a range
.
Kernel#rand works with Time range:
require 'time'
rand(Time.parse('9 am')..Time.parse('11:30 am'))
But when I tried with a custom class, I ended up with the error:
`rand': no implicit conversion of Range into Integer (TypeError)
class Int
include Comparable
attr_reader :num
def initialize(num)
#num = num
end
def succ
Int.new(num + 1)
end
def <=>(other)
num <=> other.num
end
def to_s
"Int(#{num})"
end
def to_int
#num
end
alias_method :inspect, :to_s
end
puts rand(Int.new(1)..Int.new(3))
Why? What am I missing in the custom class? Can we use such a custom class in rand(Range)?
I don't know of any documentation for what specifically Kernel#rand expects from a Range argument but we can get a look at what's going on by overriding respond_to? in your class and then watching as things fall apart:
def respond_to?(m)
puts "They want us to support #{m}"
super
end
Doing that tells us that rand wants to call the #- and #+ methods on your Int instances. This does make some sense given that rand(a..b) is designed for working with integers.
So we throw in quick'n'dirty implementations of addition and subtraction:
def -(other)
self.class.new(to_int - other.to_int)
end
def +(other)
self.class.new(to_int + other.to_int)
end
and we start getting rand Ints out of our calls to rand.
I'm not sure where (or if) this is documented so you'll have to excuse a bit of hand waving. I normally spend some time rooting around the Ruby source code to answer this sort of question but I lack the time right now.
To add a bit more to #mu-is-too-short's answer, I checked the source of Random#rand and the following is the current implementation logic for rand(Range):
Get the begin, end, and vmax from the Range object (call range_values), where vmax is computed as (call id_minus):
vmax = end - begin
vmax will be used as the upper bound of the random number generation later.
This requires the custom class to have - method defined.
Generate a random number based on the type of vmax:
If it is not Float and can be coerced to Integer (rb_check_to_int), generate a random Integer less than vmax.
In this case, the - method should either return an Integer, or an object which responds to to_int method.
If it is Numeric and can be converted to Float with to_f, (rb_check_to_float), generate a random Float number less than vmax.
In this case, the - method should return a Numeric number which can be converted to Float with method to_f.
Add the random number to begin to yield the result (call id_add).
This requires the custom class to have + method defined, which accepts the result of the random number generated in step 2 (either Integer, or Float) and returns the final result for rand.
I believe this error is because you are trying to use rand() on objects of your custom class.
`rand': no implicit conversion of Range into Integer (TypeError)
This error message clearly mentions that ruby was unable to convert your range into integer. Based on your code snippet, following works and might be what you are looking for.
puts rand(Int.new(1).to_int..Int.new(3).to_int)
Substrings work where "hello"[0..2] returns "hel"
Is there an integer equivalent, that returns the sub-bits, as in 9[1..3] returns 4 or "100"?
Context:
I'm trying to do a genetic algorithm, and there is a cross over stage where you split two chromosomes in two, then swap halves, e.g. "10101010" crossed with "00000000" at i = 4 would be "00001010", "10100000"
However, I'm doing this a few million times, so using strings is inefficient, so is there a bitwise way to do this?
You might consider refining the class Fixnum to permit Fixnum#[]'s argument to be either a bit index or a range of bit indices.
module StabBitRange
def [](arg)
case arg
when Range
mask = arg.reduce(0) { |n, idx| n |= (1 << idx) }
(self & mask) >> arg.first
else
super
end
end
end
module StabBits
refine Fixnum do
prepend StabBitRange
end
end
See Refinements and Module#prepend. Prepend StabBitRange moves the methods contained therein (here just one) before the methods with the same names in Fixnum in the ancestor chain. The alternative (commonly used before Prepend was introduced in Ruby v2.0) is to alias Fixnum#[] and then redefine Fixnum[] to be the new method that takes either a bit index or a range of bit indices. (See the edit history of this answer to see how that is done.) I expect readers will agree that Prepend is a more sensible way of doing that.
To make this code available for use we need only invoke the keyword using.
using StabBits
n = 682
n.to_s(2) #=> "1010101010"
n[0] #=> 0
n[1] #=> 1
n[2] #=> 0
n[0..7] #=> 170 170.to_s(2) => "10101010"
n[1..7] #=> 85 85.to_s(2) => "1010101"
n[2..6] #=> 10 10.to_s(2) => "1010"
When StabBitRange#\[\] is called and the argument is not a range, super invokes Fixnum#[] and passes the argument. That method handles argument errors as well as returning the desired bit when there are no errors.
When this code is run in IRB, the following exception is raised: RuntimeError: main.using is permitted only at top level. That's because IRB is running at the top level, but code run within IRB is running at a higher level from the perspective of the Ruby parser. To run this in IRB it is necessary to enclose using StabBits and the code following in a module.
I know a bit about ruby way to handle objects and references. The replace stuff, ect ...
I know it d'ont work on fixnum, cause the var is the fixnum. But i wish to change the value of a fixnum inside a function, and that the value changed in the ouside var.
How can i do this ?
I guess i can use a string like this "1" but that's quite dirty.
Ruby will always pass-by-reference (because everything is an object) but Fixnum lacks any methods that allow you to mutate the value. See "void foo(int &x) -> Ruby? Passing integers by reference?" for more details.
You can either return a value that you then assign to your variable, like so:
a = 5
def do_something(value)
return 1 #this could be more complicated and depend on the value passed in
end
a = do_something(a)
or you could wrap your value in an object such as a Hash and have it updated that way.
a = {:value => 5}
def do_something(dict)
dict[:value] = 1
end
do_something(a) #now a[:value] is 1 outside the function
Hope this helps.
You could pass an array with a single number, like [1], or a hash like {value: 1}. Less ugly than a string, as your number itself remains a number, but less overhead than a new class...
When I was building a game I had the same problem you have. There was a numeric score that represented how many zombies you've killed and I needed to manually keep it in sync between Player (that incremented the score), ScoreBar and ScoreScreen (that displayed the score). The solution I've found was creating a separate class for the score that will wrap the value and mutate it:
class Score
def initialize(value = 0)
#value = value
end
def increment
#value += 1
end
def to_i
#value
end
def to_s
#value.to_s
end
end
For Python I use PyCrust, which is a nice graphical shell that feels like a good Java IDE (has autocompletion, inline documentation and can save history).
Is there anything similar for Ruby?
If you don't need it to be graphical, you can use irb, wich I think is a part of the ruby package.
It has autocompletion. To enable it say:
require 'irb/completion'
It can also save your history between sessions.
You should write your own .irbrc file to configure it.
I don't know all the features it has, but I guess there are more than the ones I have mentioned here.
Some up and coming ruby shells:
ir - a slim ruby shell
irb2 - rewrite of irb
Thorough documentation of irb:
irb commands
configuring irb
Some gems I've written that'll enhance your ruby shell experience:
hirb - console view framework
bond - enhanced autocompletion i.e. completing method arguments
boson - console command manager and more
The gem "looksee" is helpful for method lookup.
Watch a video at RubyPulse.
You can see all the methods available at the given variable as below.
>> s = "Hello World"
=> "Hello World"
>> lp s
=> String
% casecmp downcase! include? next size sum tr_s
* center dump index next! slice swapcase tr_s!
+ chars each insert oct slice! swapcase! unpack
<< chomp each_byte inspect partition split taguri upcase
<=> chomp! each_char intern replace squeeze taguri= upcase!
== chop each_line is_binary_data? reverse squeeze! to_f upto
=~ chop! empty? is_complex_yaml? reverse! start_with? to_i
[] concat end_with? length rindex strip to_s
[]= count eql? lines rjust strip! to_str
bytes crypt gsub ljust rpartition sub to_sym
bytesize delete gsub! lstrip rstrip sub! to_yaml
capitalize delete! hash lstrip! rstrip! succ tr
capitalize! downcase hex match scan succ! tr!
Enumerable
all? drop enum_cons first max minmax_by reverse_each to_a
any? drop_while enum_slice grep max_by none? select zip
collect each_cons enum_with_index group_by member? one? sort
count each_slice find include? min partition sort_by
cycle each_with_index find_all inject min_by reduce take
detect entries find_index map minmax reject take_while
Comparable
< <= == > >= between?
Object
dump_lookup_path lookup_path taguri to_yaml to_yaml_style
local_methods recent_hisotry taguri= to_yaml_properties
PP::ObjectMixin
pretty_print pretty_print_cycle pretty_print_inspect pretty_print_instance_variables
Kernel
== dup id instance_variables private_methods tap
=== enum_for inspect is_a? protected_methods to_a
=~ eql? instance_eval kind_of? public_methods to_enum
__id__ equal? instance_exec method respond_to? to_s
__send__ extend instance_of? methods send type
class freeze instance_variable_defined? nil? singleton_methods untaint
clone frozen? instance_variable_get object_id taint
display hash instance_variable_set pretty_inspect tainted?