Sunday, March 23, 2014

Handy enhancement for ruby String: Defaults to default text

In my code , I repeat a lot this pattern: if the string is blank / empty, return a default text, otherwise return the original text.

final_text = text.blank? ? "default text" : text content_tag :h1, text.blank? ? "default text" : text, :class => 'mega'


I discovered this little gem in Stackoverflow (I lost the original source), you only need to reopen the String class, in an intializer, or decorator.

class String

  def defaults_to(what)
    self.strip!
    self.blank? ? what : self
  end


end

and, now you can use it in a readable form

text.defaults_to("default text")


Saturday, March 1, 2014

Concerns in Rails (or how to reuse code)

I have been doing some experiments with rails Concerns . While they have been widely publicited for Rails 4, you can use them in Rails 3 as well.

They are like the ruby standard include / extend pattern, but taking care of dependencies. In any case it would be easy to go back to the 'raw' include / exclude game.

This is an example of how to use them.

 require 'active_support/concern'

module Concerns

  module DummyConstants

  end

  module Dummy
    extend ActiveSupport::Concern

    #it seems that things declared here are shared between all inclusions (saving memeory)
    SEXY_REGEXP = /SEXY_REGEXP/i
    MEM_HOGGING = Array.new(1024 * 1024)
    MEMBERSHIP_STATUSES = %w(accepted invited requested rejected_by_group rejected_group)

    included do
      #The included block will be triggered at inclusion time
      before_create :stuff_on_creation

      attr_accessor :accesor_for_the_instance
      class << self
        attr_accessor :accesor_for_the_class
      end

    end

    module ClassMethods
      #Methods in ClassMethods will get added class methods
      def dummy?
        puts "\nCLASS dummy? called\n"
        true
      end

    end

    #these methods are added as instance methods

    def dummy?
      puts "\nINSTANCE dummy? called\n"
      true
    end

    def stuff_on_creation
      puts "\ncreation called\n"
    end

  end


end


Just create a file under model/concerns/dummy.rb and include it

User.send :include, Concerns::Dummy

or

class User
 include Concerns::Dummy end

Note that I repeat the namespace Concerns, but in other examples found in internet people dont. Rails 4 has included models/concerns and controllers/concerns in the autoload paths and in Rails 4 it would work without the namespace. In Rails 3, you need to add the namespace OR using some trick.

I find easier to understand if I include the 'Concern', tough.

Profit!




Saturday, February 8, 2014

Descubriendo los podcasts

Un podcast es basicamente un blog, pero de audio. Y lo realmente importante es que hay miles de podcasts disponibles, con alta calidad y tratando temáticas muy variadas. Sólo tienes que bajarte los audios (mp3) y escucharlos cuando quieras.

--

Todo se inicia con un tweet de @david_bonilla 
"Yo salgo a correr para tener una excusa para escuchar el ". Así descubro que "El amuleto de yendor" es un podcast sobre tecnología, dode 2 chavales hablan sobre cosas que me interesan.


El prime paso, fue descargarme el último programa desde ivoox : me lo escuche de una sentada pues no tiene desperdicio. ivoox está bien para descargarse programas antiguos, pero se queda corto a la hora de descubrir podcasts similares o buscar ciertas temáticas. Además yo quería que me avisaran cuando se grababan episodios nuevos (esto se llama subscripciones, y se maneja mediante rss).

--

Tras un mes de aprendizaje, así es como gestiono mis podcasts:

gPodder.net es el servicio que me permite guardar mis subscripciones . Es gratuito. Necesitas crear  una cuenta y comenzar a añadir los podcasts que te interesen.

Lo siguiente es utilizar un programa que te descarge nuevos audios (episodios) según van apareciendo. Así te ahorras mucha gestion manual.
- para ordenadores tenemos clientes oficiales de gPodder en todas las plataformas http://gpodder.org/downloads así tengo mis WIndows y Linux sincronizados.
- para android yo utilizo ListeUp (no se cual es la diferencia entre la free o la pro de 1€, pero como es un programa que utilizo mucho, acabé pagando) . Con esto cubro mi MotoG y Nexus7 . Esta aplicacion tiene 2 cosas muy buenas: recuerda el audio que estábas escuchando la última vez que apagaste y que puedes manejar los controles desde la pantalla de bloqueo. También sincroniza con gPodder.
- para el iPad utilizo la aplicacion de podcasts oficial . No me gusta mucho porque no tiene integracion con gpodder, y tengo que añadir mis subscripciones a mano. De todas formas los podcasts los suelo escuchar en el movil o en ordenador, asi que no he investigado más.
- Creo que VLC tambien puede manejar subscripciones de podcasts, pero no está integrado con gpodder.
- si no te interesa la integracion con gpodder, en android tienes PodcastAddict, que es una pasada
- si viajas mucho en coche, puedes pasar los episodios a tu reproductor de mp3 y escucharlo en el coche.

Si utilizas gpodder en varios dispositivos, recuerda gestionar que todos están subscritos a las mismas fuentes y sincronizados, desde https://gpodder.net/devices/ Asi te evitas mucho trabajo.

--

Qué escucho?

Basicamente tecnología: iCharlas, PassionGeek, Infoxicados y el amuleto de Yendor.

Aqui estan mis subscripciones: en rss , opml o directamente la página de gpodder

Suelo descubrir nuevos podcasts, navegando por las subscripciones de otros usuarios que estan suscritos a mis mismos podcasts: algunas veces te llevas una sorpresa!

Dejo un enlace con uno de los episodios que mas me gustaron: Javifrechi (de infoxicados) en iCharlas, parte1 y parte2




Wednesday, November 13, 2013

2 lines that must be on your <head>

In few words:

ensure that these lines are included in your <head>
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>

Thursday, September 5, 2013

Take a photo from Javascript

Finally it is 2013 and you can take photos from your browser without requiring flash.

The API that makes it possible is getUserMedia ( http://caniuse.com/#search=getUserMedia ) and it is available in all modern browsers (requiring some vendor-prefixes, though).

For demo: use this fiddle

The code I paste here, takes a photo, dump it to a canvas and tries to upload it to a (non-existent) server.
- The key here is that both the captured image AND the uploaded image does not need to be the same size (you usually don't want to upload very big files). That is controlled via the OUTPUT_RATIO constant: the final size is given by the output canvas.
- Neither the video or the output are required to be shown, however is good to give visual feedback to users.
- NOTE: Chrome does not allow local files to get access to getUserMedia. You can use fiddle to make the test yourself.
- At the present, the api still needs to be prefixed depending on the browsers:
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;



<!DOCTYPE html>
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
        <title>getUserApi</title>
        <meta name="description" content="">
        <meta name="viewport" content="width=device-width">
        <style type="text/css">
        video {
          background: rgba(255,255,255,0.5);
          border: 1px solid #ccc;
        }
        </style>
    </head>
    <body>
        <div id='text'>
            <p>You must grant access to the Camera first.</p>
            <p>Prompt would be shown above this lines, next to the address bar.</p>
            <button type='button' id='button'>Take photo</button>
        </div>
        <video id='video' width="640" height="480"></video>
        <canvas id='canvas' style="display:none;"></canvas>
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.js"></script>
        <script>
        (function(window) {

          var nav = window.navigator,
            doc = window.document,
            //some browsers behave differently
            is_webkit = nav.webkitGetUserMedia,
            is_mozilla = nav.mozGetUserMedia,
            showSnapshot = true,
            showVideo = true,
            OUTPUT_RATIO = 0.5, //the output is X times the captured image (ex: we upload small photos)

            source,
            video,
            canvas,
            button,
            ctx,
            localMediaStream;

          var
              initCamera = function() {
              video = document.getElementById('video'),
              canvas = document.getElementById('canvas'),
              button = document.getElementById('button'),
              ctx = canvas.getContext('2d');

              //make canvas and video the same dimensions
              canvas.width = video.width * OUTPUT_RATIO | 0;
              canvas.height = video.height * OUTPUT_RATIO | 0;

              //turn canvas to visible
              canvas.style.display = showSnapshot ? '' : 'none';
              video.style.display = showVideo ? '' : 'none';
              (button || video).addEventListener('click', takeSnapshot, false); //addEventListener: IE9+ Opera7+ Safari, FFox, Chrome

              // if (is_webkit){
              //   nav.getUserMedia('video', onSuccess, onError);
              // }else{
              nav.getUserMedia({
                video: true
              }, onSuccess, onError);
              // }

            },
            onError = function(e) {
              alert('Camera permission rejected!', e);
            },
            onSuccess = function(stream) {
                if (is_mozilla) {
                  source = window.URL.createObjectURL(stream);
                } else if (is_webkit) {
                  source = window.webkitURL.createObjectURL(stream);
                } else {
                  source = stream;
                }

                video.src = source;
                video.play();
                localMediaStream = stream;
            }, stopCamera = function(){
              localMediaStream.stop();
              video.style.display =  canvas.style.display = 'none';
              localMediaStream = canvas = ctx = null;
              button
            }, takeSnapshot = function() {
            if (localMediaStream) {
              ctx.drawImage(video, 0, 0, (video.width * OUTPUT_RATIO) | 0, (video.height * OUTPUT_RATIO) | 0);
              uploadSnapshot();
            }
          }, uploadSnapshot = function(){
              var dataUrl;

            try {
                dataUrl = canvas.toDataURL('image/jpeg', 1).split(',')[1];
            } catch(e) {
                dataUrl = canvas.toDataURL().split(',')[1];
            }
            $.ajax({
                url: "localhost:3000/uploadTest",
                type: "POST",
                data: {imagedata : dataUrl}, //in the server file.write(Base64.decode64(imagedata)) , https://gist.github.com/pierrevalade/397615
                contentType: "application/json; charset=utf-8",
                dataType: "json",
                success: function () {
                    alert('Image Uploaded!!');
                },
                error: function () {
                    alert("There was some error while uploading Image");
                }
            });
          };


            //some browsers use prefixes
          nav.getUserMedia = nav.getUserMedia || nav.webkitGetUserMedia || nav.mozGetUserMedia || nav.msGetUserMedia;

          if (nav.getUserMedia) {
            initCamera();
          } else {
            alert("Your browser does not support getUserMedia()")
          }
        }(this));
        </script>
    </body>
</html> 

Bonus

For large images you can save some bytes by sending the image as a blob instead of a base64 encoding.

First, encode the dataUrl as a blob

function dataURItoBlob(dataURI, dataTYPE) {
  var binary = atob(dataURI), array = [];
  for(var i = 0; i < binary.length; i++) array.push(binary.charCodeAt(i));
  return new Blob([new Uint8Array(array)], {type: dataTYPE});
}

Then, you have 2 options:

- using the FormData api

function uploadWithFormData(dataUrl){
  // Get our file
  var file = dataURItoBlob(dataUrl, 'image/jpeg'),
  fd = new FormData();
  // Append our Canvas image file to the form data
  fd.append("imageNameHere", file);
  // And send it
  $.ajax({
     url: "/server",
     type: "POST",
     data: fd,
     processData: false,
     contentType: false,
  });
}

- or using the XHR

function uploadWithXHR(dataUrl) {
  var file = dataURItoBlob(dataUrl, 'image/jpeg'),
  xhr = new XMLHttpRequest();
  xhr.open('POST', '/server', true);
  //add the headers you need
  // xhr.setRequestHeader("Cache-Control", "no-cache");
  // xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
  // xhr.setRequestHeader("X-File-Name", file.name || file.fileName || 'image.jpg');
  // xhr.setRequestHeader("X-File-Size", file.size || file.fileSize);
  // xhr.setRequestHeader("X-File-Type", file.type);
  // xhr.setRequestHeader("Content-Type", options.type);
  // xhr.setRequestHeader("Accept","application/json, text/javascript, */*; q=0.01");
  xhr.send(file);
}

Friday, August 23, 2013

rubygems: uninstall some gems

I just discovored a way to uninstall only the gems that match a pattern (via https://coderwall.com/p/lpqmjq )

gem list [OPTIONAL PATTERN] --no-version | xargs gem uninstall -ax

for example

gem list hobo --no-version | xargs gem uninstall -ax

removes all 'hobo'

Successfully uninstalled hobo_jquery_ui-2.0.1
Successfully uninstalled hobo_clean_admin-2.0.1
Successfully uninstalled hobo_clean-2.0.1
Successfully uninstalled hobo_bootstrap_ui-2.0.1
Successfully uninstalled hobo_bootstrap-2.0.1
Successfully uninstalled hobo_jquery-2.0.1
Successfully uninstalled hobo_rapid-2.0.1
Removing hobo
Successfully uninstalled hobo-2.0.1
Removing hobofields
Successfully uninstalled hobo_fields-2.0.1

Sunday, August 11, 2013

JS: detect unsaved changes in a form

Here is a small jQuery plugin to detect changes on a form.

https://github.com/gsusmonzon/jquery.simple.unsaved

The tricky part is to store a hash of the serialization string instead of the full serialized form. The rest is not worth mentioning.