Gastón Ramos

"La mayoría de las gaviotas no se molesta en aprender sino las normas de vuelo más elementales: como ir y volver entre playa y comida. Para la mayoría de las gaviotas, no es volar lo que importa, sino comer. Para esta gaviota, sin embargo, no era comer lo que le importaba, sino volar. Más que nada en el mundo, Juan Salvador Gaviota amaba volar".

Category: rails

Acts as Rubylit 2010

Este post es para invitarte a las jornadas de este año a realizarse en
la Facultad de Ing. y Ciencias Hídricas en Santa Fe,
en dónde vamos a tener charlas muy interesantes tanto para principiantes
como para los que están en niveles más avanzados, con oradores de reconocimiento
nacional e internacional.
Una vez más el evento es gratuito, pero es necesario que estés
registrado para poder participar del mismo, es por esto que te
invitamos a hacerlo desde acá:

https://eventioz.com/events/actsasrubylit-2010/registrations/new

podés ver la agenda acá:

http://eventioz.com/events/actsasrubylit-2010/agenda

Todos los participantes de las jornadas van a recibir un certificado firmado por las autoridades de La Facultad de Ing. y Ciencias Hídricas.

Contamos con tu presencia!

Usando filter_parameters en ApplicationController con Rails 2.3

Cómo uds. saben ApplicationController trae un método con el cual podemos ocultar datos sensibles que no queremos que aparezcan en los logs (véase la doc ) pero hay veces que además de ocultar los params en los logs, también necesitamos usar este mismo params ya procesado (con los valores FILTERED) en otro lado, por ejemplo para una app que loggea en una tabla todo lo que un user hace (incluido action, controler y params en cuestión) para esto podemos usar filter_parameters(params) que retorna el hash params filtrado, para entender un poco más cómo funciona podés leer los tests de rails en el archivo vendor/rails/actionpack/test/controller/filter_params_test.rb, cómo llegué a este archivo? fácil primero leí el código del método filter_parameter_logging, ahí me ví que crea un método al vuelo llamado filter_parameters y luego de eso simplemente hice un grep:


grep -r filter_parameters vendor/rails/ -n
vendor/rails/actionpack/test/controller/filter_params_test.rb:11:  
def test_filter_parameters
vendor/rails/actionpack/test/controller/filter_params_test.rb:13:    
assert !@controller.respond_to?(:filter_parameters)
vendor/rails/actionpack/test/controller/filter_params_test.rb:16:    
assert @controller.respond_to?(:filter_parameters)
vendor/rails/actionpack/test/controller/filter_params_test.rb:33:      
assert_equal after_filter, @controller.__send__(:filter_parameters, before_filter)
vendor/rails/actionpack/test/controller/filter_params_test.rb:43:      
assert_equal after_filter, @controller.__send__(:filter_parameters, before_filter)
vendor/rails/actionpack/test/controller/filter_params_test.rb:47:  
def test_filter_parameters_is_protected
vendor/rails/actionpack/test/controller/filter_params_test.rb:49:    
assert !FilterParamController.action_methods.include?('filter_parameters')
vendor/rails/actionpack/test/controller/filter_params_test.rb:50:   
 assert_raise(NoMethodError) { @controller.filter_parameters([{'password' => '[FILTERED]'}]) }
vendor/rails/actionpack/lib/action_controller/base.rb:486:        
define_method(:filter_parameters) do |unfiltered_parameters|
vendor/rails/actionpack/lib/action_controller/base.rb:493:            
  filtered_parameters[key] = filter_parameters(value)
vendor/rails/actionpack/lib/action_controller/base.rb:498:                 
 filter_parameters(item)
vendor/rails/actionpack/lib/action_controller/base.rb:515:        
protected :filter_parameters
vendor/rails/actionpack/lib/action_controller/base.rb:1319:      
  parameters = respond_to?(:filter_parameters) ? filter_parameters(params) : params.dup

Bueno cortito, pero útil ;)

Forrest, un runner para stories con rails

Estoy trabajando en un proyecto en el cual estamos usando stories como  framework para los tests de aceptación, resulta que me encontré con un pequeño incoveniente: los tests eran un poco lentos y no tenía forma de correr un test para un sólo scenario, si bien como son tests de aceptación o integración sabemos que hay que correrlos todos juntos, a veces cuando hay un error o failure es útil poder correr sólamente ese test y no todos. Entonces comencé a escribir un pequeño script para poder correr un sólo scenario de un story en particular, el script creció un poquito así que decidí crear una pequeña gema que se llama “Forrest” así que les muestro un poquito como funciona, es muy simple:

$ sudo gem install forrest

Successfully installed forrest-0.1.1
1 gem installed
Installing ri documentation for forrest-0.1.1...
Installing RDoc documentation for forrest-0.1.1...
Successfully installed forrest-0.1.11 gem installedInstalling ri documentation for forrest-0.1.1...Installing RDoc documentation for forrest-0.1.1...
$ cd /rails/my-project
$ forrest test/stories/auth-stories-test.rb

+ ClientSiteStoriesTest::TestAsAnAccountIWantToLoginToMyClientSiteSoICanStart
ManagingMyServices
|__ ClientSiteStoriesTest::TestAsAnAccountIWantToLoginToMyClientSiteSoICanStart
ManagingMyServices#test_Given_bad_params
|__ ClientSiteStoriesTest::TestAsAnAccountIWantToLoginToMyClientSiteSoICanStart
ManagingMyServices#test_Given_good_params

Para correr un scenario en particular simplemente copiamos y pegamos uno de las lista impresa más arriba y ejecutamos forrest así:

forrest ClientSiteStoriesTest::TestAsAnAccountIWantToLoginToMyClientSiteSoI
CanStartManagingMyServices#test_Given_good_params

o por ejemplo si estamos buscando un storie en particular podemos usar grep:

$ forrest test/stories/client_site_stories_test.rb | grep -y good_params

|__ ClientSiteStoriesTest::TestAsAnAccountIWantToLoginToMyClientSiteSoICan
StartManagingMyServices#test_Given_good_params

Otra opción es correr todos los scenarios de un story en particular, para esto tenemos que ejecutar forrest con el nombre del story, que también lo podemos copiar del listado impreso
cuando ejecutamos forrest con un archivo de stories como argumento, asi:


forrest ClientSiteStoriesTest::TestAsAnAccountIWantToLoginToMyClientSiteSoICanStart
ManagingMyServices

Otro inconveniente era que cuando había un error se mostraba toda lá página de error de rails lo cual era bastante molesto, con forresto los errores se ven un poquito más lindos:

forrest CustomerStoriesTest::TestAsACustomerIWantToResetMyPasswordIfIForgotItSo
ThatICanRecoverAccessToTheSite#test_Given_an_exisitng_account_email_but_without_
fill_in_the_company
==> Runing scenario...
================================================================================

- As a customer I want to reset my password if I forgot it so that I can recover 
  access to the site 

    Given an exisitng account email but without fill in the company
================================================================================

Finished in 0.061427 seconds.

  1) Error:
NoMethodError: undefined method `closed?' for nil:NilClass...
/usr/lib/ruby/1.8/net/http.rb:1060:in `request'
    /usr/lib/ruby/1.8/net/http.rb:772:in `get'
    lib/active_resource_extensions.rb:77:in `last'
    test/stories/customer_stories_test.rb:9:in `setup'

Cabe destacar que cuando estaba escribiendo forrest me encontré con este bug en rails:
https://rails.lighthouseapp.com/projects/8994/tickets/3153-actioncontrollerintegrationsession-broken-in-234

que si no lo fixean no van a poder usar forrest, por lo menos yo no pude.

Bueno esto fue todo, cualquier sugerencia comenten!

Rails edge, Ruby 1.9 style String interpolation support

Este es el primer post de una serie que he decidido comenzar titulada “Analizando commits de rails edge”. Aclaro que los post no van a tener orden alguno y que voy a comentar los que me llaman la atención a _mí_ :).
En este caso el commit se trata de agregar a rails la nueva forma de interpolación de strings que trae ruby 1.9 ( la idea es que se pueda usar con versiones menores también), pueden ver el código en este archivo:

activesupport/lib/active_support/core_ext/string/interpolation.rb

Cómo uds sabrán en Ruby se pueden interpolar strings de la siguiente manera:

>>"%s, %s" % ["Masao", "Mutoh"]
=> "Masao, Mutoh"

Cómo lo explica el comentario en el código:

 # call-seq:
  #   %(arg)
  #   %(hash)
  #
  # Format - Uses str as a format specification, and returns the result of applying it to arg.
  # If the format specification contains more than one substitution, then arg must be
  # an Array containing the values to be substituted. See Kernel::sprintf for details of the
  # format string. This is the default behavior of the String class.
  #   * arg: an Array or other class except Hash.
  #   * Returns: formatted String
  # Example:
  #    "%s, %s" % ["Masao", "Mutoh"]
  #
  # Also you can use a Hash as the "named argument". This is recommended way so translators
  # can understand the meanings of the msgids easily.
  #   * hash: {:key1 => value1, :key2 => value2, ... }
  #   * Returns: formatted String
  # Example:
  #   For strings.
  #   "%{firstname}, %{familyname}" % {:firstname => "Masao", :familyname => "Mutoh"}
  #
  #   With field type to specify format such as d(decimal), f(float),...
  #   "%d, %.1f" % {:age => 10, :weight => 43.4}

Es decir que ahora podemos interpolar strings de la siguiente manera:

>> "%{firstname}, %{familyname}" % {:firstname => "Masao", :familyname => "Mutoh"}
ArgumentError: malformed format string - %{
	from (irb):2:in `%'
	from (irb):2
>> 

Auuuchh! estoy probando con un irb con ruby 1.8.6, así que necesito requerir el archivo:

>> require 'activesupport/lib/active_support/core_ext/string/interpolation.rb'
=> true
>> "%{firstname}, %{familyname}" % {:firstname => "Masao", :familyname => "Mutoh"}
=> "Masao, Mutoh"

Ahora sí! podemos interpolar string nombrados pasando un hash como argumento, Hermoso!
Estuve mirando en los fuentes de Ruby 1.9 y no encuentro dónde está escrita esta nueva funcionalidad, el método que define la interpolación con”%” es rb_str_format_m y está en la línea número 1202 del archivo string.c en el trunk de ruby, pero sólo está la vieja forma de interpolación. Entonces dónde está ? Alguien sabe?
Es más hice esta prueba:

$ irb1.9
require 'activesupport/lib/active_support/string/interpolation.rb'
=> true
irb(main):002:0> "%{firstname}, %{familyname}" % {:firstname => "Masao", :familyname => "Mutoh"}
ArgumentError: malformed format string - %{
	from (irb):2:in `%'
	from (irb):2
	from /usr/bin/irb1.9:12:in `'

Y la versión que tengo de ruby 1.9 es:
ruby 1.9.0 (2008-06-20 revision 17482) [i486-linux]
Un poco vieja, probemos con ruby 1.9 compilado desde trunk
ruby 1.9.2dev (2009-07-11 trunk 24027) [i686-linux]

./ruby  -e 'puts "%{firstname}, %{familyname}" % {:firstname => "Masao", :familyname => "Mutoh"}'
Masao, Mutoh

Funcionó! entonces en algún lugar tiene que estar.

Acts as state machine y metaprogramming

Fe de erratas: En el artículo de abajo dónde expliqué cómo generar un named_scope por cada estado declarado con acts_as_state_machine hay un BUG el tema es así, los named_scope se definían en un método (self.define_named_scopes) que era llamado desde el constructor de la clase User, por consiguiente si queremos usar uno de estos named_scopes antes de que se instancie algún objeto de esta clase, el scope no existe, cómo era de esperar.

Entonces la solución rápida sería hacer directamente así:

class User   < ActiveRecord::Base
   acts_as_state_machine :initial => :inactive
   state :inactive
   state :active

   event :active do
     transitions :from => :inactive, :to => :active
   end

  ########################################################
  # define all state as named_scopes
  #
  self.states.each{|st|
    self.named_scope st, :conditions => { :state => st.to_s },
                      : order => 'created_at DESC'
  }
end

Hace bastante que vengo usando la genial biblioteca “Acts as state machine” que nos permite tener un máquina de estados finita (o autómata finito) en un modelo Active Record y a partir de las últimas versiones en cualquier objeto Ruby, y desde hace un tiempito que vengo pensando “Qué bueno sería que ASSM te genere un named_scope por cada estado posible”, entonces si por ejemplo tenemos 2 estados: active, inactive podríamos hacer algo así:

User.inactive # Esto retorna todos los usuarios con estado inactive
User.active # Esto retorna todos los usuarios con estado active

Bueno, acá está la solución que se me ocurrió:

class User   < ActiveRecord::Base
   acts_as_state_machine :initial => :inactive
   state :inactive
   state :active

   event :active do
     transitions :from => :inactive, :to => :active
   end

   def initialize(*args)
      self.class.define_named_scopes
      super *args
    end

  def self.define_named_scopes
    self.states.each{|st|
    self.named_scope st, :conditions => { :state => st.to_s },
                      : order => 'created_at DESC'
    }
  end
end

Nice eh! tenemos un método que escribe los named_scope por nosotros y seguro que se puede mejorar o quizás agregar una opción a AASM para que lo haga cuando lo deseemos, el tema es que cómo necesitamos la lista de los estados, debemos ejecutar esto después de especificar los mismos, aunque una solución más elegante sería modificar AASM y hacer que los named_scope se genere cada vez que especificamos un estado.

Hasta la próxima!

Nested Forms rails 2.3

En el proyecto en el que estoy actualemente estamos usando Rails 2.3, uno de los motivos de usar rails 2.3 es que nos venía muy bien la nueva feature “Nested Forms”, resultaque estuve dando vueltas con algunos problemas hasta que finalmente los saque funcionando bien.

Les cuento yo tengo un formulario para crear un user, el modelo de user es el siguiente:

class User < ActiveRecord::Base
   has_one :person, :as => :personable, :dependent => :destroy
   accepts_nested_attributes_for :person
....
end

Es decir que un user tiene datos de contacto a travez de has_one :person (suena raro creo que se podría cambiar por has_one :contact_info, :class_name => “Person”) , y entonces el form era el siguiente:

<% form_for(:user, :url => users_path) do |f| -%>

<p><%= label_tag 'login' %><br/>
<%= f.text_field :login %></p>

<p><%= label_tag 'password' %><br/>
<%= f.password_field :password %></p>

<p><%= label_tag 'password_confirmation', 'Confirm Password' %><br/>
<%= f.password_field :password_confirmation %></p>

<% f.fields_for(:person) do |person_fields| %>
  <p><%= person_fields.label :email %><br/>
  <%= person_fields.text_field :email %></p>

  <p><%= person_fields.label :first_name, 'First Name:' %><br/>
  <%= person_fields.text_field :first_name %></p>
<% end %>

<p><%= submit_tag 'Create User' %></p>

<% end -%>

Resulta que esto no me funcionó y me daba el siguiente error:

ActiveRecord::AssociationTypeMismatch (Person(#-619527948) expected, got HashWithIndifferentAccess(#-607513158)):

El problema puntual era que no generaba bien el form y en vez de generar esto:

<input id="user_person_attributes_first_name"
  name="user[person_attributes][first_name]" 
  size="30" type="text" />

generaba esto:

<input id="user_person_first_name"
  name="user[person][first_name]" 
  size="30" type="text" />

Y entonces daba el error por que ActiveRecord (Nested model) genera un método person_attributes= en modelo dónde declaramos el accepts_nested_attributes_for (user en este caso) y por eso el form anterior no funciona como era de esperar.

En el controller tenía lo siguiente:

class UsersController < ApplicationController
  def new
    @user = User.new(:person => Person.new)
  end

  def create
    @user = User.new(params[:user])
    @user.save
  end
.....
end

La solución fue modificar el form y hacer así:

<% form_for @user do |f| -%>

<p><%= label_tag 'login' %><br/>
<%= f.text_field :login %></p>

<p><%= label_tag 'password' %><br/>
<%= f.password_field :password %></p>

<p><%= label_tag 'password_confirmation', 'Confirm Password' %><br/>
<%= f.password_field :password_confirmation %></p>

<% f.fields_for(:person) do |person_fields| %>
  <p><%= person_fields.label :email %><br/>
  <%= person_fields.text_field :email %></p>

  <p><%= person_fields.label :first_name, 'First Name:' %><br/>
  <%= person_fields.text_field :first_name %></p>
<% end %>

<p><%= submit_tag 'Create User' %></p>

<% end -%>

cambié el

<% form_for(:user, :url => users_path) do |f| -%>

por

<% form_for @user do |f| -%>

El form para editar un user quedó prácticamente igual y funciona con los attributes de person y todo sin hacer nada del lado del controller. Ahora la pregunta es: Esta bien que nested_forms solamente funcione con la forma?:

<% form_for @user do |f| -%> 

Si alguien tiene la respuesta que comente!

Saludos

Rails agrega soporte para “shallow nested routes”

Muchas veces cuando tenemos recursos anidados nos encontramos definiendo un map.resource (o una ruta) adicional para acceder al mismo recurso pero sin la anidación. Para referenciar a un miembro específico de este sin el prefijo del padre sin tener que definir otra ruta, Rails edge agrega una opción a map.resource para lograr esto.

Ejemplo:

map.resources :users, :shallow => true do |user|
  user.resources :posts
end

* GET /users/1/posts (mapea a la acción PostsController#index action como siempre)
se agrega la ruta nombrada “user_posts” como siempre.

Lo nuevo:

* GET /posts/2 (mapea a la acción PostsController#show como si no sería anidada)
Adicionalmente, se agrega también la ruta nombrada “post”.

El libro “Rails 2.1 ¿Qué hay de nuevo?” traducido al español

Tengo el agrado de de comunicarles que junto con Lucas Florio hemos finalizado la traducción completa al español del libro “Rails 2.1 ¿Qué hay de nuevo?” de Carlos Brando, la pueden descargar desde aquí y también está disponible la versión en html, este es el proyecto en github. Desde ya cualquier sugerencia o corrección es bienvenida.

Descargar el libro: http://gastonramos.com.ar/rails21/rails21-que-hay-de-nuevo.pdf

Acts as date Module

Esta es una solución que se me ocurrió para hacerme más cómodo el trabajo con
el atributo created_at de un modelo, por ej:

Para saber el año de creación de un post, haríamos lo siguiente:

>> p = Post.find(1)
>> p.created_at.month
=> 6

como podemos observar retorna 6, pero resulta que yo lo necesito necesito con el siguiente formato: “06”. Lo mismo pasa con el día:

>> p = Post.find(1)
>> p.created_at.day
=> 4
module ActsAsDate

  def year
    created_at.strftime("%Y")
  end

  def month
    created_at.strftime("%m")
  end

  def day
    created_at.strftime("%d")
  end

  def year_month
    [year, month]
  end

  # Armo un string para usarlo en un find
  #    ej:
  #   params = {:year => "2008", :month => "06"}
  #   Post.all(:conditions => Post.created_at_conditions(params))
  #
  def self.included(klass)
    klass.class_eval do
      def self.created_at_conditions(params)
        return nil if params[:year].blank? or params[:month].blank?
        "created_at like '#{params[:year]}-#{params[:month]}%'"
      end
    end
  end

end

Entonces tenemos que incluir este módulo en modelo que lo queremos usar:

class Post < ActiveRecord::Base

  include ActsAsDate

end

Ahora con esto directamente hago p.month, p.day, p.year y obtengo el mes, día y año de creación del post con el formato que necesito y escribiendo menos código :). Además agregué un método que me arma el param conditions, que se podría un usar así por ej:


class PostsController < ApplicationController

def list
    @posts = Post.paginate(
         :page => params[:page],
         :order => 'created_at DESC',
         :conditions => Post.created_at_conditions(params)
    )

    respond_to do |format|
      format.html
      format.xml  { render :xml => @posts }
    end
  end

end

De esta manera no tengo que estar preguntando en el controller si vienen (year y month) como parámetros, por ahora este modulo sólo funciona con el atributo created_at por que está hardcodeado, pero con pocas modificaciones se podría hacer que funciona para cualquier atributo.
Ahora lo que me pregunto es si Rails ya tiene alguna solución mejor para este problema, si alguien la conoce que me lo haga saber por favor (yo no la encontré), además se aceptan modificaciones, sugerencias y comentarios.

Información de debug en Rails

Muchas veces cuando estamos desarrollando una aplicación web necesitamos mirar cierta información de debug, acá les dejo una mi versión de este senippet que leí por acá .

La idea es poner este código en un partial:

 app/view/views/shared/_debug_info.html.erb

y luego ponemos el render en el layout de la aplicación:

 <%= render :partial => 'shared/debug_info' %>

Va el pastie: http://pastie.org/222621