# How can I extract variables from a ruby snippet

**URL:** <https://www.ruby-forum.com/t/how-can-i-extract-variables-from-a-ruby-snippet/173857>\
**Category:** Ruby\
**Created:** [August 17, 2009, 4:12am UTC](https://www.ruby-forum.com/t/how-can-i-extract-variables-from-a-ruby-snippet/173857 "2009-08-17T04:12:23Z")\
**Posts on this page:** 3\
**Page:** 1

<div class="post-metadata">

**Author:** ![laran](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/l/4af34b/32.png) [@laran](https://www.ruby-forum.com/u/laran)\
**Post date:** [August 17, 2009, 4:12am UTC](https://www.ruby-forum.com/t/how-can-i-extract-variables-from-a-ruby-snippet/173857/1 "2009-08-17T04:12:23Z")

</div>

I have this text:

 \<%= params['post']['category\_id'] %\> \<%= params['post']['title'] %\> \<%= params['post']['body'] %\> \<%= params['post']['extended\_body'] %\> \<%= params['post']['private'] %\> \<% if params['notify'] then %\> \<% params['notify'].each do |person\_id| %\> \<%= person\_id %\> \<% end %\> \<% end %\> \<% if params['attachments'] then %\> \<% params['attachments'].each do |attachment| %\> \<%= attachment['name'] %\> \<%= attachment['file']['temp\_id'] %\> \<%= attachment['file']['content\_type'] %\> \<%= attachment['file'] ['original\_filename'] %\> \<% end %\> \<% end %\>

What I need to do is extract all of the variables used in order to  
learn the variables, and their structure (array/hash vs. simple value)  
used to render the view.

So, using the snippet above, I would learn that there is:

- a “post” variable which is a hash, expected to have the keys  
“category\_id”, “title”, “body”, “extended\_body” and “private”.
- a “notify” variable which is an array (indicated by the fact that  
it’s iterated over with the .each) of simple values (i.e. it’s not a  
multi-dimensional array, or an array of hashes)
- a “attachments” variable which is an array (again indicated by the  
fact that it’s iterated over with the .each) of hashes (indicated by  
the fact that each element of the “attachments” array is referenced  
with hash syntax, i.e. "attachment[‘name’]) and that the value  
corresponding to the key “file” is also a hash expected to have the  
keys “temp\_id”, “content\_type” and “original\_filename”.

Is there a tool available to allow me to do this? I haven’t been able to  
find anything.

---

<div class="post-metadata">

**Author:** ![laran](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/l/4af34b/32.png) [@laran](https://www.ruby-forum.com/u/laran)\
**Post date:** [August 18, 2009, 12:53pm UTC](https://www.ruby-forum.com/t/how-can-i-extract-variables-from-a-ruby-snippet/173857/2 "2009-08-18T12:53:15Z")

</div>

Laran Evans wrote:

> So, using the snippet above, I would learn that there is:
> 
> - a “post” variable which is a hash, expected to have the keys  
> “category\_id”, “title”, “body”, “extended\_body” and “private”.
> - a “notify” variable which is an array (indicated by the fact that  
> it’s iterated over with the .each) of simple values (i.e. it’s not a  
> multi-dimensional array, or an array of hashes)

You can’t tell that - lots of objects implement an ‘each’ iterator, and  
there isn’t really such thing as a ‘simple value’ in Ruby anyway. The  
number 1 and the Array [1] are both first-class Objects. e.g.

> > h = {1=\>“one”, 2=\>“two”}  
> > =\> {1=\>“one”, 2=\>“two”}
> 
> > h.each { |elem| p elem }  
> > [1, “one”]  
> > [2, “two”]

The full solution to what you want is probably to compile the ERB  
template into Ruby, and then compile the Ruby into an sexp (e.g. using  
ParseTree or ripper), and then analyse the sexp. This is quite a lot of  
work.

Otherwise, you could execute the template using erb, and use  
method\_missing to pick up attempts to access local variables. From these  
return a dummy object, which again uses method\_missing to see what  
methods have been called on that dummy object. If the method\_missing  
passes a block then you will need to yield the block with dummy  
arguments.

This may be good enough for what you need, but isn’t guaranteed to catch  
all possible execution paths. e.g. what about

\<% if foo %\>  
\<%= foo.xxx %\>  
\<% else %\>  
\<%= bar.yyy %\>  
\<% end %\>  
?

You are going to find it hard to exercise both branches of this  
conditional. Similarly with loops, where the nth iteration may behave  
differently to the previous ones.

Rails approaches the problem differently: whenever you render a template  
you pass in a hash of ‘local\_assigns’, and it compiles the template  
assuming that these are the variables which will be substituted. It then  
makes a method signature using this set of variables. If the template is  
rendered again and ‘local\_assigns’ contains a different set of variables  
to substitute, then it is recompiled. The assumption is that normally  
the template will be rendered multiple times using the same set of  
local\_assigns variables (just with different values, of course)

See actionpack/lib/action\_view/renderable.rb

---

<div class="post-metadata">

**Author:** ![laran](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/l/4af34b/32.png) [@laran](https://www.ruby-forum.com/u/laran)\
**Post date:** [August 18, 2009, 5:15pm UTC](https://www.ruby-forum.com/t/how-can-i-extract-variables-from-a-ruby-snippet/173857/3 "2009-08-18T17:15:32Z")

</div>

2009/8/18 Brian C. [removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid):

> Otherwise, you could execute the template using erb, and use  
> method\_missing to pick up attempts to access local variables. From these  
> return a dummy object, which again uses method\_missing to see what  
> methods have been called on that dummy object. If the method\_missing  
> passes a block then you will need to yield the block with dummy  
> arguments.

I toyed around a bit - this solution is by far not perfect!

#!ruby19 -w

class FillIn \< Object  
def initialize(name)  
@name = name.to\_s  
end

def method\_missing(s,\*a,&b)  
a.map! {|x| x.inspect}

```
nn = case s
when :[]
  "#{@name}[#{a.join(', ')}]"
when :[]=
  v = a.slice!(-1, 1)
  "#{@name}[#{a.join(', ')}]=#{v}"
else
  if a.empty?
    "#{@name}.#{s}"
  else
    "#{@name}.#{s}(#{a.join(', ')})"
  end
end

puts nn
self.class.new(nn)

```

end

def to\_s  
@name  
end

alias inspect to\_s  
end

root = FillIn.new “root”  
p root

root.foo.bar[‘help’].length  
root.foo.bar[FillIn.new(“x”)].length

> You are going to find it hard to exercise both branches of this  
> conditional. Similarly with loops, where the nth iteration may behave  
> differently to the previous ones.

Correct. I feel somehow reminded of  
[Halting problem - Wikipedia](http://en.wikipedia.org/wiki/Halting_problem) 🙂

Kind regards

robert
