Showing posts with label Ruby. Show all posts
Showing posts with label Ruby. Show all posts

Monday, May 16, 2011

Use helpers in controller

Rails applied Restful as part of its design.

To follow Restful, you should:

1. Model your web app as resources
2. Manipulate your app's resources through a conventional interface

Ex:
- if you have Product resource then urls to CRUD (Create, Edit, Update, Delete) this resource would be:

Action Urls Web MethodRestful Interface
Create /products POST products_url
Edit /products/1/edit POST edit_product_url
Update /products PUT product_url
Delete /products DELETE product_url

Then to get links to delete/edit a product through Restful interface, we can use link_to method as below:

link_to 'Edit', edit_product_path(product)

link_to 'Remove', product_path(product), :confirm => 'Are you sure?', 
        :method => :delete

By default, you can only use above interfaces in View layer. How can we use those in Controller layer?
  • In Rails 2, call through @template variable
    @template.link_to('Edit', edit_product_path(product))
  • In Rails 3, call through view_context method
    view_context.link_to('Delete', product_path(product), :confirm => 'Are you sure?', :method => :delete)
References

Sunday, March 20, 2011

A simple URL shortening algorithm

Shortening a URL is a convenient way to save long URL to make use of space when posting. It's especially popular on Twitter where message is limited to 140 words. Many websites provide this service such as tinyurl.com, bit.ly,...

There some gems or wrapper to use that service in your Rails app. But the shortened URLs belong to another domain (ex: http://bit.ly/ek8Hhe which belongs to bit.ly). If you want to make it belonged to your domain (ex: http://example.com/wfds7i), you must implement your own URLs shortener.

This is a simple way to do that.

Basically, the problem is:
given a URL, how to map it to a string which has pattern XXXXXX, where X belongs to {0..9a-zA-Z}. There would be 62^6 = 56800235584 such strings. That amount is almost enough.
Then the simple idea to solve that problem is:
map the URL to an integer in 1..62^6. That number must correspond to a string in space {XXXXXX} that could be calculated by using a 10-base to 62-base conversion algorithm (you can understand easily by figuring out how to convert a decimal number to hexa number). 

Here is an implementation of mine in Ruby:

class URLShortener
  CHARSET = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
  BASE = 62
  CODE_LENGTH = 6

  def self.encode(id)
    code = ""
    while (id > 0) do
      code = CHARSET[id % BASE].chr + code
      id = id / BASE
    end

    (code.length > CODE_LENGTH) ? "" : "0" * (CODE_LENGTH - code.length) + code 
  end

  def self.decode(code)
    return -1 if code.length != CODE_LENGTH
    id = 0
    for i in 0..(CODE_LENGTH-1) do
      n = CHARSET.index(code[i])
      return -1 if n.nil?
      id += n * (BASE ** (CODE_LENGTH - i - 1))
    end
    return id
  end
end

Saturday, March 12, 2011

A case of "Broken Pipe" error in Rails

Have you ever seen "Broken PIPE" error in Rails?

It somehow like this:


*** Exception Errno::EPIPE in Passenger RequestHandler (Broken pipe) (process 22235):
    from /usr/lib/ruby/gems/1.8/gems/passenger-2.1.2/lib/phusion_passenger/rack/request_handler.rb:67:in `write'


This error seems appear when your program interacts improperly with external programs. In my case, it happened to me in a project that I used XMLRPC library to remotely communicate with some web services.

My implementation for that feature is as following:
- create a XMLRPC connection object
- use that XMLRPC object every time calling a webservice API

There some other guys had been headache with that error also:
http://gaveen.owain.org/2008/04/errnoepipe-broken-pipe-mysql-error-in.html
http://stackoverflow.com/questions/1082166/exception-errnoepipe-in-passenger-requesthandler-broken-pipe
http://stackoverflow.com/questions/4351624/ruby-on-rails-errnoepipe-broken-pipe

and the reasons they found come around Passenger or Mysql.

In my case, I've got the same thing even with upgrading Passenger or migrating to another web server.
After a lot of retries, I've found that if I call the API very often, "Broken pipe" doesn't appear, but if I stop doing anything for a while and then call the API, it does happen. That observation led me to a guess: XMLRPC connection object may be broken after a certain amount of time (timeout). Excellently, that's exactly right! I then changed my implementation so that every time calling a webservice API, I use a newly created XMLRPC connection object instead of using the same XMLRPC connection object for every API calls.

That solved my problem!

Monday, September 27, 2010

Ruby code snippets

1. using 'any?' to check if something in a array/hash
# a = User.all
a.any? {|user| user.name == "Hoang"}

Thursday, September 23, 2010

Some ways to refactoring Rails' code

On the following, I list some ways I found to refactoring my Rails' code. This list would be added more as time's going.

1. Method delegation
    Assume you have two models:
       + User(id, name)
       + Account(id, number, user_id)
    It could be described in Rails as:

       class User < ActiveRecord::Base
         has_many :accounts
       end
       class Account < ActiveRecord::Base
         belongs_to :user
       end

    Then if I have an Account instance named account, I could get it's user's name by: account.user.name or I can define an instance method in Account as follow:

       class Account < ActiveRecord::Base
         belongs_to :user
         
         def user_name
           user.name
         end
       end  
    Then you can get account's user name by: account.user_name. We can make the above code shorter by using delegation:

       class Account < ActiveRecord::Base
         belongs_to :user
         delegate :name, :to => :user, :prefix => true   ## this will generate user_name methods 
       end  

2. Use define_method to dynamically generate similar methods
    Given a model User(id, name, role) where role could be "admin", "moderator", "senior", "junior",... You can write instance methods to see whether a user is "admin", "moderator", ... as follow:

Before
class User < ActiveRecord::Base
  ROLES = {
    :admin => "admin",  
    :moderator => "moderator",
    :senior => "senior",
    :junior => "junior"}

    def admin?
      role == ROLES[:admin]
    end
        
    def moderator?
      role == ROLES[:moderator]
    end

    def senior?
      role == ROLES[:senior]
    end
        
    def junior?
      role == ROLES[:junior]
    end          
end
After
class User < ActiveRecord::Base
  ROLES = {
    :admin => "admin",     
    :moderator => "moderator", 
    :senior => "senior", 
    :junior => "junior"} 

    ROLES.each_pair do |key, val| 
      define_method(key.to_s + "?") { role == val}
    end
end

Monday, May 17, 2010

An issue with Rspec on Windows



Rspec is a very popular test framework used to test Rails application. I have tried to install and use it for my Rails projects on Windows. It raised a bug when I ran tests:
c:/Ruby/lib/ruby/gems/1.8/gems/activesupport-2.3.5/lib/active_support/dependencies.rb:440:in load_missing_constant': uninitialized constant RbReadline::Encoding (NameError)
from c:/Ruby/lib/ruby/site_ruby/1.8/rbreadline.rb:4404 
I traced back and found these causing lines:
   if defined? ''.getbyte
      @encoding = "X"      # ruby 1.9.x or greater
      @encoding_name = Encoding.default_external.to_s
   end
It seems the Rspec uses Ruby Readline library for its command-line interface. It defined getbyte that uses Encoding module which hasn't been defined in this Ruby package on Windows. I then commented out the line using Encoding module and the bug get fixed...
   if defined? ''.getbyte
      @encoding = "X"      # ruby 1.9.x or greater
      # @encoding_name = Encoding.default_external.to_s
   end
It's not common to pass over the issue by this way but hopefully it still works fine in my case.