Responsive Design Workflow

  • Content Inventory: establish and describe the content. This gives you your raw materials
  • Content reference wireframes: establish rough responsive wireframes in HTML. Allows for really fast iterations.
  • Design in text (structured content): establishes content hierarchy and structure. Easily revisable.
  • Linear Design: Test out the plain jane structured content in HTML in the browser.
  • Breakpoint graph: display visually where the breakpoints happen
  • Design for various breakpoints: Start with the small screen first, then expand until it looks like shit.
  • TIME FOR A BREAKPOINT!
  • HTML design prototype: If w’ere not delivering designs in PS, what do we deliver? Clients wants PS because they’re used to it. Create HTML CSS, and maybe a bit of JS
  • Present prototype screenshots – It’s part of a presentation psychology – Presenting static “impressions” of the design across the different breakpoints allows you to stay ahead of your client.
  • Present prototype after revisions: Once revisions have been made, you can show the design in action
  • Document for production: Deliver a style guide along with the production code.

Stephen Hay (@stephenhay) in Responsive Design Workflow

vía Brad Frost

Sub-pixel en Internet Explorer: SASS al rescate

Uno de los problemas actuales de aplicar Responsive Design es sin duda el aburrido bug casuado por el Sub-pixel en nuestro odiado Internet Explorer.

Para usar Responsive Design versiones de IE inferiores a la 9, recuerda que puedes hacerlo usando Respond.js o css3-mediaqueries-js)

¿En qué consiste? Si visitaste el enlace anterior puedes ahorrarte la siguiente explicación. Básicamente se resumen en que este problema a la hora de maquetar se da debido al redondeo que hacen los diferentes navegadores al aplicar un porcentaje a un tamaño.

Imaginemos 3 contenedores, de 49, 50 y 51 píxeles cada uno. Dentro de cada uno vamos a crear 4 columnas (usando porcentajes, es decir, width: 25%). ¿Qué tamaño en píxeles tendría cada columna?

Caso 1: 49px x 0.25 = 12.25
Caso 2: 50px x 0.25 = 12.5
Caso 3: 50px x 0.25 = 12.75

Pues aquí está el problema: Webkit (Chrome, Safari, etc…) y Opera redondean hacia abajo, dejando espacio de sobra. FireFox, IE8, y IE9, modifican el tamaño de las cajas (siendo mayor al esperado) en caso de que sobre espacio por cubrir. Y el inservible IE7 y el menos inservible IE8 para decimales superiores o iguales a .5, redondean hacia arriba, o lo que es lo mismo, para ellos, las columnas serían de 12px, 13px y 13px, haciendo saltar inevitablemente la última columna, maldición nuestra incluída.

Entonces, como ya habrás pensado, la solución es que en nuestra hoja de estilos específicos para IE, le damos menos porcentaje a esas columnas y ya. Y sí, ¿pero cuánto menos? ¿A ojo? Puede servirnos, pero la maquetación no será fiel del todo al diseño original. La solución es:

(0.5 / tamaño_del_contenedor) * 100 = 50 / tamaño_de_contenedor

Y aquí es donde llega SASS y te hace la vida fácil. Si alguien encuentra una solución mejor, el gist siguiente es público y puede ser editado a gusto del consumidor :)

Este post no habría sido posible sin el artículo de Tyler Tate.

Sublime Text 2: el editor de texto definitivo

Le prometí fidelidad a Textmate, pero ha sido conocer y probar SublimeText y me ha faltado tiempo para ponerle los cuernos pero bien puestos.

Imagino que muchos ya habréis escuchado hablar sobre las bonanzas de Sublime Text 2, y lo cierto es que no son pocas. Detallemos algunas de ellas, solo algunas:

  1. Textmate mode: Bundles, Macros, Snippets, … ¿te suenan? Pues no los echarás de menos
  2. Autocompletado: sin tener que pulsar la tecla ESC cada vez, un autocompletado de verdad, que ayuda lo que no está en los escritos.
  3. Increíble rapidez de respuesta: es rápido, pero que muy rápido. Textmate me renqueaba bastante últimamente, en proyectos tanto grandes como pequeños.
  4. Selección múltiple y Multiedición: esto ya lo tenía Textmate pero no como SublimeText, que nos permite hacerlo en cualquier parte del documento, no necesariamente en línea como ocurría con Textmate.
  5. Auto save: LA VIDA.
  6. Modo pantalla completa y modo antidistracción: lo segundo, sobre todo, una de las cosas que más me han gustado. Buscaba algo así en un editor de texto desde hace mucho tiempo.
  7. Búsqueda: y es que la del Textmate muere con proyectos grandes, y mi Mac con ella, literalmente. La búsqueda de Sublime Text es, parte de funcionar infinitamente mejor, un auténtico gustazo.
  8. Guías: semi-implementado en Textmate pero mucho mejor resuelto en Sublime Text, me ayuda infinito a la hora de desarrollar.

Si te decides a probarlo te aconsejo también:

  1. Package Manager: instalación de paquetes/extensiones para Sublime Text.
  2. Soda Theme: aunque Sublime Text trae una buena cantidad de themes, este mola mucho comparado con los que trae por defecto.
  3. DetectSyntax: Si algo me volvía loco al principio de SBT era que la sintaxis no cambiaba del todo correctamente (marcaba Ruby cuando era Rails los snippets que necesitaba). Con esta extensión, que puedes instalar desde el Package Control, está todo solucionado.

Si voy encontrando más recursos sobre Sublime Text 2 los iré dejando por aquí.

‘border-radius’ mixins de andar por casa

Ú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!

validates uniqueness y Shoulda

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

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

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

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

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.