<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Altoros Argentina🚀 on Medium]]></title>
        <description><![CDATA[Stories by Altoros Argentina🚀 on Medium]]></description>
        <link>https://medium.com/@Altoros.com?source=rss-c4a9b6b8bc6a------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*IkWGSs2ySBOalVbxFgduPg.jpeg</url>
            <title>Stories by Altoros Argentina🚀 on Medium</title>
            <link>https://medium.com/@Altoros.com?source=rss-c4a9b6b8bc6a------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sun, 15 Nov 2026 05:31:19 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@Altoros.com/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[Existe una gran cantidad de personas que conviven con algún tipo de discapacidad o disminución.]]></title>
            <link>https://medium.com/altoros/existe-una-gran-cantidad-de-personas-que-conviven-con-alg%C3%BAn-tipo-de-discapacidad-o-disminuci%C3%B3n-ca2e814a4f85?source=rss-c4a9b6b8bc6a------2</link>
            <guid isPermaLink="false">https://medium.com/p/ca2e814a4f85</guid>
            <dc:creator><![CDATA[Altoros Argentina]]></dc:creator>
            <pubDate>Tue, 19 Nov 2019 15:23:51 GMT</pubDate>
            <atom:updated>2019-11-20T19:09:09.046Z</atom:updated>
            <content:encoded><![CDATA[<h3>Problemas de Accesibilidad usuales en un sitio Web</h3><p>Existe una gran cantidad de personas que conviven con algún tipo de discapacidad o disminución. Para ellos, al igual que para todo el mundo, internet resulta la primer forma de acceso a la mayoría de la información, además de un medio de comunicación, una forma de pertenecer, compartir y participar.</p><p>No obstante, no es usual para quienes desarrollan la mayoría de los sitios, pensar en cómo sería visitarlos y recorrerlos utilizando dispositivos poco usuales. Qué dificultades se presentan al navegar un sitio sin poder verlo, o sin poder utilizar un puntero, incluso utilizando la voz para poder navegarlo. Dispositivos como lectores de pantalla o teclados especiales son cada vez más usuales a medida que más gente tiene acceso a la web, y muchas veces, que un desarrollador piense o no en cómo sería visitar su sitio a través de estos, es la clave para hacer su sitio disponible para todos.</p><p>En este contexto, si bien existen <a href="https://www.w3.org/WAI/">muchos y diversos aspectos</a> a tener en cuenta a la hora de construir un sitio accesible, a continuación se presentarán 5 de los problemas de accesibilidad más usuales encontrados en sitios web y las formas más sencillas de resolverlos para todo aquel que este interesado en buscar la mayor inclusión en los sitios que desarrolla.</p><h3>1. Uso incorrecto de las etiquetas html y marcas de página</h3><p>En general, los sitios utilizan las etiquetas HTML y los <em>”landmarks”</em> o puntos de referencia como herramientas para aplicar estilos, colores y diseños específicos en las secciones que referencian. Además de esta función, estas etiquetas tienen un rol preponderante a la hora de asistir a los usuarios con lectores de pantalla, para que puedan entender y navegar la estructura de cada página.</p><p>Si los Landmarks son correctamente usados, permiten reconocer fácilmente las distintas secciones de un sitio web. En general, el principal problema es la falta o la mala utilización de estas etiquetas. Las más importantes en cada página son: &lt;header&gt; , &lt;nav&gt; , &lt;main&gt; y &lt;footer&gt; . Asegurarse de tener cada una de estas “regiones” delimitadas con sus respectivas etiquetas en una página web, permite que los usuarios de lectores de pantalla puedan navegar todo el sitio de una manera más amigable y sencilla.</p><h4>Solución Recomendada</h4><p>La solución recomendada es simplemente asegurarse de usar HTML semántico para describir cada página. Agrupar cada “región” de acuerdo a su respectivo contenido, si se usa un header usar el tag &lt;header&gt;, si se usan listas u otros elementos para confeccionar un menú de navegación, asegurarse de delimitarlos con un tag &lt;nav&gt;, tratar de marcar el contenido principal de cada página usando &lt;main&gt;. Además, existen más elementos que se pueden utilizar y que aumentan la semántica del sitio, tales como &lt;aside&gt; y &lt;section&gt;.</p><h3>2. Ausencia de un link para ir hacia el contenido principal</h3><p>Se recomienda que las páginas web con más de 5 elementos que requieren focus antes de la región considerada como “principal” contengan un link que permita ir directamente hacia dicho contenido.</p><p>Los usuarios que sólo usan el teclado para navegar los sitios, utilizan como herramienta principal la tabulación o la tecla Tab. Por esto, tabear demasiado para llegar hacia el contenido principal resulta arduo y molesto. Agregar un link de “ir hacia el contenido principal” o “saltear navegación” en el principio de cada página, hace que los usuarios, ya familiarizados con los contenidos del sitio, puedan saltear la estructura e ir hacia la información que están buscando fácilmente.</p><h4>Solución Recomendada</h4><p>Resulta muy simple agregar un link en el principio de la página, que funcione como el primer elemento que puede recibir “focus” en la misma. Este link debería permanecer disponible para los lectores de pantalla pero no necesariamente debe ser visible en la web, por lo que no altera el diseño.</p><p>Una forma fácil de realizarlo es definir un link con CSS que lo posicione fuera de la vista, a menudo encima o en la izquierda de la ventana del navegador. Cuando el link recibe el focus, se mueve hacia la ventana del navegador y queda visible.</p><pre>&lt;a href=”#main” class=”skip-link”&gt;Skip to main content&lt;/a&gt;</pre><pre>.skip-link a {<br>   position: absolute;<br>   top: -40px;<br>   left: 0;<br>   padding: 6px;<br>}</pre><pre>.skip-link a:focus {<br>   position: static;<br>   left: 0;<br>   top: 0;<br>}</pre><p>Además, se suele agregar un tabindex=0 a la región apuntada, para evitar que ciertos navegadores la reconozcan y hagan “foco” en la misma.</p><pre>&lt;main id=”main” tabindex=”-1&quot;&gt;</pre><h3>3. Imágenes con rótulos y enlaces incorrectamente etiquetados</h3><p>Resulta casi obvio decir que para mantener un sitio lo más accesible posible, todas las imágenes, logos e iconos deberían tener los atributos alt correspondientes y que estos deben ser lo más explicativos posible. Ej: un logo con un alt que dice “logo” no ayuda demasiado a la interpretación semántica del sitio. En general el alt para los logos debe contener, de mínima el nombre de la empresa a la que hace referencia dicho logo.</p><p>No obstante, otro problema usual que suelen tener las imágenes, es cuando son utilizadas como enlaces. Una imágen que sirve como enlace sólo posee una única forma de identificar el destino del enlace o su comportamiento: mediante la visión. Esta información permanece inaccesible para usuarios no videntes, por lo que debería estar disponible de otro modo.</p><h4>Solución Recomendada</h4><p>La mejor solución para este tipo de problemas suele ser el uso correcto del atributo alt, como se dijo anteriormente. Añadir un alt más descriptivo, puede ser de mucha ayuda para todo tipo de usuarios, un ejemplo podría ser: alt= &quot;Visit the homepage&quot; para indicar el destino de la imagen. Sin embargo, muchas veces los atributos alt se utilizan para dar algún tipo de información extra, en estos casos, una buena solución alternativa sería, reestructurar el HTML y utilizar información extra sobre la imagen, como se puede ver en el siguiente ejemplo:</p><pre>&lt;a href=&quot;http://www.theseeingeye.org&quot;&gt;<br>   &lt;img src=&quot;images/shepherd.jpg&quot; alt=&quot;&quot;&gt;<br>   Visit the Seeing Eye’s home page!<br>&lt;/a&gt;</pre><h3>4. Contenido expandible sin las indicaciones correctas</h3><p>En muchos sitios se utilizan con frecuencia contenidos que son sólo visibles cuando el usuario sitúa el puntero encima de ciertas regiones (hover) o cuando se hace click sobre algún elemento.</p><p>Cuando este tipo de contenido no es marcado correctamente, puede causar problemas tanto para los usuarios de lectores de pantalla como también para aquellos que sólo se encuentran utilizando el teclado. En general, existen dos casos en donde esto se torna frecuente:</p><ol><li>En menúes que poseen algún tipo de submenú revelado cuando el puntero se posiciona encima del elemento. Sin las consideraciones de accesibilidad correspondientes, estos submenús van a permanecer inaccesibles para aquellos usuarios que no utilicen puntero y casi invisibles para los que esten usando un lector de pantalla.</li><li>En la utilizaciones de “acordeones”, los usuarios con lectores de pantalla no tendrán ningún medio efectivo para determinar si el acordeón está desplegado o no y por lo tanto el acceso a dicho contenido no será posible.</li></ol><h4>Solución Recomendada</h4><p>Lo más sencillo es utilizar el atributo aria-controls en el elemento que controla el contenido expandible y que el valor de dicho atributo sea el ID del elemento que engloba al contenido expansible. Además, se debería agregar un atributo aria-expanded al mismo elemento anterior, para controlar cuando el contenido este expandido o no. De forma tal que dicho atributo será true con el contenido expandido y false en caso contrario. Este último, debería actualizarse dinámicamente en cada caso que el contenido sea mostrado o no. Ejemplo:</p><pre>&lt;a href=”#” aria-expanded=”false” aria-controls=”test”&gt;Test&lt;/a&gt;<br>&lt;div id=”test”&gt; Expanded Content&lt;/div&gt;</pre><h3>5. Mal uso del contraste entre colores</h3><p>No es muy usual que se piense en el contraste que deben tener los colores a la hora de superponerlos. Es decir, en la mayoría de los casos, los colores vienen definidos por los lineamientos de diseño o por la identidad que se quiere dar sobre el sitio. Sin embargo, existen muchas combinaciones que no son viables ni accesibles a todo tipo de público. Al margen de las combinaciones obvias, un texto sin el suficiente contraste resulta problemático para usuarios con poca visión o con deficiencias en la distinción de colores.</p><p>Existe una medida establecida para el contraste y diversos sitios y aplicaciones que permiten calcularla: el ratio. Se puede recurrir, por ejemplo, al <a href="https://webaim.org/resources/contrastchecker/">sitio oficial de accesibilidad</a> web para evaluar si los colores utilizados tienen el contraste correcto. En general, la presentación visual de textos sobre imágenes o sobre un color sólido debería tener un contraste de al menos: 4.5:1.</p><h4>Solución Recomendada</h4><p>Simplemente asegurarse de todos los textos sobre imágenes o colores sólidos tengan un contraste de al menos 4.5:1 para texto normal y de 3:1 para textos grandes. Se puede tener en cuenta que el color Gris es un color que a menudo puede tener ratios de contraste insuficientes con cualquier color de fondo. Si este color es utilizado para elementos en el sitio, se puede utilizar el #757575, es una buena opción, que mantiene estable el contraste tanto con fondos claros como oscuros.</p><h3>Conclusión</h3><p>Los 5 problemas listados anteriormente son simplemente un subconjunto escogido arbitrariamente, con caracter demostrativo y ejemplificativo, de muchas falencias que presentan los sitios en la actualidad. Quien quiera ir aún más lejos, puede utilizar herramientas como <a href="http://www.508checker.com/">http://www.508checker.com/</a> para analizar sus sitios. En resumen, existe mucho <a href="https://www.w3.org/WAI/roles/developers/">material sobre accesibilidad</a> que debería ser consultado y tenido en cuenta para construir nuestros sitios. No obstante, controlar los fallos aquí listados puede ser un buen primer paso.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ca2e814a4f85" width="1" height="1" alt=""><hr><p><a href="https://medium.com/altoros/existe-una-gran-cantidad-de-personas-que-conviven-con-alg%C3%BAn-tipo-de-discapacidad-o-disminuci%C3%B3n-ca2e814a4f85">Existe una gran cantidad de personas que conviven con algún tipo de discapacidad o disminución.</a> was originally published in <a href="https://medium.com/altoros">Altoros 🚀</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[A Cleaner Software Design in Ruby on Rails]]></title>
            <link>https://medium.com/a-cleaner-software-design-in-ruby-on-rails/a-cleaner-software-design-in-ruby-on-rails-fd69c907c7e?source=rss-c4a9b6b8bc6a------2</link>
            <guid isPermaLink="false">https://medium.com/p/fd69c907c7e</guid>
            <category><![CDATA[ruby-on-rails]]></category>
            <dc:creator><![CDATA[Altoros Argentina]]></dc:creator>
            <pubDate>Tue, 08 Oct 2019 18:33:48 GMT</pubDate>
            <atom:updated>2019-10-09T14:55:29.887Z</atom:updated>
            <content:encoded><![CDATA[<blockquote>A Cleaner Software Design in Ruby on Rails</blockquote><p>Ruby on Rails is a well-known web application framework that follows the Model View Controller pattern. It provides a fair set of tools that let you be productive from day one, but what happens when your business logic starts getting complex and complex? The “Fat model, skinny controller” statement is just a ticking bomb until your codebase ends up having models with multiple responsibilities and mixing concerns that at the end of the day will make maintenance and development experience worse as new requirements come by. In this post, the idea is to show how to enhance a Rails application to start using patterns that let you separate concerns ala Object-Oriented Programming. Nonetheless, I suggest taking a pragmatic approach, I don’t think these patterns should always be used nor implemented in an application from day one, it depends on which stage of development you are and if the mentioned problems start to appear.</p><p>In this post I will suggest three possible patterns to implement in your application:</p><ul><li>Policies</li><li>Services</li><li>Decorators</li></ul><h3>Policy Objects</h3><p>Policy Objects encapsulate the business rule or authorization logic for an action to happen. A fairly good implementation of this pattern can be seen in the <a href="https://github.com/varvet/pundit">pundit</a> gem, which just gives you some nice tooling around calling a plain old ruby object that must implement an interface.</p><p>Let’s say that you have a blog where you have two kinds of users: authors and admins. Authors can publish their posts if they haven’t been published yet while admins can do it whenever they want.</p><pre>class PostPolicy<br>  attr_reader :user, :post</pre><pre>  def initialize(user, post)<br>    @user = user<br>    @post = post<br>  end</pre><pre>  def publish?<br>   return true if user.admin?</pre><pre>   post.author == user &amp;&amp; post.draft?<br>  end<br>end</pre><p>Particularly, with Pundit, the boilerplate to connect a controller action to this would be:</p><pre>def publish<br>  @post = Post.find(params[:id])<br>  authorize @post, :publish?<br>  @post.publish!<br>  redirect_to @post<br>end</pre><p>It uses reflection to look for a class named like the class of your object (in this case, Post) with a “Policy” suffix.</p><h3>Service Objects</h3><p>Service objects encapsulate the actions in your application. These can be triggered by users, events, jobs or whatever triggers an action. There are many different ways to name these particular objects, either as a substantive or verb, exposing a single method named “call” or something more descriptive according to how you have named the class. In my case, I tend to use verbs, but just find a convention and stick to it.</p><p>As mentioned before, you can just use Plain Old Ruby Objects, or use good implementations such as <a href="https://github.com/collectiveidea/interactor">interactor</a>, <a href="https://github.com/trailblazer/trailblazer-operation">trailblazer-operation</a> or <a href="https://github.com/adomokos/light-service">light-service</a> that add some nice tooling around the functionality.</p><p>Given our Blog example before, let’s say that now when a Post is published, we want to push notify the author’s subscribers that a new post is available.</p><p>We could do many things here:</p><ol><li>Given our controller code from before, we could just notify the subscribers after the <strong>@post.publish!</strong> line.</li><li>We could add an <strong>after_update</strong> callback to the Post model that is triggered after publishing.</li><li>Encapsulate this logic in a separate object.</li></ol><pre>class PublishPost<br>  class &lt;&lt; self<br>    def call(*args)<br>      new(*args).call<br>    end<br>  end</pre><pre>  def initialize(post)<br>    @post = post<br>  end</pre><pre>  def call<br>    flag_post_as_published(@post)<br>    notify_subscribers(@post.author)<br>    OpenStruct.new(success?: true, post: @post)<br>  end</pre><pre>  private</pre><pre>  def notify_subscribers(author)<br>    author.subscribers.each do |subscriber|<br>      push_notification_client<br>        .publish(channel: &quot;user_#{subscriber.id}_channel&quot;,<br>                 message: &quot;#{author.name} has published a new post. See here!&quot;)<br>    end<br>  end</pre><pre>  def flag_post_as_published(post)<br>    post.publish!<br>  end</pre><pre>  def push_notification_client<br>    @push_notification_client ||= PushNotificationClient<br>                                    .new(api_key: ENV[&#39;PUSH_NOTIFY_API_KEY&#39;])<br>  end<br>end</pre><p>Controller code:</p><pre>def publish<br>  @post = Post.find(params[:id])<br>  authorize @post, :publish?<br>  result = PublishPost.call(@post)<br>  if result.success?<br>    redirect_to @post<br>  else<br>    ...<br>  end<br>end</pre><p>As you see, now the service object encapsulates all the behaviour related to a post publishing. This is a naive implementation of it, and we can debate that as soon as we have to add some error handling with database transaction and/or rollbacks to the service if it’s a good idea to have everything in a single object. Maybe we could have a separate service object that encapsulates the notification behaviour. When you have to chain service objects, each being responsible for rolling back in case of errors, it’s a good idea to use something like <a href="https://github.com/collectiveidea/interactor#organizers">interactors’ gem organizers</a>.</p><h3>Decorator Objects</h3><p>The decorator pattern is used to wrap an object and extend its functionality without modifying the object itself.</p><p>When your view starts to have branching behaviour depending on the object current state, you have two options in a vanilla Rails app: either implement this logic in the template itself or use helpers to encapsulate it a bit more. Despite the latter is better than the first, I think that in bigger applications this is a problem because all the helper methods go into a global namespace accessible by the view when they are supposed to encapsulate code for a fairly low set of models or objects.</p><p>We could use decorators to wrap all this logic, either implemented by using the <a href="https://ruby-doc.org/stdlib-2.5.1/libdoc/delegate/rdoc/SimpleDelegator.html">SimpleDelegator</a> Ruby class or implementations such as <a href="https://github.com/drapergem/draper">draper</a>.</p><p>So let’s say that in our Post view, we want to show a tag “Published” or “Draft” depending on the Post status. If it’s published, we would like to show the publishing date in a human-readable format, of course.</p><p>Implementing it in a view would look like this:</p><p><strong>posts/show.html.slim</strong></p><pre>.row<br>  .col-sm-12<br>    .status-tag<br>      - if @post.draft?<br>        = &quot;Draft&quot;<br>      - else<br>        = &quot;Published&quot;<br>    .publishing-date<br>      = !@post.draft? ? @post.published_at.stftime(&quot;%d %b %Y&quot;)<br>  <br>.row  <br>  .col-sm-12<br>    .text-editor<br>       ...</pre><p>Moving things to a helper would end up with a view that looks like this:</p><p><strong>posts/show.html.slim</strong></p><pre>.row<br> .col-sm-12<br>   .status-tag<br>     = format_status(@post)<br>   .publishing-date<br>     = format_published_at(@post)</pre><pre>.row<br> .col-sm-12<br>   .text-editor</pre><p><strong>helpers/post_helper.rb</strong></p><pre>module PostHelper<br> def format_status(post)<br>   post.draft? ? &#39;Draft&#39; : &#39;Published&#39;<br> end</pre><pre> def format_published_at(post)<br>   return &#39;&#39; if post.draft?<br>   post.published_at.strftime(&quot;%d %b %Y&quot;)<br> end<br>end</pre><p>But, as mentioned before, all method helpers are available in a global namespace where if we don’t name things properly, we can override other methods unintentionally. So maybe, a workaround would be naming the methods “<strong>post_format_status</strong>”, “<strong>post_format_published_at</strong>”, etc.</p><p>But if we use a decorator, we could encapsulate and end up with something like this:</p><p><strong>decorators/post_decorator.rb</strong></p><pre>class PostDecorator &lt; SimpleDelegator<br> def formatted_status<br>   __getobj__.draft? ? &#39;Draft&#39; : &#39;Published&#39;<br> end</pre><pre> def formatted_published_at<br>   return &#39;&#39; if __getobj__.draft?<br>   __getobj__.published_at.strftime(&quot;%d %b %Y&quot;)<br> end<br>end</pre><p><strong>posts/show.html.slim</strong></p><pre>.row<br> .col-sm-12<br>   .status-tag<br>     = @post_decorator.formatted_status<br>   .publishing-date<br>     = @post_decorator.formatted_published_at</pre><pre>.row<br> .col-sm-12<br>   .text-editor</pre><p>With the patterns described above, we started to establish more boundaries between the different components that live in our application, separating concerns and letting us extend, maintain and organize our logic in a better way than a vanilla Rails setup, which encourages you to make your controllers or models big. As a side effect, we can test in isolation fairly easy, letting us get picky in scenarios we prefer to test with less boilerplate and noise (if you can’t test all).</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=fd69c907c7e" width="1" height="1" alt=""><hr><p><a href="https://medium.com/a-cleaner-software-design-in-ruby-on-rails/a-cleaner-software-design-in-ruby-on-rails-fd69c907c7e">A Cleaner Software Design in Ruby on Rails</a> was originally published in <a href="https://medium.com/a-cleaner-software-design-in-ruby-on-rails">A Cleaner Software Design in Ruby on Rails</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How we hire! ]]></title>
            <link>https://medium.com/@Altoros.com/how-we-hire-f4ec2920bb65?source=rss-c4a9b6b8bc6a------2</link>
            <guid isPermaLink="false">https://medium.com/p/f4ec2920bb65</guid>
            <category><![CDATA[technology]]></category>
            <category><![CDATA[hiring]]></category>
            <category><![CDATA[hr]]></category>
            <category><![CDATA[recruiting]]></category>
            <dc:creator><![CDATA[Altoros Argentina]]></dc:creator>
            <pubDate>Wed, 11 Sep 2019 14:57:46 GMT</pubDate>
            <atom:updated>2019-09-11T14:57:46.733Z</atom:updated>
            <content:encoded><![CDATA[<p><strong>Nuestro proceso de selección</strong></p><p>Sabemos que iniciar un proceso de entrevistas lleva tiempo y mucha energía, todos estuvimos de ese lado alguna vez. Buscar un cambio laboral implica por un lado haber tomado la decisión de dejar tu lugar actual para encontrar uno mejor que satisfaga tus necesidades y eso no es fácil de encontrar y muchas veces los procesos de selección pueden resultar agotadores, no sólo por la energía que implican sino también porque a veces no sabemos cómo nos fue o qué está pasando puertas adentro.</p><p>En Altoros intentamos que las entrevistas nos sirvan a ambas partes, conocer la versión real, no sólo tu mejor versión (que también la queremos ver y potenciar si trabajar con nosotros).</p><p>Por esto mismo tratamos de tener un proceso claro, sin “trampas” para que sepas exactamente qué esperar y manteniendo nuestros valores. Por eso nuestra filosofía de hiring consta de:</p><p>1- Aplicá 📝: ¿Encontraste un rol que te puede interesar en nuestra website <a href="https://altoros.com.ar/#vacancySection">https://altoros.com.ar/#vacancySection</a>?</p><p>Te vamos a contactar para coordinar una primera llamada con alguien del equipo de HR.</p><p>Te dejamos algunos tips:</p><ul><li>Asegurate de que tus skills y experiencia estén lo más alineadas posible con lo que estamos buscando</li><li>Asegurate que todos tus datos de contacto sean actuales y claros (email, teléfono, skype, etc)</li><li>Incluir toda la experiencia laboral que hayas tenido. Aunque seas un recién graduado universitario o tengas experiencia laboral limitada, incluí proyectos relacionados con la universidad o cursos que demuestren tus habilidades y conocimientos relevantes.</li></ul><p>2- 🤓¿Cómo revisamos las solicitudes?</p><p>Cada vez que alguien aplica a cualquiera de nuestras búsquedas revisamos su CV, y siempre vas a recibir una respuesta nuestra, aún cuando no avancemos en el proceso.</p><p>Si encontramos coincidencias vamos a programar un llamado en los próximos 3 días hábiles para conocerte un poco más. Traé todas las preguntas que tengas! Es también tu oportunidad de saber más sobre Altoros y sobre el rol.</p><p>3- 🗣 Contanos sobre vos:</p><p>Avanzamos a la entrevista con alguien de HR, que puede ser telefónica o presencial, y durará aproximadamente 40 minutos. La idea es que podamos conocerte un poco más y podamos contarte más sobre el puesto. Preparate para contarnos y explayarte sobre tus experiencias pasadas, incluyendo tipos de proyectos, tecnologías que manejaste, equipos de trabajo que tuviste, metodologías que pusiste en práctica, tus logros en tus trabajos anteriores, tu motivación para cambiar de trabajo e interés en Altoros, entre otras cosas que vayan surgiendo a partir de la charla.</p><p>4- 👨‍💻 Pone a prueba tus conocimientos:</p><p>En esta etapa vas a conversar con parte del equipo técnico de Altoros, quienes podrían ser tus compañeros de trabajo. Es importante que para esta entrevista, que de nuevo podrá ser presencial o telefónica, tengas tu compu para realizar la entrevista ya que la idea es que puedan hacer algunos ejercicios juntos, y a veces realizan también un pair programming.</p><p>Durará aproximadamente 60 minutos<em> </em>en los que además de los ejercicios te podrán hacer algunas preguntas más teóricas.<em> </em>Para ayudarte un poquito tu contacto de HR te compartirá un <em>cheat sheet</em> y tips para que puedas prepararte antes y tener a mano la información que necesites durante la entrevista para demostrar todas tus capacidades.</p><p>La entrevista técnica simulará el tipo de trabajo que hacemos en Altoros y además se evaluará cómo lo haces! La idea es que además de resolver el ejercicio técnico puedas contarle al tech team cómo llegaste a ese resultado porque para nosotros es importante la creatividad al intentar resolver el problema, la experiencia aplicando los algoritmos correctos, capacidad de explicar claramente el enfoque adoptado y la calidad del código escrito ya que para nosotros<a href="https://altoros.com.ar/#headerSection"> “El código es ley”</a>.</p><p>La profundidad técnica del candidato, las habilidades de comunicación, la capacidad de captar nuevos conceptos y el fit cultural serán los puntos en los que haremos foco durante todo el proceso.</p><p>Si bien como te mencionamos antes, la entrevista durará 60 minutos, siempre aconsejamos agendarla un día que estén tranquilos, que cuenten con disponibilidad suficiente y un poquito más por si algo llegara a surgir y se extiende unos minutos más, tener esto en cuenta te va a beneficiar a la hora de concentrarte.</p><p>5- 👥Decidimos y comunicamos:</p><p>Recopilamos los comentarios de todos los entrevistadores y determinamos los próximos pasos.</p><p>Nuestro tiempo de respuesta suele ser entre 3 y 5 días hábiles. En el caso que decidieramos avanzar con vos, organizamos un llamado con el manager del área para que puedan conocerse, y si todos los que participaron del proceso están de acuerdo en que sos un fit tanto cultural como técnico avanzamos a realizar la oferta</p><p>6- 📜 Oferta:</p><p>Te enviamos por mail la mejor oferta que podemos hacerte de acuerdo al seniority que determinamos en la entrevista técnica. Una vez que te enviamos la oferta revisamos con vos cualquier duda que puedas tener en esta instancia. Claro que durante todo el proceso podés comunicarte con nosotros las veces que lo consideres.</p><p>Sí nosotros también somos un match para vos :) pautamos la fecha de ingreso y bienvenido al team!</p><p>Entendemos que nuestro proceso puede ser largo y difícil. Lo construimos de esa manera porque queremos asegurarnos de que todos los que se unan al team Altoros lo hagan por las razones correctas. Lo peor sería que alguien se fuera después de unos meses porque el candidato (o nosotros) nos dimos cuenta de que no encajaba bien.</p><p>Si te interesa conocernos un poco mas podes hacerlo en:</p><p><a href="https://twitter.com/AltorosArg">https://twitter.com/AltorosArg</a></p><p><a href="https://altoros.com.ar/">https://altoros.com.ar</a></p><p><a href="https://www.instagram.com/altoros_arg/">https://www.instagram.com/altoros_arg/</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=f4ec2920bb65" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[From Human Resources to Human Transformation]]></title>
            <link>https://medium.com/@Altoros.com/from-human-resources-to-human-transformation-c61ba3d5240f?source=rss-c4a9b6b8bc6a------2</link>
            <guid isPermaLink="false">https://medium.com/p/c61ba3d5240f</guid>
            <category><![CDATA[website-development]]></category>
            <category><![CDATA[hr]]></category>
            <category><![CDATA[website]]></category>
            <category><![CDATA[mindset]]></category>
            <category><![CDATA[humanresourcedevelopment]]></category>
            <dc:creator><![CDATA[Altoros Argentina]]></dc:creator>
            <pubDate>Thu, 18 Jul 2019 14:05:26 GMT</pubDate>
            <atom:updated>2019-07-18T14:05:26.893Z</atom:updated>
            <content:encoded><![CDATA[<p>We are very happy to launch our <strong>new website</strong> for Altoros Argentina.</p><p>We would like to share a little bit about the process and everything we considered to create a website from scratch that truly represents how we work, what we value -both technically and culturally- and what we aim for.</p><p>When we started working on this new website, we realized that we needed to start by defining some of our fundamental bases as a team.</p><p>First, we focused on the values we have in Altoros Argentina and, to understand that, we observed and shared conversations, debates, tech talks, events, we have done internal and external surveys with our clients too for months, in order to identify those things that we find valuable, that we appreciate and also, what our clients appreciate about working with us. Luckily, we found that we were all on the same page, so we just had to put it into words.</p><p>As a result, these are the values that represent us:</p><ul><li><strong>The Code is the Law:</strong> By this we purely mean the technical side and how the development team handles their job every day. There is no “random code” for them, just the importance of having a “clean”, right and thought code is what drives them.</li><li><strong>Excellence is Our Guide</strong>: As we handle all projects in such a clean way and without “writing code for the sake of it”, we always give our best and commit with each project, regardless of their size. This is what has proven to give us the best results in each project and gain our clients’ support.</li><li><strong>We build relationships and we communicate to be connected</strong>, so that the everyday life at the office runs smoothly and with good humour which help us develop confidence and empathy. We believe that through communication and the creation of strong bonds we get to know each other strengths and weaknesses that allow us to identify problems on time, reinforce those things which we are good at and use all of that in favour of the entire team.</li></ul><p>This is the “winning formula” that sets us apart from other companies and allows us to have excellent talents on our team.</p><p><a href="https://altoros.com.ar/">https://altoros.com.ar/</a> 😜</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c61ba3d5240f" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Compartimos lo que fue la experiencia de la meetup y lo que escribo Manuel Garcia nuestro CTO…]]></title>
            <link>https://medium.com/@Altoros.com/compartimos-lo-que-fue-la-experiencia-de-la-meetup-y-lo-que-escribo-manuel-garcia-nuestro-cto-dd92e11744d5?source=rss-c4a9b6b8bc6a------2</link>
            <guid isPermaLink="false">https://medium.com/p/dd92e11744d5</guid>
            <category><![CDATA[blockchain]]></category>
            <dc:creator><![CDATA[Altoros Argentina]]></dc:creator>
            <pubDate>Thu, 27 Jun 2019 20:38:56 GMT</pubDate>
            <atom:updated>2019-06-27T20:38:56.492Z</atom:updated>
            <content:encoded><![CDATA[<p>Compartimos lo que fue la experiencia de la meetup y lo que escribo Manuel Garcia nuestro CTO, sobre la experiencia en el blog de <a href="https://medium.com/u/711da2b79ac3">ProtoFire.io</a> 🚀👌</p><h3>Using Cryptocurrency in Real Life with Burner Wallet</h3><p><a href="https://medium.com/@mgarciap">Manuel Garcia</a></p><p><strong><em>Protofire delivers interactive experience to attendees of the meetup via Burner Wallet employed to place individual and group orders at a bar.</em></strong></p><p>We have lived an amazing <a href="https://www.meetup.com/es-ES/Blockchain-Litoral/events/261909146/?eventId=261909146">meetup</a> centered around cryptocurrencies and blockchain, which took place at Santa Fe, Argentina, on June 12, 2019. Attended by 50 enthusiasts, the event focused on a <strong>Burner Wallet</strong> and how it can be used in real life for transferring assets or for such daily routine as paying for orders at a bar. In addition, attendees learned about the MakerDAO ecosystem and its offerings in the blockchain space.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/0*L19M2iNh8sgVlk1C" /></figure><p><strong>What is DAI and the xDAI Chain?</strong></p><p>The first session was delivered by <a href="https://www.linkedin.com/in/mgarciap/">Manuel Garcia</a>, CTO at Protofire and Director at Altoros Argentina. He introduced attendees to the MakerDAO stable coin: <a href="https://makerdao.com/en/dai">DAI</a>. Later, Manuel jumped into <a href="https://poa.network/xdai">xDAI Chain</a>, which is an Ethereum-compatible sidechain with a native USD-stable currency backed up by DAI — xDai. Manuel overviewed the chain’s under-the-hood mechanisms, as well as explained some complex aspects behind operating cryptocurrencies.</p><p>“I was eager to learn more about crypto money and blockchain, I want another event!” — an attendee of the meetup</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*V2tFy_BWXahLmGWt" /><figcaption>Manuel Garcia talking about Dai and xDAI Chain at the meetup</figcaption></figure><p>The conversation continued for an hour, and the audience showed great interest in the topic with numerous questions asked after the session.</p><p><strong>Burner Wallet in action</strong></p><p>The second session was delivered by Sebastián Galiano and Agustin Pane, blockchain engineers at Protofire. They provided an overview of a customized version of Austin Griffith’s <a href="https://github.com/austintgriffith/burner-wallet">Burner Wallet</a> — designed to move xDai quickly in a web browser — and reported the progress on its development.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*X3MuhmQ1rGw8hObY" /><figcaption>An attendee scanning and loading the Burner Wallet</figcaption></figure><p>At the event, each attendee got a printed wallet with a QR code (the private key). By scanning it with their mobile phones they opened a web browser with the Burner Wallet loaded with 10 DAI and enough xDAI to pay for the network transaction fees. This way, the audience could try out how blockchain works and learn the concepts of tokens, gas, coins, hash, etc. by experimenting with transferring assets without the need to download any specific app. You can find out more about the project in <a href="https://github.com/protofire/burner-wallet/">this GitHub repo</a> or on the <a href="https://altowallet.net/">website</a>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*bYBa8Pyk_NtbJFPU" /><figcaption>A paper wallet with a leather-style design</figcaption></figure><p>“I was looking forward to a technical talk, but it was a great experience using the Burner Wallet, as well.” — an attendee of the meetup</p><p>Attendees utilized Burner Wallet to pay for orders at the bar, some individually, and others transferred everything to a single wallet to order faster for a particular table. Soon, the room was filled with beers, pizzas, hamburgers, and good vibes. It was a great night!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*pmoxFF0winUNgvB1" /><figcaption>Interactive experience with the Burner Wallet</figcaption></figure><p>We want to thank our co-sponsors — <a href="http://poa.network/">POA Network</a> and <a href="https://makerdao.com/">MakerDAO</a> — for assisting Protofire’s team in making the event such a success!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*4O6vtHcgsno5CnaS" /><figcaption>Only good vibes at the meetup</figcaption></figure><p>With 50 downloads of Burner Wallet, we shared a total of 500 DAI as a gift, gave 50 sets of branded gifts, ate 30 pizzas and 40 hamburgers, as well as drank 150 beers. And we are looking forward for a new great event!</p><p>Feel the vibes of the meetup and watch <a href="https://www.youtube.com/watch?v=Gfe_VZJKjnw">this video</a>!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=dd92e11744d5" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Redis & Ruby]]></title>
            <link>https://medium.com/@Altoros.com/redis-ruby-230ad00eb168?source=rss-c4a9b6b8bc6a------2</link>
            <guid isPermaLink="false">https://medium.com/p/230ad00eb168</guid>
            <category><![CDATA[redis]]></category>
            <category><![CDATA[rubyargentina]]></category>
            <category><![CDATA[ruby]]></category>
            <category><![CDATA[technews]]></category>
            <category><![CDATA[technology]]></category>
            <dc:creator><![CDATA[Altoros Argentina]]></dc:creator>
            <pubDate>Thu, 04 Apr 2019 20:23:36 GMT</pubDate>
            <atom:updated>2019-04-04T20:23:36.684Z</atom:updated>
            <content:encoded><![CDATA[<p>Redis &amp; Ruby</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*GZSyFCjj52bYRT2aWpYJ8A.jpeg" /></figure><p>Autor: Leonardo Tolosa</p><h3>Introducción</h3><p>En este artículo se hará una introducción a Redis, explicando sus características principales y profundizando con ejemplos de código cómo aprovechar algunas de ellas para resolver dos escenarios típicos que se presentan en varios tipos de proyectos.</p><h3>Prerrequisitos</h3><p>Este artículo hace énfasis en cómo aprovechar Redis desde aplicaciones Ruby, por lo que son necesarios conocimientos de ese lenguaje para comprender los detalles de implementación explicados, pero no es requerido para comprender cómo aprovechar esos features en otras plataformas.</p><h3>Redis y Ruby</h3><p>Hay un motivo específico por el que es importante que un desarrollador Rails conozca Redis, y se trata de<a href="https://sidekiq.org/"> Sidekiq</a>. La mayoría de las aplicaciones Rails de complejidad mediana o grande necesitan algún tipo de procesamiento asincrónico, y la solución más común para ese requerimiento es Sidekiq. El servicio de Sidekiq depende de Redis, y es por eso que en muchos proyectos ya se dispone de un servidor de Redis. Teniendo en cuenta esta gran disponibilidad, tiene sentido aprovecharlo para algo más que Sidekiq.</p><p>También es por este motivo que en los ejemplos de código en Ruby se usará la gema<a href="https://github.com/redis/redis-rb"> redis-rb</a>, que es la misma que utiliza Sidekiq para conectarse con Redis. Esto evita agregar otra dependencia a un proyecto si en el mismo ya utilizamos Sidekiq.</p><h3>Instalación de aplicaciones de ejemplo</h3><p>El código Ruby de las dos aplicaciones de ejemplo se encuentra en el<a href="https://github.com/tolosa/redis-demo"> repositorio público</a> del proyecto. Una vez clonado localmente, se requieren algunas gemas para su funcionamiento, que pueden instalarse utilizando Bundler (con el comando bundle install). En la sección de cada ejemplo se explicará cómo ejecutarlos.</p><p>También es necesario un servidor de Redis. Las aplicaciones intentarán conectarse a un servidor instalado localmente, accesible en el número de puerto por defecto (6379). El servicio puede instalarse localmente de distintas maneras pero se recomienda utilizar Docker, el proyecto ya incluye un archivo de Docker Compose con la configuración requerida, por lo que puede iniciarse un container con el servidor simplemente ejecutando el comando docker-compose up.</p><p>En los ejemplos se explican varios comandos de Redis, que pueden probarse ejecutándolos manualmente en una consola interactiva de Redis. Esta consola se instala junto al servidor, puede iniciarse con el comando redis-cli.</p><h3>Introducción a Redis</h3><p>Redis es un sistema NoSQL de almacenamiento y procesamiento de datos, de tipo clave/valor (key/value storage). Eso significa que maneja estructuras de datos atómicas, cada una accesible a través de un key identificativo único, de forma similar a un hash.</p><p>Al ser una base de datos de tipo key/value, y exceptuando unos pocos comandos particulares, Redis en general es incapaz de filtrar datos por su contenido. La funcionalidad disponible para buscar elementos almacenados es específica para cada uno de los tipos de datos soportados en Redis. Esto significa que para decidir qué tipos de datos de Redis a utilizar hay que tener en cuenta no sólo la estructura de la información a almacenar, si no también la forma en la que van a ser consultados.</p><p>Además de esa funcionalidad básica de almacenamiento de objetos, ofrece varios tipos de operaciones posibles especializadas para cada uno de los tipos de estructuras de datos soportadas. En conjunto, esas operaciones constituyen una cantidad de features inferior a las disponibles en otras bases de datos NoSQL, de mayor flexibilidad, pero por su especialización es capaz de ejecutarlas con una performance muy superior.</p><p>Si bien ofrece persistencia en disco, por defecto los datos son almacenados y procesados solamente en memoria, lo que hace que Redis tenga una performance muy elevada. La opción de persistencia a disco es configurable de distintas formas para obtener mayor o menor confiabilidad a costa de diferente impacto en la performance, pero ninguna es tan confiable como una base de datos ACID. Es por eso que no se recomienda utilizarlo como sistema de almacenamiento permanente de datos, pero sí para aprovechar su performance en procesamiento de grandes cantidades de información, o como almacenamiento de datos volátiles (tal como eventos, colas, métricas de performance, etc.)</p><h3>Sorted sets</h3><p>Los sorted sets son uno de los tipos de datos nativos de Redis. Cada sorted set se identifica con un nombre único, y cada uno contiene una lista ordenada de pares de valores. Cada par de valores está asociado entre sí, de forma similar a un hash (que también están soportados en Redis, como otro tipo nativo). Uno de los valores contiene un dato cualquiera que uno desee almacenar, y el otro es un valor numérico asociado que representa el ordenamiento de ese elemento dentro del set, denominado “score” (puntaje).</p><p>Además de comandos básicos para agregar y quitar elementos de un set, hay varios específicos relacionados con el cálculo de posiciones y ordenamiento de sus elementos, tales como obtener el ranking de un elemento dentro del set (su número de posición en la lista ordenada), obtener valores dentro de un rango específico, etc.</p><p>Este tipo de datos tiene la particularidad de que internamente en Redis la lista siempre se mantiene ordenada en memoria, por lo que listarlos o filtrarlos posteriormente por orden no tiene un costo adicional de performance (a diferencia de SQL). Es una operación de costo O(1), es decir de un tiempo constante de ejecución, independientemente de la cantidad de elementos que se encuentre dentro del set.</p><h3>Aplicación de ejemplo: ranking de páginas más visitadas</h3><p>Para ejemplificar esta funcionalidad se creó una aplicación simple de prueba, que resuelve un escenario común en muchas aplicaciones web. Se utilizará sorted sets para almacenar en Redis estadísticas simuladas de cantidad de visitas a distintas páginas web, para después obtener un ranking de las más visitadas.</p><p>Típicamente estos valores se almacenarían en una base de datos relacionales (como un campo de cantidad de visitas en una tabla de artículos, por ejemplo), pero para sitios con mucho tráfico el escribir en la base de datos en cada pageview puede tener un impacto negativo en la performance de toda la aplicación.</p><p>La implementación es una aplicación de Ruby de línea de comandos, definida en el archivo<a href="https://github.com/tolosa/redis-demo/blob/master/visits-ranking.rb"> visits-ranking.rb</a> en el repositorio de este proyecto. Puede ejecutarse directamente con cualquier intérprete de Ruby (con el comando ruby visits-ranking.rb).</p><p>Al ejecutarse, en un primer paso se conecta con el servidor de Redis y almacena en el mismo los datos de 30.000 eventos simulados de pageviews distribuidos aleatoriamente en 1.000 páginas diferentes, cada uno conteniendo una URL de página generada al azar con el formato /articles/[id], donde id es un número aleatorio entre 1 y 1000.</p><p>Tras cargar esos 30.000 eventos aleatorios en Redis, luego obtiene y muestra el ranking de las 10 páginas más visitadas y la cantidad de visitas de cada una.</p><p>Resultados de una ejecución de ejemplo:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/522/0*STuei2wqPNntjEHI" /></figure><p>Esta aplicación también muestra benchmarks básicos del tiempo de ejecución de esas operaciones. Nótese su elevada performance: ejecutándose en una PC de escritorio de rango medio (procesador Intel Core i5, 16 GB de RAM, disco SSD) es capaz de almacenar los 30.000 registros en un tiempo promedio de ejecución de 0.5 segundos, y obtener el ranking con el top 10 en menos de un milisegundo.</p><h3>Comandos de Redis relevantes</h3><p>Redis posee un<a href="https://redis.io/commands#sorted_set"> conjunto de comandos</a> para operar con sorted sets, veremos aquellos necesarios para resolver este problema particular.</p><p>El primer paso es agregar los datos que necesitamos a un sorted set. En este caso, cada elemento en el sorted set es la combinación de una URL de una página (como dato), y su cantidad de pageviews como valor de score asociado.</p><p>El comando más básico para agregar elementos a un sorted set es<a href="https://redis.io/commands/zadd"> ZADD</a>, que recibe como argumentos el nombre del sorted set, el dato a almacenar y su valor de ranking.</p><p>Ejemplo:</p><p>ZADD pageviews 1 /articles/42</p><p>En ese ejemplo, pageviews es el nombre del set, 1 es el rank (la cantidad de pageviews), y /articles/42 es el valor a almacenarse (el path de la página).</p><p>El set no es necesario crearlo explícitamente por adelantado, se crea en el momento en que se le agrega un primer elemento. Para este ejemplo se almacenarán estos valores siempre dentro de un mismo sorted set llamado pageviews, pero para casos más avanzados puede ser necesario utilizar varios (separados por categorías, rangos de tiempo, etc).</p><p>Para obtener luego el valor de score de una página se puede utilizar el comando<a href="https://redis.io/commands/zscore"> ZSCORE</a> (nótese que todos los comandos de sorted sets están prefijados con la letra Z). Recibe como primer parámetro el nombre del sorted set (como todos estos comandos), y luego el dato para el que se desea obtener su score (en este caso un path).</p><p>Ejemplo:</p><p>ZSCORE pageviews /articles/42</p><p>Para utilizar estos comandos para mantener un conteo actualizado de pageviews, en cada uno de los mismos habría que ejecutar ZSCORE para obtener la cantidad de pageviews para esa URL, incrementarla en 1, y luego volver a almacenarla con ZADD (por defecto sobreescribe un valor anterior si ya existía). Sin embargo, existe otro comando que permite incrementar el score de un valor y devolverlo en un solo paso, llamado<a href="https://redis.io/commands/zincrby"> ZINCRBY</a>.</p><p>Necesita los mismo parámetros que ZADD, pero en lugar de recibir un valor de score específico a almacenarse recibe un número a sumar al valor de un score existente (al funcionar como contador en este caso, ese valor es 1). Si el elemento no existía en el sorted set, es agregado al mismo con ese valor de incremento como score inicial.</p><p>Este es el resultado de invocar ZSCORE seis veces en una consola de Redis, incrementado el score de dos valores diferentes (/articles/42 o /articles/43):</p><p>127.0.0.1:6379&gt; zincrby pageviews 1 /articles/42</p><p>“1”</p><p>127.0.0.1:6379&gt; zincrby pageviews 1 /articles/42</p><p>“2”</p><p>127.0.0.1:6379&gt; zincrby pageviews 1 /articles/43</p><p>“1”</p><p>127.0.0.1:6379&gt; zincrby pageviews 1 /articles/42</p><p>“3”</p><p>127.0.0.1:6379&gt; zincrby pageviews 1 /articles/43</p><p>“2”</p><p>127.0.0.1:6379&gt; zincrby pageviews 1 /articles/44</p><p>“1”</p><p>Podemos observar que al ejecutar el comando incrementa el valor de score de un dato y lo devuelve, o si todavía no existía lo agrega al set con un score inicial de 1.</p><p>Esto permite que si al visitar una página tenemos que incrementar su pageview y también mostrarlo en la misma, podamos hacerlo ejecutando un sólo comando.</p><p>Una vez poblado el set con los datos, para obtener el ranking de las páginas más visitadas podemos utilizar el comando<a href="https://redis.io/commands/zrevrange"> ZREVRANGE</a>, que devuelve un rango de items ordenados descendentemente por su score (para obtenerlos por orden ascendente se utilizaría el comando<a href="https://redis.io/commands/zrange"> ZRANGE</a>).</p><p>Este comando necesita tres parámetros: el nombre del set y el rango de elementos a obtener (expresado como índice inicial, contando desde cero, y un índice final). Opcionalmente puede recibir un cuarto parámetro WITHSCORES para que devuelva los datos y también sus scores asociados (por defecto devuelve solamente los datos).</p><p>Ejemplo para devolver el top 10 de pageviews, con los valores obtenidos al haber ejecutado todos los comandos ZADD del ejemplo anterior:</p><p>127.0.0.1:6379&gt; ZREVRANGE PAGEVIEWS 0 10 WITHSCORES</p><p>1) “/articles/42”</p><p>2) “3”</p><p>3) “/articles/43”</p><p>4) “2”</p><p>5) “/articles/44”</p><p>6) “1”</p><p>Inversamente, para obtener el ranking de un dato específico podemos utilizar el comando ZREVRANK.</p><p>127.0.0.1:6379&gt; ZREVRANK pageviews /articles/43</p><p>(integer) 1</p><p>El valor devuelto es la posición en el ranking empezando desde un índice de cero, por lo que un valor de 1 significa que es el segundo puesto. En este caso de uso, este comando puede ser útil para obtener y mostrar en cada página su índice de popularidad.</p><h3>Implementación en Ruby</h3><p>La implementación de esta funcionalidad en Ruby es muy simple. El cliente de Redis incluido en la gema ofrece métodos para ejecutar cada uno de los comandos listados, con el mismo nombre y que recibe los mismos parámetros. Una vez instanciado un cliente, puede ejecutarse cada comando con facilidad a través del mismo.</p><p>Ejemplos de Ruby extraídos de la aplicación:</p><p># crea instancia del cliente de Redis</p><p>$redis = Redis.new</p><p># ejemplo de invocación de ZINCRBY</p><p>$redis.zincrby ‘pageviews’, 1, path</p><p># ejemplo de ZREVRANGE</p><p>ranking = $redis.zrevrange ‘pageviews’, 0, 9, with_scores: true</p><p>El método zrevrange devuelve un array bidimensional, donde el primer elemento de cada par es el dato y el segundo el ranking.</p><h3>Pub/Sub</h3><p>Si bien esta funcionalidad de Redis no está relacionada con el almacenamiento o procesamiento de datos, es frecuentemente necesaria en varios tipos de aplicaciones.</p><p>Pub/Sub es el nombre de la funcionalidad que ofrece Redis para implementar sistemas de mensajería aplicando el patrón<a href="https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern"> publisher/subscriber</a>, que consiste en que los emisores de un mensaje (publicadores) puedan enviarlos sin necesitar un conocimiento específico de quiénes son los receptores (subscriptores), o de cuántos son ni dónde se encuentran.</p><p>En lugar de establecer una comunicación directa entre los actores, los publicadores envían mensajes a un “canal”, una abstracción que agrupa tipos de mensajes compartidos entre emisores y receptores. A su vez, los receptores se “suscriben” a los canales de los que les interese recibir mensajes, sin necesitar tampoco conocimiento específico de quiénes son los emisores.</p><p>Físicamente, los canales se encuentran en un servidor de mensajería compartido por los emisores y receptores, en este caso Redis, quien se encarga de recibir y entregar los mensajes.</p><p>Este desacoplamiento entre emisores y receptores simplifica la implementación y permite una mayor escalabilidad. El paradigma es similar al de un message queue, pero más orientado a la entrega de mensajes en tiempo real.</p><p>Esto es útil para varios tipos de aplicaciones que necesiten el manejo de eventos en tiempo real, tales como aplicaciones de chat, notificaciones de actividad en sitios web, envío de métricas de performance, etc.</p><h3>Aplicación de ejemplo: chat</h3><p>En este ejemplo se demostrará el uso de esta funcionalidad para implementar una aplicación simple de chat en Ruby.</p><p>El código fuente de la misma se encuentra en el archivo<a href="https://github.com/tolosa/redis-demo/blob/master/chat.rb"> chat.rb</a>, en el repositorio de la aplicación. Puede ejecutarse con el comando ruby chat.rb.</p><p>Es una aplicación interactiva de consola, que permite enviar y recibir mensajes a otras instancias de la aplicación, todas utilizando un mismo canal en un servidor de Redis. Al iniciar la aplicación pregunta por un nombre de usuario, que utiliza para identificarse en los mensajes enviados. Se une automáticamente a un canal predeterminado, donde envía todos los mensaje que uno escriba (al presionar ENTER), y muestra instantáneamente los recibidos desde otras instancias de la aplicación, junto al nombre del emisor.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/389/0*RXUfe4FGeYIPc8Uo" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/392/0*YTtX9_C6xiR_KvBD" /></figure><p>Para comprobar su funcionamiento, se deben ejecutar al menos dos instancias diferentes de la aplicación (puede hacerse ejecutándola en dos ventanas de consola diferentes, en la misma computadora).</p><h3>Comandos de Redis relevantes</h3><p>Para implementar esta funcionalidad, los comandos relevantes de Redis son SUBSCRIBE y PUBLISH. Al igual que en el ejemplo anterior, estos comandos de Redis se corresponden exactamente con métodos del mismo nombre en el cliente Ruby, por lo que serán descritos en la próxima sección.</p><h3>Implementación en Ruby</h3><p>La gema redis-rb ofrece dos métodos para ejecutar los dos comandos de Redis relacionados con esta funcionalidad, PUBLISH y SUBSCRIBE.</p><p>Veremos un ejemplo del código necesario para el emisor y el receptor de los mensajes (usualmente la misma aplicación, pero pueden ser diferentes). El código aquí mostrado fue extraído de la aplicación de chat de ejemplo, pero simplificado para su comprensión.</p><p>En ambos casos, la variable $client ya contiene una instancia del cliente de Redis de la gema, inicializado de la misma manera que en el ejemplo anterior.</p><h3>Receptor</h3><p>Para recibir mensajes, la aplicación debe utilizar el método subscribe para ejecutar el comando del mismo nombre en Redis, que le permite suscribirse a un canal de un nombre específico (en este ejemplo, almacenado en la constante CHANNEL_NAME):</p><p>$client.subscribe CHANNEL_NAME do |on|</p><p>on.message do |channel, message|</p><p>puts “#{channel}: #{message}”</p><p>end</p><p>end</p><p>El método message acepta un bloque de código que es ejecutado cada vez que se recibe un mensaje en el canal suscrito, recibiendo en los parámetros channel y message el nombre del canal y el mensaje recibido, respectivamente.</p><p>Ambas variables son de tipo string, por lo que si es necesario enviar una estructura de datos más compleja como mensaje se recomienda serializarla como JSON, y deserializarla en el receptor. Eso hace la aplicación de chat, pero en este código de ejemplo solamente se ejecuta el método puts para imprimir el nombre del canal y el mensaje recibido.</p><p>Este código bloquea la ejecución del thread actual, por lo que si se necesita que continúe se recomienda ejecutarlo en un thread separado (eso también puede verse en el código de la aplicación de ejemplo).</p><h3>Emisor</h3><p>Del lado del emisor el código es aún más simple. Para enviar cada mensaje se invoca al método publish, que recibe como parámetros el nombre del canal donde enviar el mensaje y el contenido del mismo.</p><p>Ejemplo:</p><p>$client.publish CHANNEL_NAME, ‘Hello world!’</p><h3>Conclusión</h3><p>Redis es un base de datos con una funcionalidad limitada en comparación con otras soluciones de NoSQL, pero precisamente por su especialización puede resolver muchos escenarios con una performance y escalabilidad muy superior.</p><p>Es muy simple utilizarlo desde Ruby, y al ser un requerimiento para ejecutar Sidekiq en muchos proyectos de Rails ya se dispone de un servidor de Redis, por lo que es conveniente saber cómo aprovecharlo.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=230ad00eb168" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>