martes, 30 de noviembre de 2010

Tablas particionadas

Si tienes una tabla con varios millones de registros y sabes de antemano que se va a consultar por periodos mensuales, puedes particionar la tabla por años y meses. Así cuando hagas consultas, sólo utilizarás la partición correspondiente y mejorará el rendimiento.

Al particionar por los valores de un determinado campo, es obligatorio tener dicho campo como parte de la primary key de la tabla.

A continuación una migración para crear una tabla particionada por meses y años:

 class CreateHistoryAlerts < ActiveRecord::Migration

  FIRST_YEAR = 2010
  LAST_YEAR  = 2020

  def self.up
    create_table :history_alerts do |t|
      t.integer :agent_id,              :references => nil
      t.integer :alert_type_id,         :references => nil
      t.integer :country_id,            :references => nil
      t.string  :reason
      t.boolean :read

      t.timestamps
    end

    sql = "ALTER TABLE `history_alerts` drop primary key, add primary key(id, created_at)"
    execute(sql);


    sql = "ALTER TABLE `history_alerts` PARTITION BY RANGE (YEAR(created_at)) \
           SUBPARTITION BY HASH (MONTH(created_at)) \
           ("

    for year in (FIRST_YEAR..LAST_YEAR)
      sql += "PARTITION p_#{year} VALUES LESS THAN (#{year}) \
              (SUBPARTITION s_jan_#{year}, \
               SUBPARTITION s_feb_#{year}, \
               SUBPARTITION s_mar_#{year}, \
               SUBPARTITION s_apr_#{year}, \
               SUBPARTITION s_may_#{year}, \
               SUBPARTITION s_jun_#{year}, \
               SUBPARTITION s_jul_#{year}, \
               SUBPARTITION s_aug_#{year}, \
               SUBPARTITION s_sep_#{year}, \
               SUBPARTITION s_oct_#{year}, \
               SUBPARTITION s_nov_#{year}, \
               SUBPARTITION s_dec_#{year}),"
    end
    sql += "PARTITION p_all VALUES LESS THAN MAXVALUE \
            (SUBPARTITION s_jan_all, \
             SUBPARTITION s_feb_all, \
             SUBPARTITION s_mar_all, \
             SUBPARTITION s_apr_all, \
             SUBPARTITION s_may_all, \
             SUBPARTITION s_jun_all, \
             SUBPARTITION s_jul_all, \
             SUBPARTITION s_aug_all, \
             SUBPARTITION s_sep_all, \
             SUBPARTITION s_oct_all, \
             SUBPARTITION s_nov_all, \
             SUBPARTITION s_dec_all))"
    execute(sql);
  end


  def self.down
    drop_table :history_alerts
  end

end

lunes, 7 de junio de 2010

Tabs On Rails

Ya estaba cansado de ver toda la morralla de código en las vistas para pintar las pestañas, y al final he probado el plugin tabs_on_rails (http://code.simonecarletti.com/projects/tabsonrails/wiki)

Mucho más limpio y sencillo. En las vistas solemos tener un partial que pinta las pestañas. Aquí podemos poner algo como:

<% tabs_tag(:builder => MyTabBuilder) do |tab| %>
  <% if logged_in? %>
    <%= tab.home I18n.t('tabs.home'), member_dashboard_path %>
    <%= tab.members I18n.t('tabs.members'), member_profiles_path %>
  <% else %>
    <%= tab.home I18n.t('tabs.home'), root_path %>
    <%= tab.members I18n.t('tabs.members'), profiles_path %>
  <% end %>

<% end %>

Si quieres personalizar la forma de pintar las pestañas, puedes crear tu propio Builder en el directorio lib, por ejemplo:

class MyTabBuilder < TabsOnRails::Tabs::Builder
  def tab_for(tab, name, options)
    cls = (current_tab?(tab) ? 'selected' : '')
    @context.content_tag(:li) do
      @context.link_to(name, options, :class => cls)
    end
  end
end


Ahora en los controladores, eliges la pestaña que quieres seleccionar, pudiendo incluir lógica o diferenciar por action:

class ProfilesController < ApplicationController
  set_tab :home
  set_tab :members, :if => :my_profile?
  set_tab :posts, :only => %w(list edit)
 
  ...
  private

  def my_profile?
    !params[:id].blank? && params[:id].to_i == current_user.id
  end

end

jueves, 11 de febrero de 2010

merge_conditions

Más de una vez hemos tenido que crear una query dinámicamente, en función de los parámetros que recibimos.

Una opcion es mantener el string de la condicion y una hash con los parametros:

str_conditions => "email is not null"
h_conditions = {}

if !params[:group_ids].blank?
  str_conditions += " and group_id in (?)"
  h_conditions[:group_ids] = params[:groups_ids]
end

if !params[:manager_id].blank?
  str_conditions += " and manager_id = ?"
  h_conditions[:manager_id] = params[:manager_id]
end

User.find :all, :conditions => [str_conditions, h_conditions]


Pero se engorrina bastante, con tanto "+=" y con tanto "and" y con tanta hash.

Otra opcion más clara es tener cada grupo de condiciones separado, y utilizar luego el metodo merge_conditions de ActiveRecord::Base.

c1=(params[:group_ids].blank?) ? [] : ["group_id in (?)", params[:groups_ids]]
c2=(params[:manager_id].blank?) ? [] : ["manager_id = ?", params[:manager_id]]


User.find :all, :conditions => merge_conditions("email is not null", c1, c2)

martes, 26 de enero de 2010

Configuracion de las aplicaciones

Para definir los parámetros configurables de la aplicación, utilizo lo siguiente: un fichero config/config.yml y un initializer config/initializers/local_config.rb:

En el config.yml, mantengo en formato yaml los valores de los parámetros:

development:
  authentication: "restful" 
  manager:
    activities:
      activate: "Activate"
      create: "Create"

En local_config.rb:

APP_CONFIG = YAML.load_file("#{RAILS_ROOT}/config/config.yml")[RAILS_ENV]


Así, desde cualquier punto de la aplicación puedo hacer:

if APP_CONFIG['authentication'] == 'restful'
...
end

o

lst = APP_CONFIG['manager']['activities']

que devolveria una hash con:

{"create"=>"Create", "activate"=>"Activate"}

lunes, 25 de enero de 2010

Formularios personalizados

Tienes que pintar siempre el mismo tipo de formulario en la aplicacion? Por ejemplo, tienes que pintar siempre una etiqueta al lado del input?

Tienes un formulario del siguiente estilo:

<% form_for :user do |f| %>
<fieldset>
    <p>
      <label>Login</label><br/>
      <%= f.text_field :login %>
   </p>
    <p>
      <label>Password</label><br/>
      <%= f.password_field :password %>
   </p>
</fieldset>
<% end %>


Quedaría más limpio así:

<% form_for :user, :builder=>LabeledFormBuilder do |f| %>
<fieldset>
     <%= f.text_field :login, :label=>"Login" %>
     <%= f.password_field :password, :label=>"Password" %>
</fieldset>
<% end %>


Todo lo que tienes que hacer es crearte una clase en app/builders:

class LabeledFormBuilder < ActionView::Helpers::FormBuilder

  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} # Don't decorate these

  helpers.each do |name|
    define_method(name) do |field, *args|
      options = args.last.is_a?(Hash) ? args.pop : {}
      label = label(field, options[:label], :class => options[:label_class])
      @template.content_tag(:p, label +'
' + super)  #wrap with a paragraph
    end
  end

end



y añadir a tu config/environment.rb:


config.load_paths += %W( #{RAILS_ROOT}/app/builders)


Si quieres que por defecto todos tus formularios usen este builder, inicializa en application_helper o en algun initializer:

ActionView::Base.default_form_builder = LabeledFormBuilder

Controllers y Presenters

Cuando nos queremos dar cuenta, tenemos los controladores llenos de código y empiezan a ser pesados y difíciles de mantener. He visto que hay un patrón de diseño que permite mantener toda esta lógica "sobrante" en otra clase, y dejar en los controladores sólo la funcionalidad de recuperar datos de los modelos y decidir el flujo de la navegación. A este patrón lo llaman Presenter.

Consiste en crear una clase donde se haga toda la recogida de datos que se hace en los controladores. Por ejemplo, antes tenia un controlador con esta acción:

def show
  @profile = Profile.active.find(params[:id])
  @friends = @profile.friends.first 12
  @recommended_profiles = @profile.recommended(:limit => 12)
  @graffities = @profile.graffities.all(:limit => 12)
  Comment.preload_wall_associations(@graffities)
 

  store_location
 

  respond_to do |format|
    format.html # show.html.erb
    format.xml { render :xml => @profile }
  end
end


Los hay peores, pero esto se puede sustituir por:

  def show
    @presenter = ProfilePresenter.new(params)
    store_location

    respond_to do |format|
      format.html # index.html.erb
      format.xml { render :xml => @presenter }
    end
  end


Como se ve, en ProfilePresenter dejamos toda la lógica de recogida de datos.

class ProfilePresenter

  def initialize(params)
    @presenter_params = params
  end
 
  def profile
    @profile ||= Profile.active.find(@presenter_params[:id])
  end

  def friends
...

  end


  def recommended
...
  end


  def graffities
...
  end

end



Asi, en las vistas, podemos utilizar @presenter.profile o @presenter.friends, etc. y dejamos los controladores claros y limpios.

Estos presenters pueden dejarse en el directorio app/presenters, por ejemplo. No te olvides de añadir a config/environment.rb la linea:

config.load_paths += %W( #{RAILS_ROOT}/app/presenters)