I would like to know whether I can get source code a method on the fly, and whether I can get which file is this method in.
like
A.new.method(:a).SOURCE_CODE
A.new.method(:a).FILE
Use source_location:
class A
def foo
end
end
file, line = A.instance_method(:foo).source_location
# or
file, line = A.new.method(:foo).source_location
puts "Method foo is defined in #{file}, line #{line}"
# => "Method foo is defined in temp.rb, line 2"
Note that for builtin methods, source_location returns nil. If want to check out the C source code (have fun!), you'll have to look for the right C file (they're more or less organized by class) and find the rb_define_method for the method (towards the end of the file).
In Ruby 1.8 this method does not exist, but you can use this gem.
None of the answers so far show how to display the source code of a method on the fly...
It's actually very easy if you use the awesome 'method_source' gem by John Mair (the maker of Pry):
The method has to be implemented in Ruby (not C), and has to be loaded from a file (not irb).
Here's an example displaying the method source code in the Rails console with method_source:
$ rails console
> require 'method_source'
> I18n::Backend::Simple.instance_method(:lookup).source.display
def lookup(locale, key, scope = [], options = {})
init_translations unless initialized?
keys = I18n.normalize_keys(locale, key, scope, options[:separator])
keys.inject(translations) do |result, _key|
_key = _key.to_sym
return nil unless result.is_a?(Hash) && result.has_key?(_key)
result = result[_key]
result = resolve(locale, _key, result, options.merge(:scope => nil)) if result.is_a?(Symbol)
result
end
end
=> nil
See also:
https://rubygems.org/gems/method_source
https://github.com/banister/method_source
http://banisterfiend.wordpress.com/
Here is how to print out the source code from ruby:
puts File.read(OBJECT_TO_GET.method(:METHOD_FROM).source_location[0])
Without dependencies
method = SomeConstant.method(:some_method_name)
file_path, line = method.source_location
# puts 10 lines start from the method define
IO.readlines(file_path)[line-1, 10]
If you want use this more conveniently, your can open the Method class:
# ~/.irbrc
class Method
def source(limit=10)
file, line = source_location
if file && line
IO.readlines(file)[line-1,limit]
else
nil
end
end
end
And then just call method.source
With Pry you can use the show-method to view a method source, and you can even see some ruby c source code with pry-doc installed, according pry's doc in codde-browing
Note that we can also view C methods (from Ruby Core) using the
pry-doc plugin; we also show off the alternate syntax for show-method:
pry(main)> show-method Array#select
From: array.c in Ruby Core (C Method):
Number of lines: 15
static VALUE
rb_ary_select(VALUE ary)
{
VALUE result;
long i;
RETURN_ENUMERATOR(ary, 0, 0);
result = rb_ary_new2(RARRAY_LEN(ary));
for (i = 0; i < RARRAY_LEN(ary); i++) {
if (RTEST(rb_yield(RARRAY_PTR(ary)[i]))) {
rb_ary_push(result, rb_ary_elt(ary, i));
}
}
return result;
}
I created the "ri_for" gem for this purpose
>> require 'ri_for'
>> A.ri_for :foo
... outputs the source (and location, if you're on 1.9).
GL.
-r
Internal methods don't have source or source location (e.g. Integer#to_s)
require 'method_source'
User.method(:last).source
User.method(:last).source_location
I had to implement a similar feature (grab the source of a block) as part of Wrong and you can see how (and maybe even reuse the code) in chunk.rb (which relies on Ryan Davis' RubyParser as well as some pretty funny source file glomming code). You'd have to modify it to use Method#source_location and maybe tweak some other things so it does or doesn't include the def.
BTW I think Rubinius has this feature built in. For some reason it's been left out of MRI (the standard Ruby implementation), hence this hack.
Oooh, I like some of the stuff in method_source! Like using eval to tell if an expression is valid (and keep glomming source lines until you stop getting parse errors, like Chunk does)...
Related
I would like to know whether I can get source code a method on the fly, and whether I can get which file is this method in.
like
A.new.method(:a).SOURCE_CODE
A.new.method(:a).FILE
Use source_location:
class A
def foo
end
end
file, line = A.instance_method(:foo).source_location
# or
file, line = A.new.method(:foo).source_location
puts "Method foo is defined in #{file}, line #{line}"
# => "Method foo is defined in temp.rb, line 2"
Note that for builtin methods, source_location returns nil. If want to check out the C source code (have fun!), you'll have to look for the right C file (they're more or less organized by class) and find the rb_define_method for the method (towards the end of the file).
In Ruby 1.8 this method does not exist, but you can use this gem.
None of the answers so far show how to display the source code of a method on the fly...
It's actually very easy if you use the awesome 'method_source' gem by John Mair (the maker of Pry):
The method has to be implemented in Ruby (not C), and has to be loaded from a file (not irb).
Here's an example displaying the method source code in the Rails console with method_source:
$ rails console
> require 'method_source'
> I18n::Backend::Simple.instance_method(:lookup).source.display
def lookup(locale, key, scope = [], options = {})
init_translations unless initialized?
keys = I18n.normalize_keys(locale, key, scope, options[:separator])
keys.inject(translations) do |result, _key|
_key = _key.to_sym
return nil unless result.is_a?(Hash) && result.has_key?(_key)
result = result[_key]
result = resolve(locale, _key, result, options.merge(:scope => nil)) if result.is_a?(Symbol)
result
end
end
=> nil
See also:
https://rubygems.org/gems/method_source
https://github.com/banister/method_source
http://banisterfiend.wordpress.com/
Here is how to print out the source code from ruby:
puts File.read(OBJECT_TO_GET.method(:METHOD_FROM).source_location[0])
Without dependencies
method = SomeConstant.method(:some_method_name)
file_path, line = method.source_location
# puts 10 lines start from the method define
IO.readlines(file_path)[line-1, 10]
If you want use this more conveniently, your can open the Method class:
# ~/.irbrc
class Method
def source(limit=10)
file, line = source_location
if file && line
IO.readlines(file)[line-1,limit]
else
nil
end
end
end
And then just call method.source
With Pry you can use the show-method to view a method source, and you can even see some ruby c source code with pry-doc installed, according pry's doc in codde-browing
Note that we can also view C methods (from Ruby Core) using the
pry-doc plugin; we also show off the alternate syntax for show-method:
pry(main)> show-method Array#select
From: array.c in Ruby Core (C Method):
Number of lines: 15
static VALUE
rb_ary_select(VALUE ary)
{
VALUE result;
long i;
RETURN_ENUMERATOR(ary, 0, 0);
result = rb_ary_new2(RARRAY_LEN(ary));
for (i = 0; i < RARRAY_LEN(ary); i++) {
if (RTEST(rb_yield(RARRAY_PTR(ary)[i]))) {
rb_ary_push(result, rb_ary_elt(ary, i));
}
}
return result;
}
I created the "ri_for" gem for this purpose
>> require 'ri_for'
>> A.ri_for :foo
... outputs the source (and location, if you're on 1.9).
GL.
-r
Internal methods don't have source or source location (e.g. Integer#to_s)
require 'method_source'
User.method(:last).source
User.method(:last).source_location
I had to implement a similar feature (grab the source of a block) as part of Wrong and you can see how (and maybe even reuse the code) in chunk.rb (which relies on Ryan Davis' RubyParser as well as some pretty funny source file glomming code). You'd have to modify it to use Method#source_location and maybe tweak some other things so it does or doesn't include the def.
BTW I think Rubinius has this feature built in. For some reason it's been left out of MRI (the standard Ruby implementation), hence this hack.
Oooh, I like some of the stuff in method_source! Like using eval to tell if an expression is valid (and keep glomming source lines until you stop getting parse errors, like Chunk does)...
I would like to use something similar to Lodash's get and set, but in Ruby instead of JavaScript. I tried few searches but I can't find anything similar.
Lodash's documentation will probably explain it in a better way, but it's getting and setting a property from a string path ('x[0].y.z' for example). If the full path doesn't exist when setting a property, it is automatically created.
Lodash Set
Lodash Get
I eventually ported Lodash _.set and _.get from JavaScript to Ruby and made a Gem.
Ruby 2.3 introduces the new safe navigator operator for getting nested/chained values:
x[0]&.y&.z #=> result or nil
Otherwise, Rails monkey patches all objects with try(…), allowing you to:
x[0].try(:y).try(:z) #=> result or nil
Setting is a bit harder, and I'd recommend ensuring you have the final object before attempting to set a property, e.g.:
if obj = x[0]&.y&.z
z.name = "Dr Robot"
end
You can use the Rudash Gem that comes with most of the Lodash utilities, and not only the _.get and _.set.
Sometimes I have had the need to programmatically get the value for a property deep into an object, but the thing is that sometimes the property is really a method, and sometimes it needs parameters!
So I came up with this solution, hope it helps devising one for your problem:
(Needs Rails' #try)
def reduce_attributes_for( object, options )
options.reduce( {} ) do |hash, ( attribute, methods )|
hash[attribute] = methods.reduce( object ) { |a, e| a.try!(:send, *e) }
hash
end
end
# Usage example
o = Object.new
attribute_map = {
# same as o.object_id
id: [:object_id],
# same as o.object_id.to_s
id_as_string: [:object_id, :to_s],
# same as o.object_id.to_s.length
id_as_string_length: [:object_id, :to_s, :length],
# I know, this one is a contrived example, but its purpose is
# to illustrate how you would call methods with parameters
# same as o.object_id.to_s.scan(/\d/)[1].to_i
second_number_from_id: [:object_id, :to_s, [:scan, /\d/], [:[],1], :to_i]
}
reduce_attributes_for( o, attribute_map )
# {:id=>47295942175460,
# :id_as_string=>"47295942175460",
# :id_as_string_length=>14,
# :second_number_from_id=>7}
I wrote the dictation gem on my Mac, and deserialization works fine. When I installed it on another Mac it would not work because it "fails" to deserialize object, because it can only deserialize to a Hash.
Private Mac Ruby version: ruby-1.9.3-p0, json v1.8.0
Another Mac Ruby version: ruby-1.9.3-p448, json v1.8.0
I also tried different Ruby versions and Gem versions on both, but none of them works, only the initial one where I first wrote it.
When I try this code in the working environment:
require 'json'
class Word
attr_accessor :value, :translation
def initialize(value, translation)
#value = value
#translation = translation
end
def to_json(*args)
{
'json_class' => self.class.name,
'data' => [ #value, #translation ]
}.to_json(*args)
end
class << self
def json_create(object)
new(*object['data'])
end
end
end
str = '{"json_class":"Word","data":["Morgen","Tomorrow"]}'
p JSON.parse(str)
It prints a Word object, which is expected:
#<Word:0x007fcce22c9c58 #translation="Tomorrow", #value="Morgen">
With the other environment, it always prints a Hash:
{"json_class"=>"Word", "data"=>["Morgen", "Tomorrow"]}
I also tried to pass :object_class key, it throws another exception:
p JSON.parse(str, :object_class => Word)
# => ArgumentError: wrong number of arguments (0 for 2)
I could not figure out the require 'json' version during runtime using:
puts Gem.loaded_specs['json'].version
because Gem.loaded_specs.keys doesn't contain it.
Thanks for any hint.
Replied from the author of JSON lib - on newer version, due to security reason, to deserialize custom object, either you can:
JSON.parse(str, :create_additions => true)
or you can:
JSON.load(str)
So, I overlooked the JSON#load part in ruby-doc:
load(source, proc = nil, options = {})
Load a ruby data structure from a JSON source and return it. A source
can either be a string-like object, an IO-like object, or an object
responding to the read method. If proc was given, it will be called
with any nested Ruby object as an argument recursively in depth first
order. To modify the default options pass in the optional options
argument as well.
BEWARE: This method is meant to serialise data from trusted user
input, like from your own database server or clients under your
control, it could be dangerous to allow untrusted users to pass JSON
sources into it. The default options for the parser can be changed via
the ::load_default_options method.
This method is part of the implementation of the load/dump interface
of Marshal and YAML.
Deserializing directly into a rich object (especially if your JSON comes from an unknown source) can be a pretty serious attack vector (recent Rails vulnerabilities are related to that).
I would guess that this ability was disabled between Ruby versions, or, at least changed to a whitelist-based approach. I wasn't able to find any links to support this claim though, so I might be wrong.
Anyway, you might find it simpler and more compatible to initialize your class from the deserialized hash instead:
class Word
def self.from_json(json)
args = JSON.parse(json)["data"];
new(*args)
end
end
Here is another workaround, because my code is not used in web communication, vulnerability is not a problem here.
Before I was doing:
JSON.parse(str)
Now just need to add few lines:
obj = JSON.parse(str)
if obj.is_a?(Hash)
class_name = obj['json_class'].split('::').inject(Kernel) { |namespace, const_name| namespace.const_get(const_name) }
args = obj['data']
word = class_name.new(*args)
else
word = obj
end
I have a gem that has code like this inside:
def read(file)
#file = File.new file, "r"
end
Now the problem is, say you have a directory structure like so:
app/main.rb
app/templates/example.txt
and main.rb has the following code:
require 'mygem'
example = MyGem.read('templates/example.txt')
It comes up with File Not Found: templates/example.txt. It would work if example.txt was in the same directory as main.rb but not if it's in a directory. To solve this problem I've added an optional argument called relative_to in read(). This takes an absolute path so the above could would need to be:
require 'mygem'
example = MyGem.read('templates/example.txt', File.dirname(__FILE__))
That works fine, but I think it's a bit ugly. Is there anyway to make it so the class knows what file read() is being called in and works out the path based on that?
There is an interesting library - i told you it was private. One can protect their methods with it from being called from outside. The code finds the caller method's file and removes it. The offender is found using this line:
offender = caller[0].split(':')[0]
I guess you can use it in your MyGem.read code:
def read( file )
fpath = Pathname.new(file)
if fpath.relative?
offender = caller[0].split(':')[0]
fpath = File.join( File.dirname( offender ), file )
end
#file = File.new( fpath, "r" )
end
This way you can use paths, relative to your Mygem caller and not pwd. Exactly the way you tried in your app/main.rb
Well, you can use caller, and a lot more reliably than what the other people said too.
In your gem file, outside of any class or module, put this:
c = caller
req_file = nil
c.each do |s|
if(s =~ /(require|require_relative)/)
req_file = File.dirname(File.expand_path(s.split(':')[0])) #Does not work for filepaths with colons!
break
end
end
REQUIRING_FILE_PATH = req_file
This will work 90% of the time, unless the requiring script executed a Dir.chdir. The File.expand_path depends on that. I'm afraid that unless your requirer passes their __FILE__, there's nothing you can do if they change the working dir.
Also you may check for caller:
def read(file)
if /^(?<file>.+?):.*?/ =~ caller(1).first
caller_dir, caller_file = Pathname.new(Regexp.last_match[:file]).split
file_with_path = File.join caller_dir, file
#file = File.new "#{file_with_path}", "r"
end
end
I would not suggest you to do so (the code above will break being called indirectly, because of caller(1), see reference to documentation on caller). Furthermore, the regex above should be tuned more accurately if the caller path is intended to contain colons.
This should work for typical uses (I'm not sure how resistant it is to indirect use, as mentioned by madusobwa above):
def read_relative(file)
#file = File.new File.join(File.dirname(caller.first), file)
end
On a side note, consider adding a block form of your method that closes the file after yielding. In the current form you're forcing clients to wrap their use of your gem with an ensure.
Accept a file path String as an argument. Convert to a Pathname object. Check if the path is relative. If yes, then convert to absolute.
def read(file)
fpath = Pathname.new(file)
if fpath.relative?
fpath = File.expand_path(File.join(File.dirname(__FILE__),file))
end
#file = File.new(fpath,"r")
end
You can make this code more succinct (less verbose).
This is a newbie question as I am attempting to learn Ruby by myself, so apologies if it sounds like a silly question!
I am reading through the examples of why's (poignant) guide to ruby and am in chapter 4. I typed the code_words Hash into a file called wordlist.rb
I opened another file and typed the first line as require 'wordlist.rb' and the rest of the code as below
#Get evil idea and swap in code
print "Enter your ideas "
idea = gets
code_words.each do |real, code|
idea.gsub!(real, code)
end
#Save the gibberish to a new file
print "File encoded, please enter a name to save the file"
ideas_name = gets.strip
File::open( 'idea-' + ideas_name + '.txt', 'w' ) do |f|
f << idea
end
When I execute this code, it fails with the following error message:
C:/MyCode/MyRubyCode/filecoder.rb:5: undefined local variable or method `code_words' for main:Object (NameError)
I use Windows XP and Ruby version ruby 1.8.6
I know I should be setting something like a ClassPath, but not sure where/how to do so!
Many thanks in advance!
While the top-level of all files are executed in the same context, each file has its own script context for local variables. In other words, each file has its own set of local variables that can be accessed throughout that file, but not in other files.
On the other hand, constants (CodeWords), globals ($code_words) and methods (def code_words) would be accessible across files.
Some solutions:
CodeWords = {:real => "code"}
$code_words = {:real => "code"}
def code_words
{:real => "code"}
end
An OO solution that is definitely too complex for this case:
# first file
class CodeWords
DEFAULT = {:real => "code"}
attr_reader :words
def initialize(words = nil)
#words = words || DEFAULT
end
end
# second file
print "Enter your ideas "
idea = gets
code_words = CodeWords.new
code_words.words.each do |real, code|
idea.gsub!(real, code)
end
#Save the gibberish to a new file
print "File encoded, please enter a name to save the file"
ideas_name = gets.strip
File::open( 'idea-' + ideas_name + '.txt', 'w' ) do |f|
f << idea
end
I think the problem might be that the require executes the code in another context, so the runtime variable is no longer available after the require.
What you could try is making it a constant:
CodeWords = { :real => 'code' }
That will be available everywhere.
Here is some background on variable scopes etc.
I was just looking at the same example and was having the same problem.
What I did was change the variable name in both files from code_words to $code_words .
This would make it a global variable and thus accesible by both files right?
My question is: wouldn't this be a simpler solution than making it a constant and having to write CodeWords = { :real => 'code' } or is there a reason not to do it ?
A simpler way would be to use the Marshal.dump feature to save the code words.
# Save to File
code_words = {
'starmonkeys' => 'Phil and Pete, those prickly chancellors of the New Reich',
'catapult' => 'chucky go-go', 'firebomb' => 'Heat-Assisted Living',
'Nigeria' => "Ny and Jerry's Dry Cleaning (with Donuts)",
'Put the kabosh on' => 'Put the cable box on'
}
# Serialize
f = File.open('codewords','w')
Marshal.dump(code_words, f)
f.close
Now at the beginning of your file you would put this:
# Load the Serialized Data
code_words = Marshal.load(File.open('codewords','r'))
Here's the easy way to make sure you can always include a file that's in the same directory as your app, put this before the require statement
$:.unshift File.dirname(__FILE__)
$: is the global variable representing the "CLASSPATH"