Sunday, November 9, 2008

Hpricot for XML parsing


Hpricot is flexible and fast html/xml parser.
To install:
$ sudo gem install hpricot

require 'hpricot'

doc = Hpricot(xml_string)
doc.search("/rows/row").each do |row|
group_and_row_id = row.get_attribute("id")
row.search("/cell").each do |cell|
cell_text = cell.html
end
end

Caution with String concat <<

If you have a string a = "test" and you want to append dynamic data, be careful with <If the dynamic data happens to be a Fixnum between 0 and 255, it is converted to a character before concatenation.

>> a = "test"
=> "test"
>> a << 12
=> "test\f"
>>

dhtmlxGrid in a Rails App

Here is my experience using dhtmlxGrid in ROR app.
In general, it is a nice component and comes with lots of sample code. Their support and knowledge is good. Their document list json as supported data type but json support is a subset of XML type. Even if you load the grind in JSON, when you serialize the grid, you get xml back and cell id is not sent back. In rails project, to generate xml, when you use xml builder, make sure, you don't indent. Grid dosn't load xml string with indentation.
Use Builder::XmlMarkup.new (no indent)

Don't use column name type

I had a table and there was a business need to add a type column in the table. Adding a type column as an integer or as an string didn't work.
"type" is a reserved field name for single table inheritance (STI). Active Record allows inheritance by storing the name of the class in a column that by default is called “type”

Here are the magic field names in Rails:
http://wiki.rubyonrails.com/rails/pages/MagicFieldNames

HowtoGenerateJSON

Recently, I needed JSON format to load data in dhtml component. It was trivial. I installed ruby-json gem. In my model class, I had to do the following:

require 'json/objects'

def to_json
result = Hash.new
rows = []
qg_rows = all_records

qg_rows.each_with_index do |row_data|
row = {}
data = []
row_data.each {|cells|
------
data << cell_data
}
row [:data] = data
rows << row
end
result[:rows] = rows
result.to_json

end

Saturday, October 11, 2008

escaping javascript

Javascript provides
escape(string)
method but it does not escapes the following chars:
* @ - _ + . /

Use encodeURIComponent(URIstring)instead. The encodeURIComponent method encodes all characters.
Use the decodeURIComponent() function to decode URIs encoded with encodeURIComponent().

Friday, October 10, 2008

URI escaping

Which one to use? URI.escape or CGI.escape? Lets look at irb:
>> URI.escape('Test <>?/&;=:.')
=> "Test%20%3C%3E?/&;=:."
>> CGI.escape('Test <>?/&;=:.')
=> "Test+%3C%3E%3F%2F%26%3B%3D%3A."

CGI.escape is looking better but it does not escape dots which gives "RoutingError No route matches". Here is the solution working for me
>> test = URI.escape(CGI.escape('Test <>?/&;=:.'),'.')
=> "Test+%3C%3E%3F%2F%26%3B%3D%3A%2E"
>> CGI.unescape(test)
=> "Test <>?/&;=:."

Sunday, October 5, 2008

Formatting phone numbers, Fax numbers

I created a plug-in act_as_formatted to format numbers, phone/fax numbers and zip codes. If there is number field (stored as integer in DB) and user formats it using "," for example 10,010, this gets saved as 10 (without special handling). To solve this problem I created a plug-in act_as_formatted. This plug-in creates *_ui and *_ui= methods on ActiveRecord models. attrubute_ui does the formatting and attribute_ui= ensures, right data is being saved. Example usage:
In model
acts_as_number :revenue
acts_phone_number :phone
In view use attribute_ui
<%= company.revenue_ui %>
<%= company.phone_ui %>
---------------------------------------
Here is the code. Ideally model should not be using ActionView::Helpers::NumberHelper but for now this seems to be working fine for e.

class ActiveRecord::Base
include ActionView::Helpers::NumberHelper
include ERB::Util

def self.format_as_number(*args)

args.each do |arg|

define_method "#{arg.to_s}_ui=" do |number_string|
if number_string
# If a valid number, sanitize to digits only
if valid_number? number_string
number_string.gsub!(/[^0-9]/, "")
end
end
write_attribute(arg, number_string)
end
end

args.each do |arg|
define_method "#{arg.to_s}_ui" do
val = read_attribute(arg)
begin
number_with_delimiter val if val
rescue
val
end
end
end

define_method 'validate' do
args.each { |arg|
error_message = 'is invalid. It can contain only digits'
if !eval(arg.to_s).nil?
errors.add(arg, error_message) unless valid_number? eval(arg.to_s)
end
}
end
end

def self.format_as_phone_number(*args)

args.each do |arg|

define_method "#{arg.to_s}=" do |number_string|
if number_string
# If a valid phone number(can contain only 0-9/-()+xX),
# sanitize it to "digits only" only if 10 digits long
# If number has more that 10 digits, store it as is
if valid_phone? number_string
numbers = number_string.gsub(/[^0-9]/, "")
if numbers.size == 10
number_string = numbers
end
end
end
write_attribute(arg, number_string)
end
end

args.each do |arg|
define_method "#{arg.to_s}_ui" do
val = read_attribute(arg)
begin
if val
numbers = val.gsub(/[^0-9]/, "")
#Format 10 digits phone number only
if numbers.size == 10
return number_to_phone(numbers,:area_code => true)
end
end
val
rescue
val
end
end
end

define_method 'validate' do
args.each { |arg|
error_message = 'is invalid. It must contain at least 5 digits, only the following characters are allowed: 0-9/-()+x'
if !eval(arg.to_s).nil?
errors.add(arg, error_message) unless valid_phone? eval(arg.to_s)
end
}
end
end

# Valid phone number can contain only 0-9/-()+xX and whitespace.
def valid_phone?(number)
return true if( number.nil? || number.strip.size == 0)
n_digits = number.scan(/[0-9]/).size
valid_chars = (number =~ /^[xX.+\/\-() 0-9]+$/)
return n_digits >= 5 && valid_chars
end

# Valid number can contain only digits and ,.
def valid_number?(number)
return true if( number.nil? || number.strip.size == 0)
valid_chars = (number =~ /^[\,\ 0-9]+$/)
return valid_chars
end

end

Saturday, October 4, 2008

Protecting app from Cross Site Scripting

RoR provides methods for escaping metacharacters. To HTML escape data in RoR simply add an "h" inside your output tag.

It is hard to remember using this everywhere. 

There are several plug-in available which you can install and forget about adding "h". I tried auto_escape plug-in. This modifies ActiveRecord and and automatically applies CGI.escapeHTML to all text column. The plug-in works by defining an after_find call_back from ActiveRecord. Whenever a record is found and loaded from DB, this escapes text columns. Plug-in worked great but it slowed down the APP.

xss_terminate plug-in makes stripping and sanitizing HTML automatic. Install the plug-in and forget about h() because you don't need to. This plug-in makes use of before_save hook and strips HTML tags. Generally there are more read operations than save, so performance wise this plug-in worked better for my case.

Tuesday, July 15, 2008

Textfield with auto complete

I wanted to add auto complete feature in a form field. I used auto_complete plug-in. It is a great plug-in, very simple to use. In few lines of code, I was able to get this up and running.

1) To install run script/plugin install auto_complete
2) In controller, add auto_complete_for :tag, :name where Tag is a model and name is a field, I wanted my auto complete on.
3) In view add <%= text_field_with_auto_complete :tag, :name %>. The method it implements will search through all the Tags in you records database and do a LIKE comparison on the name column, comparing the name to the contents of the tag[title] form field.
4) I had to reopen auto_plug to fix "Mysql::Error: #23000Column 'name' in where clause is ambiguous". In my case, for auto_complete_for method I was passing :joins condition.
I wanted to show tags for a given model. Both Tag and Model had same column name "name". To fix the problem, I created another plug_in name auto_complete_extended with single class init.rb. In init.rb I reopened auto_complete_for method (as follows):

class ActionController::Base
# Reopening auto_complete_for from auto_complete plug-in
### This is copy of auto_complete_for except for the part in condition where it qualifies column name with table name.

def self.auto_complete_for(object, method, options = {})
define_method("auto_complete_for_#{object}_#{method}") do
find_options = {
:conditions => [ "LOWER(#{object.to_s.pluralize}.#{method}) LIKE ?", '%' + params[object][method].downcase + '%' ],
:order => "#{method} ASC",
:limit => 10 }.merge!(options)

@items = object.to_s.camelize.constantize.find(:all, find_options)

render :inline => "<%= auto_complete_result @items, '#{method}' %>"
end
end
end

Thanks to for the following post. It helped me get started. http://codeintensity.blogspot.com/2008/02/auto-complete-text-fields-in-rails-2.html