José Galisteo Ruiz

¯\_(ツ)_/¯

activerecord-preload_query

| Comments

Este post va sobre mi gema activerecord-preload_query y otras maneras de hacer lo mismo.

Activerecord nos provee los métodos preload, eager_load e includes para precargar relaciones y evitar hacer N+1 queries a la base de datos cuando queremos acceder a esta. También nos provee de counter_cache, el cual está muy bien, pero está muy imitado.

Pero que pasa si tenemos algunos cálculos mas complicados? Imaginemos que tenemos el siguiente código:

1
2
3
4
5
6
7
8
9
10
11
class Category < ActiveRecord::Base

  has_many :products

end

class Product < ActiveRecord::Base

  belongs_to :category, counter_cache: true

end

Gracias a counter_cache podemos hacer algo así:

1
2
3
Category.all.each do |category|
  p category.products_count
end

Y solo se hará una sola llamada.

Pero y si queremos saber por alguna razón el stock total de cada categoría? Imaginemos que Product tiene el atributo stock.

En la categoría podremos añadir un método products_stock como este:

1
2
3
def products_stock
  products.sum(:stock)
end

De esta manera, tendremos de nuevo el problema del N+1.

Solución con joins

Para evitarlo podríamos optar por algo como:

1
  @categories = Category.select('*, sum(products.stock) as products_stock').joins(:products)

Esto soluciona el problema del N+1, pero estamos sobrescribiendo el select, entonces a @categories no podremos enviarle muchos métodos de activerecord porque no estarán disponibles los campos que el espera.

Un ejemplo sencillo es con @categories.count y si en vez de ser Category lo que precede a select fuese un “scope” mucho más complicado como podría ser en el caso del scope producido por una búsqueda compleja seguramente acabaríamos rompiendo la query.

Además que incluso con este sencillo caso, @categories.count acaba dando un error al no estar presentes los campos que se esperaban, porque hemos sobreescrito el select.

Otro problema a tener en cuenta, pero no el principal es hacer joins de tablas muy grandes.

Consulta a parte

Para evitar los problemas de sobreescribir el select, lo mejor es hacer la consulta a parte, por ejemplo:

1
2
3
4
5
6
7
categories = Category.limit(10).includes(:foo, :bar).where('whatever = foo')

products_stock = Category.where(id: categories.map(&:id)).select('*, sum(products.stock) as products_stock').joins(:products).map{|x| [x.id, x.products_stock]}.to_h

categories.map do |category|
  category.define_singleton_method :products_stock, lambda { products_stock[category.id] }
end

Con preload_query

Con preload_query es básicamente igual, pero interceptando el método de ActiveRecord que se encarga de hacer la consulta, así no llamaremos a la base de datos hasta el momento en el que sea necesario.

Ejemplo de uso básico:

1
2
3
4
5
6
7
8
9
10
11
12
class Category < ActiveRecord::Base

  has_many :products

  class << self
    def sum_stock(ids)
      where(id: ids).group("categories.id").joins(:products).select("categories.id, sum(products.stock) AS sum_stock")
    end
  end
end

Category.preload_query(:sum_stock).map(&:sum_stock)

A la clase Category puede que nos interese implementarle el método sum_stock para cuando no se hace un preload.

Por ejemplo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Category < ActiveRecord::Base

  has_many :products

  class << self
    def preloaded_sum_stock(ids)
      where(id: ids).group("categories.id").joins(:products).select("categories.id, sum(products.stock) AS preloaded_sum_stock")
    end
  end

  def sum_stock
    try(:preloaded_sum_stock) || products.sum(:stock)
  end
end

Category.preload_query(:sum_stock).map(&:sum_stock)
Category.map(&:sum_stock)
Category.last.sum_stock

Me gustaría pensar un DSL para implementar este último ejemplo de forma mas sencilla, pero de momento no se me ocurre :P

Gemas similares

Estas gemas hace algo parecido, pero solo se encargan de los counts, quizás para ti sea suficiente.

Heroku y Memcached local

| Comments

La única opción “oficial” para usar memcaché en Heroku es usar el addon Memcachier o algún otro servicio externo, lo cual está genial pero a veces no es lo mejor, todo dependerá de nuestra aplicación y la estrategia de caching que usemos.

En mi caso quiero usar memcached para cachear partes de vistas basándome en el updated_at de los objetos, una estrategia super sencilla. Además en los listados puedo hacer desde 10 hasta cientos de peticiones a Memcached pues son caches de fragmentos anidados.

El problema aquí está en la latencia, por poca que sea, multiplica eso por 100 o 500 peticiones. Lo ideal para mi caso sería tener Memcached corriendo en la misma maquina que mi aplicación.

Como ejecutar Memcached local en Heroku dynos

Añade https://github.com/Americastestkitchen/heroku-buildpack-apt a tu app. Puedes hacerlo desde consola o la interfaz de Heroku.

1
$ heroku buildpacks:add https://github.com/Americastestkitchen/heroku-buildpack-apt

Crea un fichero Aptfile con los paquetes a instalar, en este caso solo Memcached.

1
2
3
# Aptfile

memcached

Cambia tu Procfile para que ejecute memcached y tu aplicación, en mi caso hice:

1
2
3
4
# Procfile
web: memcached -m 64 & bundle exec puma -C config/puma.rb

# -m es la cantidad de ram

Con esto cuando vuelvas a desplegar tendrás disponible memcached en cada dyno dedicado a web. Los parametros son los que vienen por defecto 127.0.0.1:11211.

Cosas a tener en cuenta:

La cantitdad de RAM disponible

Puedes usar log-runtime-metrics para monitorizarlo o las estadisticas que ofrece Heroku en el dashboard, pero estas solo están disponibles para aplicaciones que usan dynos a partir de standar-1x. Necesitas saber cuánta RAM consume tu app normalmente, cosa que puedes ver con NewRelic (gratis) y decidir que cantidad le dedicas a Memcached.

Una instancia de Memcached por dyno

Esto es lo que buscábamos al fin y al cabo, si quieres memcached para tus workers tendrás que hacer lo mismo que para web. Si tienes varios dynos corriendo cada uno tendrá su propia caché, por lo que tu aplicación debe estar preparada para ello. Si necesitas una sola instancía de Memcached entonces lo mejor es que uses un servicio externo.

Volatilidad

Cada vez que despliegues y posiblemente cada vez que hagas un restart perderás toda la caché. Por esta razón no es buena idea hacer lo mismo para Redis, a menos que uses Redis única y exclusivamente para caché.

Las pruebas

Por último una muestra de mis logs antes y después.

ActiveRecord, find_each y uncached

| Comments

Si tenemos que recorrer coleciones muy grandes de ActiveRecord deberíamos usar find_each o find_in_batches, hasta aquí ninguna novedad, pero el otro día me di cuenta de que mis workers de sidekiq consumían muchísima memoria, el problema es que AR cache los objetos que devuelve el find_each.

La solución para evitar esto pasa por usar el método uncached.

Ejemplo:

1
2
3
4
5
ActiveRecord::Base.uncached do
  scope.find_each(batch_size: 1000) do |tweet|
    gz.write tweet.to_csv
  end
end

DIY plotter CNC

| Comments

Como proyecto para el puente de semana santa empecé un plotter CNC de bajo coste, me basé en este instructable.

En el instructable ya viene casi todo detallado así que solo me queda daros algunos consejos y agrupar en un repo todo el código.

El mini plotter usa dos lectores de CD o DVD, si no tenéis lo mejor es que los compréis de segunda mano, yo he conseguido dos por menos de 5 euros.

En el tutorial explica como controlar los motores con arduino usando los chips L293D IC, pero para ahorrame cableado decidí usar una Motor shield que ya la tenía por casa, esta es compatible con la Adafruit Motor Shield library.

Si usas la librería Adafruit Motor Shield tendrás que cambiar un poco el código que cargues a tu Arduino.

Este lo saqué de un comentario del propio tutorial, aquí os lo dejo un poco más bonito.

Para conectar los cables a los motores, creo que lo mejor es soldar directamente los cables en los 4 puntos que hay expuestos, no trate de quitar el plastiquito, así solo conseguiras cargartelo.

Y este es el plotter funcionando, aún sin el servo para levantar el boligrafo.

Postgres after upgrade to Yosemite

| Comments

I recently upgraded to Yosemite after skip the reminder each day for the last year…

Everything was fine, Mysql working, brew packages are OK, rbenv, ruby and Rails stuff looks fine except Postgres.

Checking the logs I saw:

1
FATAL:  could not open directory "pg_tblspc": No such file or directory

The solution was easy:

1
2
cd /usr/local/var/postgres
mkdir pg_tblspc pg_twophase pg_stat_tmp

Instalar QT4 en Ubuntu 14.04

| Comments

Ubuntu 14.04 viene con QT5 por defecto lo cual está bien, pero a veces podemos necesitar usar QT4 para trabajar con versiones de librerías o gemas antiguas, como por ejemplo capybara-webkit 0.12.1. En algún momento habrá que actualizarlo…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ sudo apt-get install -y build-essential
$ sudo apt-get build-dep qt4-qmake

$ wget http://download.qt.io/archive/qt/4.8/4.8.6/qt-everywhere-opensource-src-4.8.6.tar.gz
$ tar -xzvf qt-everywhere-opensource-src-4.8.6.tar.gz
$ cd qt-everywhere-opensource-src-4.8.6

# En el configure nos pedirá elegir entre la version open source o privada,
# yo elegí la open source.
$ ./configure

# Esto llevará un buen rato
$ make

$ sudo make install

Con esto ya tenemos QT4, pero al hacer por ejemplo gem install capybara-webkit -v 0.12.1 dirá que no encuentra qmake para QT4.

Yo lo que hice fue:

1
2
3
4
# Guardo una copia de qmake

$ sudo mv /usr/bin/qmake /usr/bin/qmake.old
$ sudo /usr/local/Trolltech/Qt-4.8.6 /usr/bin/qmake

Y con esto ya instala sin problemas.

Tengo pendiente para el lunes probar qtchooser, el cual parece mejor opción.

Arduino modulo RF 433Mhz

| Comments

Para el proyecto de monitorización de mi pequeño huerto urbano necesito comunicar dos módulos Arduino de forma inalámbrica, para eso tenemos distintas opciones y dependiendo de las necesidades podremos usar un sistema u otro.

En mi caso, solo necesito enviar unos pocos datos a pocos metros, lo que viene siendo desde mi balcón donde tengo un módulo hasta el router donde tengo el otro conectado a Internet.

Para ello he usado un transmisor y un receptor de RF a 433Mhz. Lo más interesante de estos módulos es su bajo coste y lo podemos encontrar fácilmente en Amazon o DX.com por menos de 5 euros.

Para usar estos módulos podemos usar la librería VirtualWire la cual nos hará el proceso mucho más sencillo.

Modulo emisor:

Conexiones:

1
2
3
4
5
Arduino     | Emisor
----------- | ---------------
5V          | VCC
GND         | GND
Digital 12  | DATA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <VirtualWire.h>

void setup() {

  // Para el led
  pinMode(13,OUTPUT);

  // Pin de escritura
  vw_set_tx_pin(12);

  // Velocidad de transferencia en Kbps
  // debe ser la misma en el receptor
  vw_setup(2000);
}

void loop(){

  // Un mensaje cualquiera, podríamos preparar una cadena
  // concatenando distintos valores de sensores.
  String message =  "hello world!";

  // Convertimos el string a un array char.
  char buf[message.length()];
  message.toCharArray(buf, message.length() + 1);

  // Iluminamos el led para indicar que estamos transmitiendo
  digitalWrite(13, true);

  // Enviamos el mensaje
  vw_send((uint8_t *)buf, strlen(buf));
  // Se mantiene en espera hasta que todo el mensaje se ha enviado
  vw_wait_tx();

  // Apagamos el led
  digitalWrite(13, false);

  // Simplemente esperamos un segundo
  delay(1000);
}

Modulo receptor:

Conexiones:

1
2
3
4
5
Arduino     | Receptor
----------- | -----------------------------------------
5V          | VCC
GND         | GND
Digital 11  | DATA # Uno de los dos disponibles
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include <VirtualWire.h>

String data;

void setup()
{
  Serial.begin(9600);

  // Para el led
  pinMode(13, OUTPUT);

  // Pin de lectura
  vw_set_rx_pin(8);

  // Velocidad de transferencia en Kbps
  // debe ser la misma en el transmisor
  vw_setup(2000);

  // Ponemos en escucha
  vw_rx_start();
}

void loop()
{
  // Inizializamos el buffer con las constantes definidas en VirtualWire
  uint8_t buf[VW_MAX_MESSAGE_LEN];
  uint8_t buflen = VW_MAX_MESSAGE_LEN;

  if (vw_get_message(buf, &buflen)) {

    // Iluminamos el led para indicar que hay una transmisión entrante
    digitalWrite(13, true);

    // Leemos el buffer y lo volcamos en la variable data
    data = "";
    int i;
    for (i = 0; i < buflen; i++) {
      data += String(char(buf[i]));
    }

    // Lo imprimimos por el puerto serie
    Serial.println(data);

   // Y apagamos el led
   digitalWrite(13, false);
  }
}

Con todo enchufado y el código cargado en ambos módulos podremos ver los leds parpadeando y por el puerto serie del receptor los mensajes que manda el emisor.

Monitorizar huerto urbano con Arduino

| Comments

A pesar de lo pequeñito de mi balcón desde hace unos meses me decidí a sembrar algunas hortalizas y hierbas aromáticas en mi balcón. También desde hace unos meses estoy trasteando mas que nunca con Arduino y como no podía ser de otra manera me propuse monitorizar mi mini huerto con Arduino.

El proyecto se compone de dos partes:

  • Modulo auto suficiente de medición encargado de tomar los datos del huerto y que los envié al modulo receptor.

  • Modulo receptor de datos que se encarga de almacenar los datos y tratarlos.

Objetivos:

El modulo de medición debería ser energeticamente autosuficiente, es decir, alimentarse por energía solar y ser capaz de enviar datos al modulo receptor.

El modulo receptor estaría a cierta distancia y conectado a una fuente eléctrica.

Modulo de medición

La idea es que cada modulo pueda medir al menos la humedad de varias zonas, plantas, macetas… para transmitir los datos al receptor.

Componentes

Modulo receptor

Recibe las lecturas de cada transmisor y los envía por HTTP a algún servicio web o almacena en la SD.

Componentes

Básico:

Opción A:

Opción B:

Swagger Docs, Swagger UI y tests de integración

| Comments

En Apidemic estoy usando Swagger Docs para generar la documentación swagger de la API interna y Swagger UI para visualizarla.

La vista por defecto de Swagger UI la he personalizado un poco para añadirle un campo para la API key, pero si estás logado esta no es necesaria.

Todas estas características quería cubrirlas con tests de integración, pero tenía el problema de que los swagger docs se generan con una tarea rake y alguno parámetros como el base_path cambiaban en cada entorno. Además en los tests de integración el puerto cambia cada vez.

Así prepare los tests:

config/initializers/swagger_docs.rb
1
2
3
4
5
6
7
8
9
10
11
class Swagger::Docs::Config
  def self.transform_path(path, api_version)
    "#{Rails.application.config.host}/docs/#{path}"
  end
end

Swagger::Docs::Config.register_apis({
  "1.0" => {
    :api_extension_type => :json,
    :base_path => Rails.application.config.host,
# continue...
1
2
3
4
5
6
7
8
9
10
test 'foo' do
  visit(root_path)
  uri = URI(current_url)
  base_path = "http://#{uri.host}:#{uri.port}"

  Rails.application.config.stubs(:host).returns(base_path)
  Swagger::Docs::Config.registered_apis["1.0"][:base_path] = base_path
  Rake::Task["swagger:docs"].invoke
  # continue...
end

Tutum

| Comments

El otro día casi por casualidad llegué a la web de Tutum.co.

Tutum se define como “Docker Platform for Devs and Ops”, yo lo veo como un Marathon pero mucho más sencillo y sin necesidad de tener más infraestructura que los servidores sobre los que quieres correr tus dockers.

A golpe de click puedes lanzar nodos sobre Amazon Ec2, Digital Ocean, Azure o Softlayer, eliges la región, el tamaño, el numero de maquinas y las taggeas para poder decirle a los servicios que levantes que vayan a un servidor o a otro.

Lo mejor de todo es la API, gracias a la cual se me ha ocurrido montar un pequeño sistema de integración continua as a service, ya se que hay muchos, pero es un reto interesante.

Mientras sigan en beta será totalmente gratuito y los que se registren mientras tanto tendrán una cuenta gratuita para siempre.

Si no sabes que es Docker o Marathon puedes seguir estos enlaces:

De todas formas dentro de Tutum puedes encontrar mucha documentación sobre Tutum o sobre Docker en general. Mi recomendación además de que pruebes Docker es que trates de seguir algún tutorial sencillo del servicio.