Archive for the ‘rails’ Category

Dynamics finders in ActiveRecord using AOP.

Yesterday, I have been playing with Aquarium, an AOP framework for ruby. I decided hack a little bit. I try to refactor the dynamics finders of ActiveRecord, using an AOP approach.

First lets try to understand what are and how dynamics finders works.

This are queries are equvalent :

User.find(:first, :conditions => ["name = ?", name])
User.find_by_name(name)

User.find(:all, :conditions => ["city = ?", city])
User.find_all_by_city(city)

User.find(:all, :conditions => ["street = ? AND city IN (?)", street, cities])
User.find_all_by_street_and_city(street, cities)

And how this cool methods are generated? They are not generated, ActiveRecords use a bit of the method_missing magic to parse the method_name to find a pattern. A simplify version of the method_missing :

def method_missing(method_id, *arguments)
  if match = /find_(all_by|by)_([_a-zA-Z]\w*)/.match(method_id.to_s)
    # find...
  elsif match = /find_or_create_by_([_a-zA-Z]\w*)/.match(method_id.to_s)
    # find_or_create...
  else
    super
  end
end

Now its time to introduce the AOP in the recipe. We create two advices, the pointcut is the method missing. This two advices perform as a chain, first the last defined.

class Base
    before :method=>:method_missing do |this, execution_point, *args|
        if match = /^find_(all_by|by)_([_a-zA-Z]\w*)$/.match(args.fir st.to_s)
            # find_by_xxx... #do something smart and break the flow
        end
    end

    before :method=>:method_missing do |this, execution_point, *args|
        if match = /^find_or_(initialize|create)_by_([_a-zA-Z]\w*)$/. match(args.first.to_s)
          # find_or_xxx #do something smart and break the flow
        end
    end

  # And so on, until you have a minimum common behaviour or an empty method_missing.   
end

We can extract more behaviour from the method missing, We can repeat this process until we have the minimum common behaviour or a empty method_missing. Sometimes we neer to break the chain at some point. In this cases we can uses around.

One of the benefits of this approach is that will be more easy to introduce new finder in rails. Currently to create a new finder we need to create a plugin in the vendor folder. For example we are going to create a find_like finder. We write our code in the vendor/plugins/find_like/lib/find_like.rb :

module ActiveRecord
  class Base
    class << self
      private
      alias_method :previous_method_missing, :method_missing
     def method_missing(method_id, *arguments)
        if match = /find_(all_like|like)_([_a-zA-Z]\wo*)/.match(method_id.to_s)
           # do something smart 
        else
          previous_method_missing(method_id, *arguments)
        end
      end
    end
  end
end

If we use the AOP approach we need to write, also our code in the vendor/plugins/find_like/lib :

module FinderLike
 # A custom finder finder_like_xxx
 before :types => :Base, :methods =>:method_missing do |execution_point,  *args|
   if match = /find_(all_like|like)_([_a-zA-Z]\w*)/.match(args.first.to_s)
     # Do something smart and break the flow     
   end
 end
end

I Don’t know if this is a good design in this particular case. But at least was a funny Kata.

Advertisements

Ramdon tips for Rails

  • MVC as a constraint
  • Over do MVC
  • Don’t put any code in the view
  • Don’t do any DB operation in the controller
  • Set Default Values in Methods : Avoid unexpected behaviour.
  • Simple Hash as Default Method Parameter : This form allows you to add functionality to methods without breaking existing calls
  • Helpers are hard to test, its will turn Rails into PHP

Snippet : session expires in rails

  def check_auth
    if session[:admin_authenticated] 
      if session[:expires]  "login"
      end
    else
      redirect_to :action => "login"
    end
  end

Rails and how he decide to render on errors

  1. Rails makes a distinction between local request and public requests.
  2. On error, two different things happen depending on if the request is local or public.
  3. With a local request, Rails internal templates are rendered optimized for developers and displaying exception details.
  4. With a public request, Rails renders either public/404.html or a static error message.

Via semergence

Two snippets on how to refactor a ruby application using block helpers.

Poor coding approach :
1, Insert HTML code as strings

<%= link_to "<strong>#{product.name}</strong><span>
            #{pluralize(product.topic_count(company), 'topic')}</span>",
    href, :class => "product_label" %>

2. Use condinional sentences to display content :

 <% if @user.rol == :admin %>
   Section only for admins
 <%end >

Solutions,

1. Replace the old lin_to with a block helper (via educate, liberate):

<% link_to browse_url(product), :class => "product_label" do %>  <%= product.name %>

(<%= pluralize(product.topic_count(company), 'topic') %>)

<% end %>

2. Use a code block helper to insolate the rol administation logic :

 <% @user.admin? do %>
   section only for admins
  <%end%>

Know Ruby (& Rails) Well

You should understand the following before
delving too deeply into complex helpers and
y FormBuilders:

  1. Enumerable (inject, select, reject, map, etc)
  2. Basic Ruby programming techniques (instance/class/module_ eval, define_method, etc)
  3. ActiveSupport ( returning, constantize , etc)

When V is for Vexing: Patterns to DRY Up Your Views

Bad Smells sign in rails code.

  1. Calling find on a model direct
    1
    2
    3
    4
    %  Sales.find_all_by_region(params[:region]) 
                                    .each do |t| %>
    
      # some inteligent code
    
    <% end %>
  2. alling find on an association
  3. Conditionally inserting content if/else clauses, case statements, etc
    1
    <% if current_user.admin? %>Admins see this<% end %>
  4. Doing a complex inline map, sort, select, etc on a collection
  5. 1
    
    2
    <% applications.sort_by{|app| [app.priority, app.creator.name]}
                                  .each do |app|
    <% end %>
  6. assigning temporary variables
    1
    
    2
    
    3
    
    4
    
    5
    
    
    <% indicator_id = "loading_indicator_#{item.id}" %>
    
        <%= link_to_remote("Show Status",:url => item_path(item),
    
            :loading => "$('#{indicator_id}').show()",
    
            :complete => "$('#{indicator_id}').hide()",) %>
    
    <%= image_tag('progress.gif', :id => indicator_id)%>
    
    

When V is for Vexing: Patterns to DRY Up Your Views

My rake cheatsheet for rails

Rake :

  • rake db:migrate
  • rake db:sessions:create
  • rake db:sessions:clear
  • rake log:clear
  • rake rails:freeze:gems
  • rake rails:freeze:edge
  • rake rails:unfreeze
  • rake stats

Install SQLite3 in Ubuntu

  1. Install Sqlite3
    
    sudo apt-get install sqlite3 libsqlite3-dev
    sudo gem install sqlite3-ruby
  2. To create a data base, we only need to create a empty file.
    
    touch database_name_dev.db
    touch database_name_test.db
    touch database_name_prod.db
    
  3. Configure Ruby on Rails Modificar el archivo config/database.yml:
    
    development:
      adapter: sqlite3
      database: db/database_name_dev.db
    
    test:
      adapter: sqlite3
      database: db/database_name_test.db
    
    production:
      adapter: sqlite3
      database: db/database_name_prod.db
    

Reminder : Migrate supports values

basic data types :

  1. :string
  2. :text
  3. :decimal
  4. :integer
  5. :float
  6. :datetime
  7. :timestamp
  8. :date
  9. :time
  10. :binary
  11. :boolean