Sunday, July 12, 2009

Escape me, escape me NOT. Assigning a Ruby expression to a Javascript variable.

I came across a problem: before directly assigning a ruby expression to a javascript variable, it's a good idea to escape it:

var my_javascript_var = '<%= escape_javascript(my_ruby_expression) %>';


The reason to do that is your ruby expression might contain special symbols ( for example some innocent quotes), making your life excruciatingly painful when all of your javascript code stops working.

This was a direct assignment. However, if you assign a javascript variable using PrototypeHelper, don't escape your ruby expression! If you use:

page.assign 'my_javascript_var', escape_javascript(my_ruby_expression)


you will see your code's html guts on the page.

In this case you need to use:

page.assign 'my_javascript_var', my_ruby_expression


The expression has been already escaped for you by Ruby on Rails.

Tuesday, April 7, 2009

Quick Notes on restful_authentication with email activation

A good step-by-step guide how to install user authentication system with email activation can be found here

There are still a few issues when using those wonderful plugins.

1. Emailing activation code using a gmail account doesn't work explicitly with the current Rails version 2.2.2. In order to make it work, place the following class named smtp_tsl.rb into the lib folder (This is not my code, I found it in some news group. I don't remember where, sorry.)

require "openssl"
require "net/smtp"

class Net::SMTP
class << self
send :remove_method, :start
attr_accessor :use_tls
end

@use_tls = true

def self.start( address, port = nil,
helo = 'localhost.localdomain',
user = nil, secret = nil, authtype = nil, use_tls = ::Net::SMTP.use_tls,
&block) # :yield: smtp
new(address, port).start(helo, user, secret, authtype, use_tls, &block)
end

alias tls_old_start start

def start( helo = 'localhost.localdomain',
user = nil, secret = nil, authtype = nil, use_tls = ::Net::SMTP.use_tls ) # :yield: smtp
start_method = use_tls ? :do_tls_start : :do_start
if block_given?
begin
send start_method, helo, user, secret, authtype
return yield(self)
ensure
do_finish
end
else
send start_method, helo, user, secret, authtype
return self
end
end

private

def do_tls_start(helodomain, user, secret, authtype)
raise IOError, 'SMTP session already started' if @started
check_auth_args user, secret, authtype if user or secret

sock = timeout(@open_timeout) { TCPSocket.open(@address, @port) }
@socket = Net::InternetMessageIO.new(sock)
@socket.read_timeout = 60 #@read_timeout
@socket.debug_output = @debug_output

check_response(critical { recv_response() })
do_helo(helodomain)

raise 'openssl library not installed' unless defined?(OpenSSL)
starttls
ssl = OpenSSL::SSL::SSLSocket.new(sock)
ssl.sync_close = true
ssl.connect
@socket = Net::InternetMessageIO.new(ssl)
@socket.read_timeout = 60 #@read_timeout
@socket.debug_output = @debug_output
do_helo(helodomain)

authenticate user, secret, authtype if user
@started = true
ensure
unless @started
# authentication failed, cancel connection.
@socket.close if not @started and @socket and not @socket.closed?
@socket = nil
end
end

def do_helo(helodomain)
begin
if @esmtp
ehlo helodomain
else
helo helodomain
end
rescue Net::ProtocolError
if @esmtp
@esmtp = false
@error_occured = false
retry
end
raise
end
end

def starttls
getok('STARTTLS')
end

alias tls_old_quit quit

def quit
begin
getok('QUIT')
rescue EOFError
end
end

end unless Net::SMTP.private_method_defined? :do_tls_start or
Net::SMTP.method_defined? :tls?


Now, in order to configure your ActionMailer, create a file email.rb in your config/initializers and place the following code into it:

require 'smtp_tls'

ActionMailer::Base.delivery_method = :smtp
ActionMailer::Base.smtp_settings = {
:address => "smtp.gmail.com",
:port => 587,
:domain => "gmail.com",
:authentication => :plain,
:user_name => "<mygmailaccount>@gmail.com",
:password => "<mypassword>"
}
ActionMailer::Base.perform_deliveries = true
ActionMailer::Base.raise_delivery_errors = true
ActionMailer::Base.default_charset = "utf-8"
2. acts_as_state_machine assigns the activation code twice. As a result, the activation code saved in the database is not the same as the activation code sent to the user... Logically, the initial state of the state machine should be "passive", not "pending" and then the activation code is assigned once... except the machine doesn't change the state to "pending" for some reason. The hack I found on the internet is to reload the object from the database before sending an email... except, it nukes the object associations. My own hack is provided below, replace make_activation_code function in the model with the following one:

    def make_activation_code
unless self.state == 'pending'
self.deleted_at = nil
self.activation_code = Digest::SHA1.hexdigest( Time.now.to_s.split(//).sort_by {rand}.join )
end
end


3. I have implemented a couple of functions of my own:
a. resend activation code

This was trivial

b. resend password if forgotten

The user account in this case should be active, i.e. the state machine makes a self-transition from the active state to the same. In order to be able to execute code on transitions, I used the following enhancement of the acts_as_state_machine plugin (big thanks to this guy).

Also, I created 2 extra fields in the model - passreset_code and passreset_at. Once the user requests to resend the password, passreset_code is created and sent to them. Once the user clicks on the link with this code, they're presented with a form to reset the password. Once they update the password, the code is cleared.

In order to implement this, add the following transitions to the model:
  event :resetpassword do
transitions :from => :active, :to => :active, :on_transition => :make_passreset_code
end

event :updatepassword do
transitions :from => :active, :to => :active, :on_transition => :do_update_password
end


and the following protected methods:
    def make_passreset_code
self.passreset_code = Digest::SHA1.hexdigest( Time.now.to_s.split(//).sort_by {rand}.join )
end

def do_update_password
self.passreset_at = Time.now.utc
self.passreset_code = nil
end

Saturday, March 7, 2009

Why I don't like writing blogs

Blogs are personal experiences. There are personal experiences other people can use, and there are those they cannot. Speaking about the ones from the latter category, I used to keep diaries in my mid and late teens. I thought they were some kind of spiritual expression, but they were mostly "this guy" and "that guy" girlish kind of stuff. I found those books later in my life and drowned then in my Mom's bathtub because it faded the ink. I could've burned them but my Mom would've complained about damaging her bathtub.

As examples of the useful personal experiences, I read a couple of blogs when I was learning to make customized form builders. They came as really good help for me. Unfortunately, they were not enough for my own purpose. Hence, after I finished with my own version of the tabular form builder, I decided to publish my experience in order to help somebody else.

I really didn't enjoy the very process of blogging too much.
Free blogs are very basic and are not customized to publish code. I had to learn that the "Compose" window doesn't escape code. Also, when I managed to escape the code on my own and format it, it sometimes stripped off my formatting (for some reason, only the first 2 snippets). OK, I got the moment when it looked right in the preview and published it. It didn't look like my preview at all, I couldn't read my code. Eventually, after I almost gave up, I found this tool for code formatting http://formatmysourcecode.blogspot.com/. This formatting worked.

So, I hope this post doesn't completely look as the second category. This is the summary of the practical things:
1. Diaries can be drowned instead of burning
2. Format your code on blogger using http://formatmysourcecode.blogspot.com/

Friday, March 6, 2009

DRY forms with Tabular FormBuilder

I wanted to create a form using old good tables. Like this one:



This table consists of 3 columns: label, field and comment (optional). Some of the cells span for 2 columns. Also, the third row has several elements in it.

If I directly embed code into HTML, my view will result in the following mostrosity:

<table border="0" cellpadding="0" cellspacing="0">
<%form_for :event, @event, :html =>{:method => 'post'}, :url => {:action => 'create'}, :width => '90%' do |f| %>
<tbody><tr>
<td align="right" width="25%">
<%= f.label "<span class="asterick">*</span> Event Name: %>
</td>
<td align="left">
<%= f.text_field :name, :style => "width: 200px" %>

</td>
<td class="comment" width="60%">Choose a meaningful name, e.g. Bob's Birthday Party, Susie's Baby Shower
</td>
</tr>
<tr>
<td align="right" width="25%">
<%= f.label "<span class="asterick">*</span> Event Type:"
</td>
<td align="left">
<%= f.select :eventtype, {'Dinner'=>1, 'Breakfast'=>2, 'Lunch'=>3, 'Brunch'=>4}, :html_options => {:style => "width:150px"} %>
</td>
<td class="comment" width="60%">
Leave it blank if there is no meal
</td>
</tr>
...



etc. And this is just a part of the form.

Ugly, isn't it?

I would like to spell out my form in a simpler and DRYer way, like:

<% table_form_for :event, @event, :html =>{:method => 'post'}, :url => {:action => 'create'}, :width => '90%' do |f| %>
<%= f.text_field :name, :required => true, :label => "Event Name", :comment => "Choose a meaningful name, e.g. Bob's Birthday Party, Susie's Baby Shower ", :style => "width: 200px" %>
<%= f.select :eventtype, {'Dinner'=>1, 'Breakfast'=>2, 'Lunch'=>3, 'Brunch'=>4},:label => "Event Type", :include_blank => true, :comment => 'Leave it blank if there is no meal', :html_options => {:style => "width:150px"} %>
<% f.row_of_elements :nocells => true, :colspan => '2' do |r| %>
<%= r.check_box :picnic, :text => 'Picnic' %>
<%= r.check_box :party, :text => 'Party of' %>
<%= r.select :party_size, {"1-4" => [1,4], "5-10" => [5,9], "11-20" => [11, 20], ">20" => [21, 1000]}, :include_blank => true, :text => 'people', :html_options => {:style => "width:70px"} %>
<% end %>
<%= f.datetime_select :date, :required => true, :label => 'Date and Time', :colspan => "2", :html_options => {:style => "width: 70px"}%>
<%= f.text_area :directions, :required => true, :label => "Address and Directions", :colspan => "2", :style => "width: 400px; height: 120px" %>
<%= f.submit 'Submit', :style => 'width:200px' %>
<% end %>


where each field takes "label" and "comment" as attributes, and has other attributes like "required" - if the field is compulsory and "text" - for the text to append after the field. Also, the "colspan" attribute takes care of the fields that span for more then 1 column. In addition, if my fields are in the same row, they are wrapped into the "row_of_elements" wrapper. Is it too much to ask for?

This problem can be solved if we make a custom form builder take care of the formatting. First, we create a basic form builder with methods to encapsulate elements into 'tr' and 'td' tags by subclassing FormBuilder

class TabularFormBuilder < ActionView::Helpers::FormBuilder

# the constructor
def initialize(object_name, object, template, options, proc)
#initialization parameters
@create_row = options[:create_row].nil? ? true : options.delete(:create_row)
@create_cells = options[:create_cells].nil? ? true : options.delete(:create_cells)
super(object_name, object, template, options, proc)
end


# empty row
def empty_row(options = {})
@template.content_tag('tr', @template.content_tag('td', '&nbsp;', options))
end


# cells incapsulated into a tr tag
def table_row(contents, options = {})
@template.content_tag('tr', options) do
table_cells(contents)
end
end

# a row of td cells
def table_cells(contents)
cells = ""
contents.each do |content, options|
options = {} if options.nil?
cells += @template.content_tag('td', options) do
"#{content}"
end
end
return cells
end

# a row for a block of elements
def create_row(builder, nocells = true, header = '&nbsp;', rowoptions = {}, celloptions = {}, headeroptions = {}, &block)
raise ArgumentError, "Missing block" unless block_given?
builder = ActionView::Base.default_form_builder if builder.nil?
cells = ""
# make a header cell
unless header.nil?
cells += @template.content_tag('td', header, headeroptions)
end
if nocells
# make one cell for all the elements
cells += @template.content_tag('td', celloptions) do
yield builder.new(@object_name, @object, @template, @options.merge(:create_row => false, :create_cells => false), @proc)
end
else
# let the elements handle their own cells
cells += @template.capture do
yield builder.new(@object_name, @object, @template, @options.merge(:create_row => false, :create_cells => true), @proc)
end
end
row = ""
# wrap
row = @template.content_tag('tr', rowoptions) do
"#{cells}"
end
@template.concat(row)
end

end


Let's look at it closer.

initialize (constructor)
We pass 2 options to the constructor. The option create_row is telling whether the form builder creates a row for every element (true) or arranges the elements into 1 row (false). The option create_cells is telling whether the form builder creates a separate cell for each element (true) or the elements are placed into the same cell (false).

table_cells encapsulates each element in an array into a 'td' tag. As a parameter it takes an array where each member is a pair of an element and the options to format the cell.

table_row calls table_cells to create 'td' cells for the elements and then encapsulates them into a 'tr' tag. As the parameters it takes an array where each member is a pair of an element and the options to format the cell, and options to format the row.

create_row processes the wrapper for a row of elements. It encapsulates the content into a 'tr' tag, and also creates a cell for the elements if they will be in the same cell. Then the builder passed to it takes care of each element. As the parameters it takes the class of the internal builder, a parameter whether there are no separate cells created for each element, the header of the row (first column), options for the row, options for the content cell and the header cell.

There also a method to create an empty row empty_row

In order to apply those methods to my form, I extended this form builder. Also, we're missing the submit method, so I added it.

class MyTabularFormBuilder < TabularFormBuilder

def submit(title = 'Submit', options = {})
button = super(title, options)
table_row(['&nbsp;', [button, {:align => 'left'}]])
end

def get_required
"<span class='asterick'>*</span>"
end

def row_of_elements(*args, &block)
raise ArgumentError, "Missing block" unless block_given?

extract the options and call create_row
...
end


def build_row(... )

build the contents array and call table_row

...
end

def build_cell(... )

build the contents array and call table_cells
...
end


def self.build_tabular_field(name)
define_method(name) do |field, *args|
options = args.extract_options!
extract the options
...

if @create_row
call build_row
elsif @create_cells
call build_cell
else
the content is "content + &nbsp;&nbsp;&nbsp;"
end
end
end

helpers = field_helpers +
%w{date_select datetime_select time_select} +
%w{collection_select select country_select time_zone_select} -
%w{hidden_field label fields_for}

helpers.each do |name|
self.build_tabular_field(name)
end


end


In addition, to wrap everything into a 'table' tag and tell my form to use the new form builder, I had to add this method to application_helper.rb

  def table_form_for(name, *args, &proc)
options = args.extract_options!
width = options.delete(:width)
if width.nil?
width = '100%'
end
args.push(options)
concat("<table cellspacing='0', cellpadding ='0', border = '0' width = '" + width + "'>")
form_for(name, *(args << {builder => MyTabularFormBuilder})), &proc)
concat("</table>")
end


We are all set now. Use the method table_form_for instead of form_for to build very DRY tabular forms.

References:
Professional Ruby on Rails by Noel Rappin
Advanced Rails Recipes by Mike Clark