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.

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

Wednesday, 18 February , 2009

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

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”.

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

Thursday, 26 June , 2008

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],
         :o rder => 'created_at DESC',
         :conditions => Post.created_at_conditions(params)
    )

    respond_to do |format|
      format.html
      format.xml  { render :x ml => @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

Thursday, 26 June , 2008

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

Rails agrega un nuevo método al módulo Enumerable. Vamos al código para explicar cómo funciona este nuevo método:

>> Post.all.many?
=> false
>> Post.create({:title => "hola", :post => "hola"})
>> Post.all.many?
=> false
>> Post.create({:title => "otro hola", :post => "otro hola"})
>> Post.all.many?
=> true
>> Post.all.size
=> 2
>> posts = Post.find(:all, :conditions => {:post => "hola"})
>> posts.many?
=> false
>> posts.size
=> 1

La función de este nuevo método many? es básicamente encapsular collection.size > 1, es un detalle bastante cómodo que opinan uds?

Grupo de usuarios de Ruby del litoral

Sunday, 02 September , 2007

Esta semana hablando con algunos chicos de lug oro verde nos surgió la idea de hacer una reunión con todos aquellos que estamos interesados en ruby y ruby on rails, entonces pensé… por que no crear un grupo de usuarios de ruby del litoral? y acá está el resultado: tenemos wiki y lista de correo , desde ya litoraleños están todos invitados a participar, los esperamos!!!.

http://www.rubylit.com.ar/

Bueno, lo primero que quiero hacer es agradecer a la gente de lugoroverde por invitarnos a las jornadas, la charla estuvo muy buena (mejor que de lo que yo creía ) la gente quedó muy entusiasmada con esto de Rails, luego de algo de teoría hicimos un pequeño ejemplo práctico, creamos un modelo y mostramos el scaffold y como personalizar las vistas creadas.

Les dejo acá los slides de la charla dwsr.pdf
La última parte de la charla