This post originated from an RSS feed registered with Ruby Buzz
by Guy Naor.
Original Post: Lost In Binding - Adventures In Ruby Metaprogramming
Feed Title: Famundo - The Dev Blog
Feed URL: http://devblog.famundo.com/xml/rss/feed.xml
Feed Description: A blog describing the development and related technologies involved in creating famundo.com - a family management sytem written using Ruby On Rails and postgres
I've been using the security_extensions plugin to secure forms in Famundo and some other projects. It's a very simple plugin that adds protection against CSRF.
When upgrading one of my projects to Rails 1.2, I got a deprecation warnings from Rails, as this plugin requires start_form_tag and end_form to work. Thinking it was all easy to change, I replaced all calls to the new form_tag ... do format. This change resulted in lots of errors, all complains from erb on missing the _erbout variable.
After a lot of digging, I realized this error is caused by the way variables bindings work in Ruby, and the fact that erb uses it to pass along the output string it creates.
What are bindings? Bindings are (put very simply) the context for the variables in an execution block. It's what's used in Ruby to bind a variable to a block and have the block access it even after the variable went out of scope in the original code block, or the variable is re-defined in a new block. Here is some code to make it clear:
# Define the a vara=5# Create a proc that prints alevel_a=lambda{putsa}level_a.call# => 5# Create a method that accepts a proc, redefines a and calls the procdef level_b(blk)a=10blk.callendlevel_b(level_a)# => 5
The best place I found to learn about bindings is this page.
How is this affecting the security_extensions plugin? The plugin needs to wrap the block given to the form, and inject into it the hidden field used to validate the form when it's posted back. When using the non-block accepting start_form_tag, it just appends a new field at the end:
Failed again! The problem is that the context of the internal block is completely different from the context of the block passed to the function, and so the _erbout variable isn't bound to the internal block, only to the external one.
The solution I used was to copy the binding from the passed block into the internal block, call the passed block, and then copy it back from the internal block to the passed block. Here is the code to do it:
This code does a lot of copying of strings, but as it's in very specific places that aren't performance sensitive, I rather get the nice way to use secured forms, and incur the performance penalty in this case.
The same trick can be use to extended other erb related methods that use blocks and need the _erbout bindings.
If there are better solutions, let me know. I'd love a simpler solution for this.