Skip to content

Commit

Permalink
Fix old posts that became stale
Browse files Browse the repository at this point in the history
  • Loading branch information
radanskoric committed Jun 10, 2024
1 parent 951781a commit f1ca79b
Show file tree
Hide file tree
Showing 4 changed files with 17 additions and 11 deletions.
5 changes: 3 additions & 2 deletions _posts/2023-09-01-ruby-local-variables.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
layout: post
title: "Is it possible to conditionally define a local variable in Ruby?"
date: 2023-09-01
last_modified_at: 2024-06-10
categories: articles
tags: ruby
---
Expand Down Expand Up @@ -107,7 +108,7 @@ puts RubyVM::InstructionSequence.compile("if defined?(x); x = true; end").disasm
```
Gives:
```
== disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,29)> (catch: false)
== disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,29)>
local table (size: 1, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
[ 1] x@0
0000 putself ( 1)[Li]
Expand All @@ -127,7 +128,7 @@ puts RubyVM::InstructionSequence.compile("x = true if defined?(x)").disasm
```
Gives:
```
== disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,23)> (catch: false)
== disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,23)>
local table (size: 1, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
[ 1] x@0
0000 putobject true ( 1)[Li]
Expand Down
17 changes: 11 additions & 6 deletions _posts/2023-09-27-locating-source-of-ruby-method.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,23 @@
layout: post
title: "How to locate the source of a Ruby method"
date: 2023-09-27
last_modified_at: 2023-09-29
last_modified_at: 2024-06-10
categories: articles
tags: ruby metaprogramming
---

**UPDATE Jun 10, 2024:** Modify the article to account for Ruby 3.3 [adding default location for eval'd methods](https://github.com/ruby/ruby/commit/43a5c191358699fe8b19314763998cb8ca77ed90){:target="_blank"}.

---

_"Wait, where the hell is this method coming from??"_"

One of Ruby's strengths is how highly dynamic it is. This makes it very expressive but it also means that it can often be hard to figure out where a method that you see on an object has come from. Full featured Ruby editors like [RubyMine](https://www.jetbrains.com/ruby/){:target="_blank"} and various plugins for other editors based on gems like [Solargraph](https://solargraph.org/){:target="_blank"} have advanced in recent years and have excellent ability to find the definition of a method.

However, Ruby is so dynamic that there are cases where it is impossible to determine reliably without actually running the code. Static analysis having its limits is the cost of super high dynamism and you will eventually find yourself in a ruby REPL wondering: "Where is this method actually defined?"

There are multiple cases to consider, let's dig in.

## The easy case: Regular Ruby method

Assume you have a source file defined as follows:
Expand Down Expand Up @@ -84,18 +89,18 @@ end
```
Both `source_location` and Pry's `show-source` will point to the place where the method is defined, in this case the line number 6, the one calling `define_method`. Easy.

However, in a case where you're calling one of the `eval` methods with a string, all of the information gets destroyed while parsing the string:
Since Ruby 3.3., if you call one of the `eval` methods with a string, the caller location be shown in the location string:
```ruby
# in dynamic.rb
Dynamic.class_eval "def evald; 'EVALD!'; end"
# later
Dynamic.new.method(:evald).source_location # => returns [(eval), 1]
Dynamic.new.method(:evald).source_location # => returns [(eval at dynamic.rb:1), 1]
```
The reason is that `eval` requires you to explicitly pass what you consider the correct location of the source. I.e. if you do the following:
However, this is just a default and eval allows you to pass what you consider the correct location of the source. I.e. if you do the following:
```ruby
Dynamic.class_eval "def evald_with_source; 'EVALD!'; end", __FILE__, __LINE__
Dynamic.class_eval "def evald_with_source; 'EVALD!'; end", "intended_source_file.rb", 42
```
Then `source_location` will return the location where this was called, just like in the `define_method` with block case above.
Then `source_location` will return the custom, and presumably correct, location you specified.

To help your future self, get into the habit of specifying the file and line number when doing meta-programming with `eval` and friends. :)

Expand Down
4 changes: 2 additions & 2 deletions spec/posts/2023-09-01-ruby-local-variables_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@

specify do
expect(RubyVM::InstructionSequence.compile("if defined?(x); x = true; end").disasm).to eq(<<~ASM)
== disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,29)> (catch: false)
== disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,29)>
local table (size: 1, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
[ 1] x@0
0000 putself ( 1)[Li]
Expand All @@ -72,7 +72,7 @@

specify do
expect(RubyVM::InstructionSequence.compile("x = true if defined?(x)").disasm).to eq(<<~ASM)
== disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,23)> (catch: false)
== disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,23)>
local table (size: 1, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
[ 1] x@0
0000 putobject true ( 1)[Li]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ def bar

it "works for dynamic method with string source" do
expect(Dynamic.new.method(:evald).source_location).to eq [
"(eval)",
"(eval at #{File.dirname(__FILE__)}/#{DYNAMIC}:11)",
1
]
end
Expand Down

0 comments on commit f1ca79b

Please sign in to comment.