The Artima Developer Community
Sponsored Link

Ruby Buzz Forum
Lost In Binding - Adventures In Ruby Metaprogramming

0 replies on 1 page.

Welcome Guest
  Sign In

Go back to the topic listing  Back to Topic List Click to reply to this topic  Reply to this Topic Click to search messages in this forum  Search Forum Click for a threaded view of the topic  Threaded View   
Previous Topic   Next Topic
Flat View: This topic has 0 replies on 1 page
Guy Naor

Posts: 104
Nickname: familyguy
Registered: Mar, 2006

Guy Naor is one of the founders of famundo.com and a long time developer
Lost In Binding - Adventures In Ruby Metaprogramming Posted: Mar 28, 2007 12:22 PM
Reply to this message Reply

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
Latest Ruby Buzz Posts
Latest Ruby Buzz Posts by Guy Naor
Latest Posts From Famundo - The Dev Blog

Advertisement

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 var
a = 5
# Create a proc that prints a
level_a = lambda { puts a }
level_a.call # => 5

# Create a method that accepts a proc, redefines a and calls the proc
def level_b(blk)
  a = 10
  blk.call
end

level_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:

def secure_form_tag(*args)
  return start_form_tag(*args) + "\n" +
    hidden_field_tag('session_id_validation', security_token)
end

The simple solution I thought will work, is very simple:

def secure_form_tag(url_for_options = {}, options = {}, *parameters_for_url, &block)
  if block_given?
    form_tag(url_for_options, options, *parameters_for_url) do
      yield
    end
    hidden_field_tag("session_id_validation", security_token)
  else
    "#{form_tag(*args)} \n #{hidden_field_tag('session_id_validation', security_token)}"
  end
end

It failed. I decided to try and call the external block directly:

def secure_form_tag(url_for_options = {}, options = {}, *parameters_for_url, &block)
  if block_given?
    form_tag(url_for_options, options, *parameters_for_url) do
      block.call
    end
    hidden_field_tag("session_id_validation", security_token)
  else
    "#{form_tag(*args)} \n #{hidden_field_tag('session_id_validation', security_token)}"
  end
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:

def secure_form_tag(url_for_options = {}, options = {}, *parameters_for_url, &block)
  if block_given?
    _erbout = eval('_erbout', block)
    form_tag(url_for_options, options, *parameters_for_url) do
      concat(hidden_field_tag("session_id_validation", security_token), block.binding)
      eval "_erbout = %q[#{_erbout}]"
      yield
    end
    eval "_erbout = %q[#{_erbout}]", block
  else
    "#{form_tag(*args)} \n #{hidden_field_tag('session_id_validation', security_token)}"
  end
end

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.

Read: Lost In Binding - Adventures In Ruby Metaprogramming

Topic: Attack of the Blogger Terrorists Previous Topic   Next Topic Topic: Celebrating Two Years of Anarchaia!

Sponsored Links



Google
  Web Artima.com   

Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use