‘border-radius’ mixins de andar por casa

28 de noviembre de 2011

Últimamente estoy dándole algo de caña a Compass y usándolo en algún que otro proyecto. Es un framework CSS increíble, con muchas herramientas para hacernos la vida más fácil a los fronteneros (o al menos a intentarlo). Entre muchas de estas herramientas está el módulo CSS3, que proporciona mixins para propiedades CSS3 como por ejemplo border-radius. Os animo a darle una oportunidad a este fantástico framework, nos os decepcionará.

En cualquier caso, si no os da por probar Compass, pero usáis SCSS y estáis hartos de repetiros como loros en vuestro código (DRY!) estáis tardando en utilizar mixins y variables para solucionarlo. Aquí os dejo un ‘mixin casero’ para la propiedad border-radius, que no es (pero casi) como los de Compass, pero dan el apaño. Enjoy it!

Desactivar Spotlight

27 de septiembre de 2011

Fácil y rápido. Para desactivar la indexación, en consola haremos:

sudo mdutil -i off

Si por cualquier motivo queremos volver a reactivarlo y además limpiar la indexación anterior:

sudo mdutil -i on / && sudo mdutil -E /

validates uniqueness y Shoulda

31 de agosto de 2011

Generalmente utilizo Factory Girl y Shoulda para testear aplicaciones rails. El primero os lo recomiendo y sobre Shoulda, aunque siempre me da algún que otro dolor de cabeza, también me da muchas satisfacciones.

Hoy me encontraba en la tesitura de testear que un atributo tuviera un valor único.

class Example < ActiveRecord::Base
  validates :name, :uniqueness => true
end

Si incluimos el test correspondiente con shoulda nos da un error del tipo should require case sensitive unique value for … Para solucionarlo basta con incluir la primera línea que podéis leer en el test:

class ExampleTest < ActiveSupport::TestCase
  subject { Factory(:example) }
  should validate_uniqueness_of(:name)
end

should validate_uniqueness_of necesita tener un registro creado y con subject conseguimos tal objetivo. Podéis obtener más información sobre subject, que es bastante interesante, en la documentación de Shoulda.

Comenzar un proyecto desde 0 con Ruby 1.9.2, Rails 3.1 y Mongodb

27 de agosto de 2011

En lo último que ando metido en mis ratos libres es en aprender un poco sobre otros tipos de base de datos. Usualmente suelo trabajar con MySQL pero tenía ganas de probar una base de datos documental, en este caso, MondoDB.

Para trastear MongoDB he empezado un proyectillo simple donde aplicar todo lo que vaya aprendiendo. El proyecto será en Rails 3.1 y utilizaré Mongoid, un ODM (Object-Document-Mapper) muy amigable para los que ya estamos acostumbrados a ActiveRecord. En este primer post os voy a contar los pasos que he seguido para poner en pie el entorno de trabajo:

Antes que nada, tendremos en cuenta que vamos a utilizar Ruby 1.9.2. Para trabajar con diferentes entornos de desarrollo Ruby utilizo RVM. Una vez tengas instalado Ruby 1.9.2, cambia a ese entorno de desarrollo con rvm use 1.9.2.

Ahora a instalar MongoDB:

brew update
brew install mongodb

Con la primera línea actualizamos las formulas de homebrew, el instalador de paquetes con el que trabajo. Si no lo conoces, dale una oportunidad, merece la pena. Una vez instalado mongodb, sigue los pasos que te vayan indicando y, para comprobar que todo ha ido correctamente, prueba a ejecutar mongod y acceder a http://localhost:28017.

Lo siguiente es empezar el proyecto de rails. Lo importante es evitar que utilice ActiveRecord:

rails new myproject -O

Esto basicamente crea tu proyecto Rails con todo lo necesario para trabajar con MongoDB. Puedes ver qué hace realmente viendo la documentación de MongoDB, donde explican cómo trabajar con Rails.

Ahora vamos a hacer un alto en el camino. Es una buena práctica con rvm definir un gemset por proyecto, para evitar posibles conflictos. Lo explica muy bien Javi Baena en su blog. Accedemos al directorio de nuestro proyecto /myproject y creamos un fichero .rvmrc que incluya lo siguiente:

rvm --create use 1.9.2@myproject

Sal del directorio del proyecto y vuelve a entrar. Si no había ningún gemset con el nombre myproject lo creará y cambiará a él de manera automática. Como ves, esto es de mucha ayuda cuando tienes varios proyectos en diferentes entornos de desarrollo, no tienes que estar pensando qué entorno utiliza cada uno y demás. Muy útil.

Bien, ahora vamos con el último paso, Mongoid. Simplemente incluye en tu Gemfile:

gem "mongoid", "~> 2.1"
gem "bson_ext", "~> 1.3"

Y para terminar, en consola:

bundle install
rails g mongoid:config

Con la última línea creamos el fichero de configuración que necesita Mongoid (config/mongoid.yml). En su página oficial puedes las diferentes opciones que se pueden incluir en este fichero.

Como primera toma de contacto no está nada mal, ahora toca pelearse con MongoDB. Te recomendaría ver el railscast de Ryan Bates sobre Mongoid, donde te cuenta lo mismo que yo y mucho más.

Actualización: Si os encontráis en el momento de plantear cómo va a ser el esquema de la base de datos, os aconsejo echarle un vistazo a esta presentación de Kyle Banker. Intentaré plasmar en un post lo que saque en claro después de verla.

Metaprogramación: métodos y delegates

18 de agosto de 2011

En el proyecto que estoy trabajando ahora mismo necesitamos diferenciar en vista ciertos elementos en función del tipo al que pertenecen. Sin embargo, el tipo no está asociado directamente a ellos. Para entendernos:

class Kind < ActiveRecord::Base
  has_many :items
end

class Item < ActiveRecord::Base
  belongs_to :kind
end

class ItemData < ActiveRecord::Base
  belongs_to :item
end

Un tipo consta de nombre y label. Para saber si un Item es de un tipo concreto podemos solucionarlo facilmente con un poco de metaprogramación básica:

if ActiveRecord::Base.connection.tables.include?('kinds')
  Kind.all.map(&:label).each do |label|
    define_method "#{label}?" do
      self.kind.label == label
    end
  end
end

Ahora tenemos métodos como por ejemplo item.a?, item.b?, etc… Sin embargo, por manos del diablo, en vista no trabajamos directamente con un objeto de tipo Item sino con objetos de tipo ItemData. Si todo estuviera cerradito podríamos hacer cosas como:

class ItemData < ActiveRecord::Base
  belongs_to :item

  delegate :a?, :to => :item
end

Pero no es nuestro caso, no está cerrado. ¿Cómo lo hacemos? Le echamos un ojo a cómo está hecho el método delegate y tras poner aquí y quitar allá, nos queda algo como esto:

class ItemData < ActiveRecord::Base
  belongs_to :item
  if ActiveRecord::Base.connection.tables.include?('kinds')
    Kind.all.map(&:label).each do |label|
      method = label + '?'
      module_eval(<<-EOS, "(__DELEGATION__)", 1)
         def #{method}
           notice.__send__(#{method.inspect})
         end
       EOS
    end
  end
end

Y ya está. Ahora podemos hacer cosas como @item_data.a? y si el día de mañana se añaden tipos, tendremos su método delegate correspondiente.

Actualización: incluyo un condicional para saber si está disponible o no la tabla Kind, ya que de otro modo, rake ni siquiera nos deja correr migraciones para que exista.

Dragonfly y procesado de imágenes: problema con el uid

26 de julio de 2011

Desde hace algún tiempo venimos trabajando en Flowers in Space en un nuevo producto propio que queremos lanzar a Internet. En él, para el tratamiento de imágenes, por diversos motivos que no vienen a cuento, nos decidimos por utilizar Dragonfly, un framework Rack para el tratamiento de imágenes.

Dragonfly es una maravilla. Nos abstraemos del post procesado de imágenes y cuando necesitamos que cierta imagen esté en un determinado tamaño basta hacer en vista algo tan simple como por ejemplo @album.cover_image.thumb(’400×200#’) y él nos sirve la imagen, la guarda donde le hayamos indicado, y nos olvidamos.

Sin embargo, en algunos casos concretos sabemos que no vamos a necesitar más de un tamaño de imagen. Para esos determinados podemos seguir utilizando Dragonfly y post procesar la imagen antes de guardarla. Mi problema era que estaba intentando hacerlo mediante un before_save de toda la vida y el uid de mi imagen no se generaba. Consecuencia: que en realidad no tenía imagen.

He aquí la solución. Imaginemos el caso anterior, un album que tiene una portada (el ejemplo de la documentación de Dragonfly, vaya). Pero sabemos de antemano que cuando mostremos nuestra portada en la web tendrá un tamaño de 50×50 (un poné). Pues al definir el image_accessor en nuestro modelo podemos hacer lo siguiente:

image_accessor :cover_image do
    after_assign{ |img| img.process!(:thumb, '50x50') }
end

Podéis encontrar más ejemplos y otros callbacks en la documentación de Dragonfly.

Error ActiveRecord::ReadOnlyRecord al actualizar atributos

9 de marzo de 2011

Sigo con mi proyecto final de carrera y poco a poco vamos aprendiendo cosillas nuevas de Rails. Al mismo tiempo que voy picando código en el controlador, modelo y vistas voy testeando, dos frentes de aprendizaje pero que recomiendo a todos los que comiencen en Rails. Aunque al principio avanzas de forma más lenta y te das (como estoy haciendo yo) de bruces contra un muro una y otra vez, a la larga aprendes mucho tanto de tus múltiples errores como de tus aciertos.

Una vez tenía hecho el test de un update supuestamente correcto obtuve un error al actualizar los atributos de un objeto. En primera instancia pensé que había olvidado en el modelo alguna cosa, pero también estaba correcto. Tras una búsqueda en Google encontré la solución.

Extrayendo la explicación del artículo

Introduce read-only records. If you call object.readonly! then it will mark the object as read-only and raise ReadOnlyRecord if you call object.save. object.readonly? reports whether the object is read-only. Passing :readonly => true to any finder method will mark returned records as read-only. The :joins option now implies :readonly, so if you use this option, saving the same record will now fail. Use find_by_sql to work around.

O lo que es lo mismo, si tu objeto lo obtienes tirando de relaciones, por ejemplo, un has_many :through, por debajo está haciendo un INNER JOIN con el modelo intermedio, y ahi está el problema, que el objeto pasa a ser de solo lectura. Las soluciones podrían ser dos:

  1. Obligar al objeto a que no sea solo lectura indicando como parámetro del find :readonly => false
  2. Utilizar include en lugar de join, si es posible

Hasta aquí lo que venías buscando. Pero si quieres ver un ejercicio práctico sobre relaciones y cómo evitar el ActiveRecord::ReadOnlyRecord en él sigue leyendo.

Ejemplo práctico

Imaginemos que estamos en el WoW (oh dios, no puedo creer que esté escribiendo esto). Tenemos hermandades (guild) y jugadores (users). Las hermandades pueden tener varios jugadores, de los cuales varios pueden ser Maestre, algo así como los “admin” de la hermandad, y puede haber más de un Maestre por hermandad. Además los jugadores pueden estar en varias hermandades.

Seguro que se puede hacer de varias formas, pero nosotros vamos a hacerlo mediante un has_many :through, utilizando como modelo intermedio membership, donde tenemos un campo owner que nos indica si es o no maestre de la hermandad.

Empezamos con los modelos:

guild.rb

  has_many :memberships
  has_many :users, :through => :memberships, :uniq => true
  has_many :owners, :through => :memberships, :source => :user, :conditions => { "memberships.owner" => true }

  validates_presence_of :name

De esta manera si tenemos una hermandad en @guild podemos saber quienes son todos sus miembros haciendo @guild.users y sus maestres @guild.owners.

IMPORTANTE: Este ejemplo no está completo. Es decir, si hacemos @guild.owners << User.find(5) (por ejemplo) no crea adecuadamente el membership, ya que faltaría marcar el campo owner del membership creado a true. Eso lo dejo para otro post y no seguir complicando las cosas.

user.rb

  has_many :memberships
  has_many :guilds, :through => :memberships
  has_many :owned_guilds, :through => :memberships, :source => :owner, :conditions => { "memberships.owner" => true }

De esta manera podemos saber qué hermandades lidera un jugador haciendo @user.owned_guilds y a cuales pertenece (sea maestre o no) haciendo @user.guilds.

De nuevo, importante: Este ejemplo no está completo. Es decir, si hacemos @user.owned_guilds << Guild.find(7) (por ejemplo) no crea adecuadamente el membership, ya que faltaría marcar el campo owner del membership creado a true.

ownership.rb

belongs_to :user
belongs_to :owner, :class_name => "User"
belongs_to :guild

Hasta aquí el ejercicio práctico de relaciones, ahora vamos al lío. Imaginemos que queremos actualizar el nombre de nuestra hermandad. Para hacer esto debes ser maestre de la hermandad. Si tenemos en cuenta que no hemos metido aquí un sistema de permisos (rollo CanCan o similares) podemos solucionar esto de dos formas:

Buscar y modificar si es posible

guilds_controller.rb

def edit
  @guild = Guild.find(params[:id])
end

def update
  @guild = Guild.find(params[:id])
  if @guild.update_attributes(params[:guild])
    redirect_to guild_path(@guild), :success => "Guild actualizada"
  else
    render :edit
  end
end

Algo de refactoring básico:

guilds_controller.rb


before_filter :get_guild

def edit
end

def update
  if @guild.update_attributes(params[:guild])
    redirect_to guild_path(@guild), :success => "Guild actualizada"
  else
    render :edit
  end
end

protected

def get_guild
  @guild = Guild.find(params[:id])
end

Además vamos a tener aquello de que actualizar el nombre de la hermandad solo puede hacerlo el maestre.

guilds_controller.rb


before_filter :get_guild
before_filter :check_ownerhip

def edit
end

def update
  if @guild.update_attributes(params[:guild])
    redirect_to guild_path(@guild), :success => "Guild actualizada"
  else
    flash[:error] = "No ha sido posible actualizar los datos de la guild"
    render :edit
  end
end

protected

def get_guild
  @guild = Guild.find(params[:id])
end

def check_ownership
   redirect_to user_path(current_user), :error => "No tienes permisos" unless current_user.owned_guilds.include?(@guild)
end

En este primer ejemplo, no obtenemos un ActiveRecord::ReadOnlyRecord ya que obtenemos la @guild directamente (sin el uso del join de la has_many :through) y el objeto @guild no es de solo lectura. La consulta que se hace a base de datos sería:

SELECT `guilds`.* FROM `guilds` WHERE `guilds`.`id` = 1 LIMIT 1
Tirar de relaciones y 404

Esta es la segunda forma, donde no vamos a utilizar un before_filter para comprobar si el jugador es maestre de la hermandad. En su lugar vamos a aprovecharnos de que al buscar entre las owned_guilds de un jugador una de la que no sea maestre en Rails obtenemos un ActiveRecord::RecordNotFound, o lo que es lo mismo, un 404.

guilds_controller.rb

before_filter :get_guild

protected

def get_guild
  @guild = current_user.owned_guilds.find(params[:id])
end

El resto de acciones del controlador permanece intacto (exceptuando como dije, el before_filter :check_ownership que no estaría en este segundo ejemplo).

Y ahora… por fin, ¿y el ActiveRecord::ReadOnlyRecord donde anda en este segundo ejemplo? Pues al hacer @guild = current_user.owned_guilds.find(params[:id]), @guild es de solo lectura, al haber tirado de relaciones.

Suponiendo que el User con id igual a 1 quiere actualizar la Guild con id igual a 1. Lo primera lanzaría la siguiente consulta:

SELECT `guilds`.* FROM `guilds` INNER JOIN `ownerships` ON `guilds`.id = `ownerships`.guild_id WHERE `guilds`.`id` = 1 AND ((`ownerships`.user_id = 1)) LIMIT 1

Ahí podéis ver el JOIN al que antes hacía mención. Habiendo un JOIN de por medio, el objeto es de solo lectura. Y no podremos actualizarlo.

Tío, termina ya, so pesao

Pues la solución para mantener el segundo ejemplo tal cual y poder actualizar es tan simple como indicar que no es de solo lectura. ¿Cómo?

before_filter :get_guild

protected

def get_guild
  @guild = current_user.owned_guilds.find(params[:id], :readonly => false)
end

Vaya artículo largo. No tengo remedio. Al menos espero que le sirva a alguien. Y como siempre, si alguien tiene algo que aportar o corregir, dispone de los comentarios para ello, yo lo agradezco un montón.

has_many con múltiples foreign_keys

4 de marzo de 2011

Estos días estoy desconectado del mundo. No tengo tiempo para nada entre el proyecto final de carrera y el WoW al que José Galisteo aka Ceritium me enganchó muy gentilmente. Pero como estoy aprendiendo cositas de Rails, donde estoy todavía muy verde, y son sencillas, pues quería compartirlas.

Tenemos un modelo, por ejemplo, Person. Y tenemos otro Dish. Los platos (dishes) están relacionados de diferentes formas con las personas: alguien los cocina, alguien se los come, alguien los limpia (un poné, como se dice en mi tierra). Un plato tiene un cocinero, un comensal, un limpiador, pero todos son Personas (nen). Y una persona puede tener varios platos cocinados, varios platos degustados y haber limpiado varios platos. Veamos como solucionarlo ahora que está más claro:

User.rb

has_many :cooked_dishes, :class_name => "Dish", :foreign_key => "cooker_id"
has_many :tasted_dishes, :class_name => "Dish", :foreign_key => "taster_id"
has_many :cleaned_dishes, :class_name => "Dish", :foreign_key => "cleaner_id"

Dish.rb

belongs_to :cooker, :class_name => "User"
belongs_to :taster, :class_name => "User"
belongs_to :cleaner, :class_name => "User"

De esta manera, si en @user tenemos a un usuario, podemos hacer @user.cooked_dishes, @user.tasted_dishes o @user.cleaned_dishes. Y del mismo modo si tenemos un plato podríamos saber quién lo cocinó, quién lo degustó y quien lo limpió: @dish.cooker, @dish.taster, @dish.cleaner.

Crear un blog en Heroku con Toto: alternativa a WordPress

2 de diciembre de 2010

Creo que este es uno de los pasos que me falta por dar pero no me decido a hacerlo, al menos de golpe y porrazo. Jose, el evangelizador, me puso sobre la pista de Toto, una plataforma para la creación de blogs.

Y diréis que tengo más cara que espalda, hacer un post recomendando que os paséis a Toto y estar usando yo un WordPress, que debo predicar con el ejemplo y todo eso. Os doy la razón, podéis fustigarme, soy un ser vil. Pero a mi favor diré que estamos trabajando en ello :P

Total, al grano que siempre me enrollo. ¿Qué porras es Toto? Alexis Sellier, el creador de Less y de Toto os lo cuenta:

toto is a git-powered, minimalist blog engine for the hackers of Oz. The engine weighs around 300 sloc at its worse. There is no toto client, at least for now; everything goes through git.

Dejando de un lado posibles modificaciones de Toto, gracias a él podemos crear un blog en 15 segundos, listo para usar. No almacena los post en una base de datos, sino en archivos .txt que creamos en local y que luego, mediante git, subimos a un repositorio. Y al poder almacenarlo facilmente en Heroku, os invito al menos a probar, que no se pierde nada.

Prerequisitos para cambiarte a Toto: que estés cansado de las actualizaciones de WordPress, que uses git, que abraces el cambio :)

¿Os mola? Pues al lío:

Crear una cuenta en Heroku

Antes que nada, para poder alojar nuestro blog en Heroku tenemos que darnos de alta. Nos enviarán un email de confirmación al correo con el que nos registremos.

Instalar la gema Heroku y añadir nuestra clave pública

Instalación:

$ sudo gem install heroku

Añadir clave pública:

$ heroku keys:add

Y para terminar escribimos nuestro email y contraseña.

Creamos nuestro blog

$ git clone git://github.com/cloudhead/dorothy.git myblog
$ cd myblog
$ heroku create myblog
$ git push heroku master

¿Ya? Sí, ya. Antes del siguiente paso te recomiendo que edites (o crees) un fichero .Gemfile y añadas la versión 0.4.6 de la gema de toto, la última versión, la 0.4.7 peta, no sé muy bien todavía por qué.

Añadiremos el servidor heroku.com a nuestra lista de servidores seguros y de una tacada subirá nuestro blog al repo e instalará las gemas que necesite. ¡Yasta! Blog ready to go!

Viendo la parrafada que me he marcado, debo replantearme escribir post más cortos, in Gali Mode.

PD: Desde la web de Toto podéis obtener más información sobre él

Optimiza imágenes para web con ImageOptim (Mac)

1 de diciembre de 2010

Aquellos que utilicéis Photoshop o cualquier otra aplicación para diseñar (por cierto, estoy inmerso en el cambio a Pixelmator, ya os contaré qué tal) seguramente os molará ImageOptim, una pequeña pero gran aplicación para Mac para la optimización de imágenes para Internet.

La aplicación no hace más que facilitarnos la vida y proporcionarnos una interfaz gráfica para el uso de diferentes herramientas de optimización que ya trae de serie.

AdvPNG from AdvanceCOMP, OptiPNG, PngCrush, JpegOptim, jpegtran from libjpeg, Gifsicle and optionally PNGOUT.

Como bien indica la web de la aplicación, por el tipo de licencia de PNGOUT no puede venir incluido “de serie”, así que tenemos que instalarlo aparte. Es fácil:

  1. Lo descargáis desde su web: http://www.jonof.id.au/index.php?p=pngout
  2. Descomprimimos y (si lo habéis descargado en la carpeta Downloads), abrimos el terminal y hacemos:
    sudo mv /Downloads/pngout-darwin /usr/bin/pngout

Acto seguido abrimos ImageOptim y añadimos el path correspondiente (/usr/bin/pngout), nada más. A disfrutar de una herramienta muy útil para el día a día de un maquetador maquero.