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
martes, 30 de noviembre de 2010
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:
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:
def my_profile?
!params[:id].blank? && params[:id].to_i == current_user.id
end
end
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 %>
<% 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 :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:
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]]
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:
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:
o
que devolveria una hash con:
{"create"=>"Create", "activate"=>"Activate"}
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:
Quedaría más limpio así:
Todo lo que tienes que hacer es crearte una clase en app/builders:
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:
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 %>
<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 %>
<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
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:
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.
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)
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)
@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
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
...
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)
Suscribirse a:
Entradas (Atom)