<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Paul Marclay</title>
    <description>The latest articles on DEV Community by Paul Marclay (@paulmarclay).</description>
    <link>https://dev.to/paulmarclay</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1064450%2F9c708feb-ae4b-4f15-a39e-734f3c9b051a.jpeg</url>
      <title>DEV Community: Paul Marclay</title>
      <link>https://dev.to/paulmarclay</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/paulmarclay"/>
    <language>en</language>
    <item>
      <title>Estuve a punto de crear una especie de Health Tech SAAS...</title>
      <dc:creator>Paul Marclay</dc:creator>
      <pubDate>Tue, 15 Apr 2025 20:39:23 +0000</pubDate>
      <link>https://dev.to/paulmarclay/estuve-a-punto-de-crear-una-especie-de-health-tech-saas-4e1n</link>
      <guid>https://dev.to/paulmarclay/estuve-a-punto-de-crear-una-especie-de-health-tech-saas-4e1n</guid>
      <description>&lt;p&gt;Pero de repente sentí que no era moralmente correcto, o bien, que yo no estaba moralmente preparado para afrontar la responsabilidad de que alguien se haga daño por negligencia, o como quieran llamarle...&lt;/p&gt;

&lt;p&gt;Tenía todo casi listo, el modelo de negocio, los flujos de usuarios, un chip de telefonía, etc...&lt;/p&gt;

&lt;p&gt;La idea era simple, el modelo de negocios también, no había manera de que no funcione.&lt;/p&gt;

&lt;p&gt;Iba a hacer un Bot para Whatsapp al que pasándole los resultados de tus análisis médicos, éste te devolvería, mediante un Agente IA (Agente creado en ChatGPT), datos más relevantes y estado de tus análisis, no iba a ser un diagnóstico, sólo sería una especie de "traductor", de la jerga del laboratorio, hacia la gente no técnica.&lt;/p&gt;

&lt;p&gt;Pero como dije antes, no pude hacerlo, me había instruido acerca de GDPR, Derechos ARCO, Ley 25.326 Y RES. 47/2018 (Argentina), y un amplio etc para asegurarme de pasar una auditoría.&lt;/p&gt;

&lt;p&gt;Hice bien?, creo que si, creo que la gente en general tiende al "comodismo", y me dio miedo de que se pudieran conformar con la respuesta de una app y omitir visitas al médico, o peor aún, que tomen alguna clase de medida drástica si el "pronóstico" no era tan favorable, porque, seamos sinceros, puede pasar, y puede pasar también en un contexto de una mala interpretación de los análisis por parte del Agente IA.&lt;/p&gt;

&lt;p&gt;Se que si no lo hago yo, de seguro alguien más lo hará, pero bueno, creo que al menos tendré la consciencia tranquila?&lt;/p&gt;

&lt;p&gt;No se que opinarán de esto.&lt;/p&gt;

&lt;p&gt;Saludos!&lt;/p&gt;

&lt;h1&gt;
  
  
  ia #saas #startup
&lt;/h1&gt;

</description>
    </item>
    <item>
      <title>Ruby on Rails está muerto, fin.</title>
      <dc:creator>Paul Marclay</dc:creator>
      <pubDate>Tue, 28 May 2024 23:23:54 +0000</pubDate>
      <link>https://dev.to/paulmarclay/ruby-on-rails-esta-muerto-fin-2cc</link>
      <guid>https://dev.to/paulmarclay/ruby-on-rails-esta-muerto-fin-2cc</guid>
      <description>&lt;p&gt;No, es broma, ni cerca de estar muerto, a pesar de que todos los años aparece alguien sensacionalista matando a Ruby, Rails, PHP y otros lenguajes o frameworks.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Me encanta ROR, no es mi lenguaje materno, pero casi.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Cada vez que tengo una idea, la pienso en Ruby, en Ruby on Rails, me resulta mucho más fácil, tranquilamente se puede hacer un MVP en un día, si uno sabe a lo que quiere llegar.&lt;/p&gt;

&lt;p&gt;Usando ROR, he trabajado en proyectos pequeños y grandes, algunos demasiado grandes, pero Rails hace que se sienta natural cada movimiento que das.&lt;/p&gt;

&lt;p&gt;Ruby y Rails llevan al extremo el paradigma de  "Convention Over Configuration" hasta el punto de que pensar saltarselo suene a blasfemia.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Todo hermoso, pero se acerca un problema&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Al pensar en hacer todo lo que conlleva a un proyecto utilizando sólo Rails, podemos pecar de integridad, y es lo que me pasó.&lt;br&gt;
¿Qué quiero decir con eso?, que estamos utilizando un framework y sus buenas prácticas, pero las buenas prácticas deben organizarnos, no limitarnos.&lt;/p&gt;

&lt;p&gt;Por ejemplo, me ha pasado tener que hacer consultas SQL complejas y, para no ser hereje, obviamente las hacía utilizando las buenas prácticas y modelos de ActiveRecord, a veces resultando queries costosas, difíciles de digerir para SQL mismo, ah, pero yo usaba buenas prácticas del framework.&lt;/p&gt;

&lt;p&gt;Según mis pensamientos, nada debía tocar la base de datos si no era a través de un modelo de datos, su "updated_at" debía reflejar la fecha de última actualización de lo que se tocara, y ni hablar de que algo externo al proyecto accediera a ella!.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;¿A dónde quería llegar con todo esto?&lt;/strong&gt;, justo a este punto, un framework, por más poderoso, grande y versátil que sea, no deja de ser eso, un framework, un marco de trabajo, no restrictivo, si organizativo.&lt;/p&gt;

&lt;p&gt;Utilizar un framework no quiere decir que tenemos que estrictamente limitarnos a sus buenas prácticas (ya veo un aluvión de comentarios de fanáticos de las buenas prácticas), simplemente debemos utilizarlas siempre y cuando nos sea posible, tomar lo que necesitemos y para el resto, usar nuestro ingenio de desarrolladores.&lt;/p&gt;

&lt;p&gt;Este último tiempo trabajando con scrapers para obtener precios de productos en &lt;a href="https://rantiq.com"&gt;RantiQ&lt;/a&gt;, indexando noticias y menciones a marcas, organizaciones y personas en &lt;a href="https://dolemlabs.com.ar"&gt;Dolem Labs&lt;/a&gt;, etc.; decidí romper con el paradigma que tenía casi impreso en mi hardware, a pesar de haber realizado las tareas con mi framework preferido, decidí darle una oportunidad a otros métodos, otros lenguajes incluso.&lt;/p&gt;

&lt;p&gt;Ahora los crawlers corren sobre Python y sin un framework de guía, al menos no un framework de verdad, organizo mis propios modelos de datos cuando lo necesito, corro queries costosas que hacen "cosas locas" cuando lo necesito, analizando miles de páginas web por día y una vez más rompiendo mis estándares, no en un solo lenguaje, no todo bajo la tutela de un framework.&lt;/p&gt;

&lt;p&gt;Y si, los scripts python tocan la base de datos, insertan miles de registros, actualizan otros cientos e indexan información para que a Rails le resulte más sencillo hacer su trabajo.&lt;/p&gt;

&lt;p&gt;Y si, las cosas van mejor, no quiero entrar en el tema de Monolito vs Micro Servicios, ya está muy gastado. Vas a ver que al cargar un script Python (no porque sea Python, es porque no hay un framework) para realizar la tarea de scrapeo, sin necesidad de cargar todo un framework para una simple tarea, todo fluyó mucho más rápido, con mayor performance a la hora de hacer SQL queries libremente. También para el front fue todo más fácil y barato, ya que algunos scripts "pre mastican" la información y la dejan lista para consumir.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;¿Hay que tener cuidado?&lt;/strong&gt;, si, hay que tener cuidado, hay que conocer el framework como para no corromper datos y que después todo explote cuando se lo quiera consumir.&lt;/p&gt;

&lt;p&gt;Ahora tengo tablas de las que Ruby on Rails no está enterado de que existen, tablas que se utilizan para hacer procesos costosos de indexación, ROR no necesita saber de eso, y ya no me siento culpable :) &lt;/p&gt;

&lt;p&gt;En fin, no estoy diciendo que hagan cochinadas, estoy diciendo que al igual que Scrum es un marco de trabajo en el cual debemos basarnos para hacer un desarrollo de manera ágil, no es algo que nos limite para nada, las ceremonias que tiene (muchas para mi gusto) no suelen ser obligatorias, uno va tomando lo que necesita para que el proyecto fluya.&lt;/p&gt;

&lt;p&gt;Volviendo al código, hay cosas que pueden ser mucho más performantes en otros lenguajes y/o frameworks que el que estás usando de base, vale la pena evaluarlo y sacarse la duda.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;¿Es siempre recomendable hacer esto?&lt;/strong&gt;&lt;br&gt;
No, en general es recomendable atenerse a las buenas prácticas del lenguaje/framework que estés usando, más si es una startup en sus primeros pasos. Puede ser muy costoso mantener un proyecto con diferentes lenguajes y frameworks, sin mencionar el conocimiento de la lógica de ejecución de los mismos, y más aún, si hay scripts corriendo en diferentes servidores, puede llegar a ser un dolor de cabeza.&lt;/p&gt;

&lt;p&gt;Y como para cerrar, experimenten, no se cierren a una sola manera de hacer las cosas.&lt;/p&gt;

&lt;p&gt;Un saludo.&lt;/p&gt;

&lt;p&gt;Paul.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>python</category>
      <category>development</category>
    </item>
    <item>
      <title>El Poder del Análisis de Noticias: Perspectivas, Desafíos y Aplicaciones Futuras</title>
      <dc:creator>Paul Marclay</dc:creator>
      <pubDate>Fri, 19 Apr 2024 14:51:10 +0000</pubDate>
      <link>https://dev.to/paulmarclay/el-poder-del-analisis-de-noticias-perspectivas-desafios-y-aplicaciones-futuras-6c1</link>
      <guid>https://dev.to/paulmarclay/el-poder-del-analisis-de-noticias-perspectivas-desafios-y-aplicaciones-futuras-6c1</guid>
      <description>&lt;p&gt;Como desarrollador y entusiasta de la investigación, he creado un framework de análisis de noticias que utiliza técnicas de inteligencia artificial para revolucionar la forma en que procesamos y extraemos conocimiento de la abrumadora cantidad de información que nos rodea. &lt;/p&gt;

&lt;p&gt;Este framework, es capaz de realizar una amplia gama de funcionalidades que van desde la extracción de noticias hasta el análisis de sentimiento y reconocimiento de personas en imágenes.&lt;/p&gt;

&lt;p&gt;El framework fue desarrollado en Python y utiliza técnicas de procesamiento de lenguaje natural (NLP), visión artificial (CV) y aprendizaje automático (ML) para su análisis.&lt;/p&gt;

&lt;p&gt;En este artículo, quiero compartir con ustedes los detalles y las capacidades del framework. &lt;br&gt;
A través de una arquitectura modular y técnicas avanzadas de procesamiento de lenguaje natural, he creado una solución integral que permite aprovechar el potencial oculto en el flujo informativo.&lt;/p&gt;

&lt;p&gt;Características clave del framework de análisis de noticias&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Extracción de noticias:&lt;/strong&gt; Puede obtener el contenido de una noticia a partir de su URL, permitiendo el análisis de una amplia variedad de fuentes de información.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generación de hashtags y palabras clave:&lt;/strong&gt; Utiliza técnicas de procesamiento de lenguaje natural para generar hashtags y palabras clave relevantes que resumen el contenido de la noticia.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reconocimiento de entidades:&lt;/strong&gt; Es capaz de identificar y clasificar diferentes tipos de entidades mencionadas en la noticia, como lugares, empresas, nombres propios y sustantivos.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resumen automático:&lt;/strong&gt; Genera un resumen conciso del contenido de la noticia, destacando los puntos más relevantes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Análisis de sentimiento:&lt;/strong&gt; Realiza un análisis de sentimiento del texto de la noticia, determinando si el tono es positivo, negativo o neutral.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Análisis de imágenes:&lt;/strong&gt; Mediante el uso de técnicas de visión artificial, el framework puede identificar a las personas que aparecen en las imágenes de las noticias, incluyendo personalidades reconocidas a nivel local y global, contando con una base de datos de más de 300 personas.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Análisis del estado de ánimo:&lt;/strong&gt; Además del reconocimiento de personas, el framework también es capaz de analizar el estado de ánimo reflejado en los rostros de los individuos presentes en las imágenes.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Estas características clave demuestran la versatilidad y la potencia del mismo, el resultado de mucha  dedicación, esfuerzo y un enfoque innovador.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Posibles aplicaciones y casos de uso&lt;/strong&gt;&lt;br&gt;
El framework de análisis de noticias abre un abanico de posibilidades para diversas industrias y áreas de aplicación. &lt;br&gt;
Algunas de las aplicaciones más destacadas podrían incluir:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Monitoreo y análisis de menciones de marcas, empresas y personalidades:&lt;/strong&gt; Las empresas pueden utilizar el framework para rastrear las menciones de sus marcas, productos o ejecutivos en los medios de comunicación, evaluar el sentimiento asociado y obtener insights sobre su reputación y el impacto de sus acciones.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Informes estadísticos personalizados:&lt;/strong&gt; Los clientes podrían solicitar informes personalizados que analicen un conjunto de noticias relevantes para sus intereses, obteniendo estadísticas y visualizaciones sobre tendencias, patrones y características clave.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Análisis de eventos y noticias de actualidad:&lt;/strong&gt; Periodistas, analistas y expertos podrían utilizar el framework para profundizar en el análisis de eventos importantes, evaluando el sentimiento y la percepción de los diferentes actores involucrados.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Como conclusión, el framework de análisis de noticias representa un ejemplo rebuscado de la intersección entre la innovación tecnológica y la investigación analítica. A través de la integración de técnicas de inteligencia artificial y procesamiento de lenguaje natural, he logrado una solución integral que permite extraer conocimiento valioso de un mar de información.&lt;/p&gt;

&lt;p&gt;Las capacidades del framework, que van desde la generación de hashtags y palabras clave hasta el análisis de sentimiento y reconocimiento de personas, fueron un desafío para aprender tecnologías nuevas, salir de mi zona de confort y tomar nuevos retos.&lt;/p&gt;

&lt;p&gt;A medida que el mundo se sumerge cada vez más en un flujo incesante de noticias, estoy seguro de que soluciones de este tipo se pueden convertir en herramientas indispensables para aquellos que buscan extraer insights significativos y tomar decisiones informadas.&lt;/p&gt;

&lt;p&gt;El framework podría presentar un paso adelante en la evolución del análisis de noticias, y sin duda puede abrir nuevas posibilidades para mí y para aquellos que adopten esta innovadora solución.&lt;/p&gt;

&lt;p&gt;Si tenés un perfil más técnico puede interesarte la respuesta que da nuestra API:&lt;/p&gt;

&lt;p&gt;Para la noticia: &lt;a href="https://www.infobae.com/politica/2024/04/19/sergio-massa-anuncio-que-ira-a-la-marcha-de-la-semana-proxima-en-defensa-de-la-universidad-publica/"&gt;Sergio Massa anunció que irá a la marcha de la semana próxima en defensa de la universidad pública&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Esta es su imagen de portada:&lt;br&gt;
&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frhhoh698w04mkzbfz5yg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frhhoh698w04mkzbfz5yg.png" alt="Image description" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Y esta es la respuesta con toda la información recolectada:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"news"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Sergio Massa anunció que irá a la marcha de la semana próxima en defensa de la universidad pública"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"body"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Sergio Massa compartió el spot sobre la UBAEl excandidato presidencial Sergio Massa participará el próximo martes de la jornada en defensa de la universidad pública que se realizará en la Plaza de Mayo de la Ciudad de Buenos Aires.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;“Creen que estamos dormidos, que miramos para otro lado; que las diferencias nos debilitan.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;Hasta ahora, tras el balotaje presidencial del pasado 19 de noviembre, en el que se impuso Javier Milei, Sergio Massa prácticamente no tuvo apariciones públicas.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;El mes pasado participó del Congreso del Frente Renovador, en el que se eligieron a las nuevas autoridades del espacio, en el que dejó algunas definiciones políticas.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;La concentración comenzará a las 15:30 horas, mientras que el acto central será a las 18 horas, frente a la Casa Rosada."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"image_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://www.infobae.com/new-resizer/bbcPQipJWDhG3-mxXNj9BTIlkCs=/1200x630/filters:format(webp):quality(85)/cloudfront-us-east-1.images.arcpublishing.com/infobae/ZG2RTJS6U5C4TLIHY5MSJ7WMYU.jpg"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://www.infobae.com/politica/2024/04/19/sergio-massa-anuncio-que-ira-a-la-marcha-de-la-semana-proxima-en-defensa-de-la-universidad-publica/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2024-04-19T00:00:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"General"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"author"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"mood"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"NEG"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"people"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Sergio Massa"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"emotion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Neutral"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"bounding_box"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="mi"&gt;172&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="mi"&gt;541&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="mi"&gt;433&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="mi"&gt;280&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"keywords"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PER"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Sergio Massa"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ORG"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"UBA"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PER"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Sergio Massa"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"LOC"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Plaza de Mayo de la Ciudad de Buenos Aires"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ORG"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Instagram"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ORG"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Universidad de Buenos Aires"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"MISC"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"#YoVoy"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PER"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Javier Milei"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PER"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Sergio Massa"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ORG"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Congreso del Frente Renovador"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PER"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Milei"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"LOC"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Argentina"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PER"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Massa"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ORG"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Universidad Pública"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ORG"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Gobierno"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ORG"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Ejecutivo"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ORG"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Universidades"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ORG"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Gobierno"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ORG"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ministerio de Capital Humano"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ORG"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"UBA"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PER"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Emiliano Yacobitti"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ORG"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Gobierno"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ORG"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Gobierno"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ORG"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"A24"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ORG"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Consejo Interuniversitario Nacional"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PER"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Sandra Petovello"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ORG"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Gobierno Nacional"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ORG"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Universidades Nacionales"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ORG"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Congreso"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"LOC"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Plaza de Mayo"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"LOC"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Casa Rosada"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cualquier duda, crítica o sugerencia, será bienvenida. &lt;/p&gt;

&lt;p&gt;Gracias!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Service Objects in Ruby on Rails: Best Practices for Clean and Maintainable Code</title>
      <dc:creator>Paul Marclay</dc:creator>
      <pubDate>Wed, 19 Apr 2023 22:07:00 +0000</pubDate>
      <link>https://dev.to/paulmarclay/service-objects-in-ruby-on-rails-best-practices-for-clean-and-maintainable-code-2odk</link>
      <guid>https://dev.to/paulmarclay/service-objects-in-ruby-on-rails-best-practices-for-clean-and-maintainable-code-2odk</guid>
      <description>&lt;p&gt;Many times we need to communicate our ROR application with the outside world, either to consume a service that will provide us with information of some type or to send information ourselves.&lt;/p&gt;

&lt;p&gt;And we can do this in two ways,&lt;br&gt;
&lt;strong&gt;The caveman's way&lt;/strong&gt;, which is simple using HTTParty and including a method to contact the API within our controller or model. This will work without any problems, it's fast, and perhaps easy to maintain if it's only your project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Ruby on Rails way&lt;/strong&gt;, in this way, we will create a directory for services in our project called "services", inside the "app" directory, where we will put all the external services or APIs that our project consumes.&lt;/p&gt;

&lt;p&gt;Taking advantage of the fact that it is fashionable, we are going to connect with Chat GPT.&lt;/p&gt;

&lt;p&gt;Ok, let's get to work!&lt;/p&gt;
&lt;h3&gt;
  
  
  Install HTTParty
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;gem 'httparty'&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;We're going to use the &lt;a href="https://rubygems.org/gems/httparty/versions/0.13.7?locale=es"&gt;HTTParty&lt;/a&gt; gem to perform all our HTTP requests.&lt;br&gt;
To install it just add the above piece of code into your &lt;code&gt;Gemfile&lt;/code&gt; and then run &lt;code&gt;bundle install&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Service configuration
&lt;/h3&gt;

&lt;p&gt;To communicate con Chat GPT we need to pass an API KEY or TOKEN in our request headers, and to keep it secure, we will save it into the Rails credentials file.&lt;br&gt;
You can edit the credentials file by running this command in your terminal&lt;/p&gt;

&lt;p&gt;&lt;code&gt;EDITOR="nano" bin/rails credentials:edit&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;That will open your credentials file using "nano" as text editor.&lt;br&gt;
Once you are editing it, add the following configuration line&lt;/p&gt;

&lt;p&gt;&lt;code&gt;chatgpt_api_key: YOUR-API-KEY&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Please save and close the editor, if you're using nano, &lt;code&gt;Ctrl + x&lt;/code&gt; will do the trick.&lt;/p&gt;
&lt;h3&gt;
  
  
  Starting making the service
&lt;/h3&gt;

&lt;p&gt;We can treat all our services as modules, see the following structure&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;➜  chatgpt_service git:(master)  tree app/services
app/services
├── chat_gpt
│   ├── data
│   │   ├── chat_completions_choice_item_data.rb
│   │   ├── completions_choice_item_data.rb
│   │   ├── models_item_data.rb
│   │   └── models_list_data.rb
│   ├── responses
│   │   ├── base_response.rb
│   │   ├── chat_completions_response.rb
│   │   ├── completions_response.rb
│   │   └── models_list_response.rb
│   └── service.rb
└── http_response.rb

4 directories, 10 files

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The idea is to have a module to encapsulate the API we want to consume, and inside it the &lt;code&gt;Service&lt;/code&gt; class with all methods to request or send information.&lt;br&gt;
Inside responses we will store our classes which will be in charge to manage the information returned by the API.&lt;br&gt;
In case the API returns complex data, or many objects inside each other, you could use some data classes, the ones you can see inside the &lt;code&gt;data&lt;/code&gt; directory.&lt;/p&gt;

&lt;p&gt;Don't be worried about the code, since I prepared a public Github repository with the project, take a look at it at: &lt;a href="https://github.com/paul-ot/ror_chatgpt_client"&gt;https://github.com/paul-ot/ror_chatgpt_client&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To explain the above service structure we could say that the service will have one public method for each endpoint we will use, for example in our case we have three public methods in the service,&lt;br&gt;
&lt;code&gt;completions&lt;/code&gt;, &lt;code&gt;chat_completions&lt;/code&gt; and &lt;code&gt;models_list&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;ChatGpt&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Service&lt;/span&gt;
    &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;HTTParty&lt;/span&gt;
    &lt;span class="n"&gt;base_uri&lt;/span&gt; &lt;span class="s1"&gt;'https://api.openai.com/v1'&lt;/span&gt;

    &lt;span class="c1"&gt;# ...&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="vi"&gt;@model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;nil?&lt;/span&gt;

      &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="no"&gt;COMPLETIONS_PATH&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;body: &lt;/span&gt;&lt;span class="n"&gt;completions_body&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="ss"&gt;headers: &lt;/span&gt;&lt;span class="n"&gt;common_headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;timeout: &lt;/span&gt;&lt;span class="no"&gt;TIMEOUT_SECONDS&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="no"&gt;ChatGpt&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Responses&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;CompletionsResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;chat_completions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="vi"&gt;@model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;nil?&lt;/span&gt;

      &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="no"&gt;COMPLETIONS_PATH&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;body: &lt;/span&gt;&lt;span class="n"&gt;chat_completions_body&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="ss"&gt;headers: &lt;/span&gt;&lt;span class="n"&gt;common_headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;timeout: &lt;/span&gt;&lt;span class="no"&gt;TIMEOUT_SECONDS&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="no"&gt;ChatGpt&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Responses&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ChatCompletionsResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;models_list&lt;/span&gt;
      &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="no"&gt;MODELS_PATH&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;headers: &lt;/span&gt;&lt;span class="n"&gt;common_headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;timeout: &lt;/span&gt;&lt;span class="no"&gt;TIMEOUT_SECONDS&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="no"&gt;ChatGpt&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Responses&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ModelsListResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="c1"&gt;# ...&lt;/span&gt;

  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each method will return its own response, it means that each method will return a different response, a different class, stored inside the &lt;code&gt;responses&lt;/code&gt; directory, we do that on that way in order to isolate the responses, creating different methods to get the information from the JSON responses.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pay attention to the responses&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Completions method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="c1"&gt;# ...&lt;/span&gt;

      &lt;span class="no"&gt;ChatGpt&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Responses&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;CompletionsResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Chat completions method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;chat_completions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="c1"&gt;# ...&lt;/span&gt;

      &lt;span class="no"&gt;ChatGpt&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Responses&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ChatCompletionsResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The models list:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;models_list&lt;/span&gt;
      &lt;span class="c1"&gt;# ...&lt;/span&gt;

      &lt;span class="no"&gt;ChatGpt&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Responses&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ModelsListResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here each response class inherits from a &lt;code&gt;ChatGpt::Responses::BaseResponse&lt;/code&gt; class which contains the common methods across responses, the error message.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;BaseResponse&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;ChatGpt&lt;/span&gt;
  &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Responses&lt;/span&gt;
    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BaseResponse&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;HttpResponse&lt;/span&gt;
      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;error_message&lt;/span&gt;
        &lt;span class="n"&gt;response_body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'error'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'message'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At the same time, &lt;code&gt;ChatGpt::Responses::BaseResponse&lt;/code&gt; inherits from &lt;code&gt;HttpResponse&lt;/code&gt; class, which contains the very common methods, for example the one in charge of determines whether response was ok or not, and also converts the response into JSON object.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;HttpResponse&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HttpResponse&lt;/span&gt;
  &lt;span class="nb"&gt;attr_reader&lt;/span&gt; &lt;span class="ss"&gt;:response&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;response_body&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;present?&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="no"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
  &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;JSON&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ParserError&lt;/span&gt;
    &lt;span class="p"&gt;{}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;successful?&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;code&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_i&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;Rack&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Utils&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;SYMBOL_TO_STATUS_CODE&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;failed?&lt;/span&gt;
    &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;successful?&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I did that on that way to make it more reusable, so you can have several services each with their own responses, inheriting from this &lt;code&gt;HttpResponse&lt;/code&gt; class.&lt;/p&gt;

&lt;p&gt;About the usage of this service, it is very simple&lt;/p&gt;

&lt;p&gt;1- Create a service instance&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;ChatGpt&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;

&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="c1"&gt;#&amp;lt;ChatGpt::Service:0x00007fccde2ed0d0 @model="gpt-3.5-turbo"&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;2- You can try to get the GPT models list as follows&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;models_list&lt;/span&gt;

&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="c1"&gt;#&amp;lt;ChatGpt::Responses::ModelsListResponse:0x00007fccdd7d6390 @response=#&amp;lt;HTTParty::Response:0x7fccdd6c7ad0 parsed_response={"object"=&amp;gt;"list", "data"=&amp;gt;[{"id"=&amp;gt;"babbage", "object"=&amp;gt;"model", "created"=&amp;gt;1649358449, "owned_by"=&amp;gt;"openai", "permission"=&amp;gt;[{"id"=&amp;gt;"modelperm-49FUp5v084tBB49tC4z8LPH5", ...&amp;gt;&lt;/span&gt;

&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;64&lt;/span&gt;

&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="c1"&gt;#&amp;lt;ChatGpt::Data::ModelsItemData:0x00007fccde9cfa60 @params={"id"=&amp;gt;"babbage", "object"=&amp;gt;"model", "created"=&amp;gt;1649358449, "owned_by"=&amp;gt;"openai", "permission"=&amp;gt;[{"id"=&amp;gt;"modelperm-49FUp5v084tBB49tC4z8LPH5", "object"=&amp;gt;"model_permission", "created"=&amp;gt;1669085501, "allow_create_engine"=&amp;gt;false, "allow_sampling"=&amp;gt;true, "allow_logprobs"=&amp;gt;true, "allow_search_indices"=&amp;gt;false, "allow_view"=&amp;gt;true, "allow_fine_tuning"=&amp;gt;false, "organization"=&amp;gt;"*", "group"=&amp;gt;nil, "is_blocking"=&amp;gt;false}], "root"=&amp;gt;"babbage", "parent"=&amp;gt;nil}&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For more examples please refer to the Github repository, it has a README.md file with them.&lt;/p&gt;

&lt;p&gt;I hope I have contributed something useful, and I hope I have been able to explain my point of view about the use of services in Ruby on Rails.&lt;/p&gt;

&lt;p&gt;Thanks for reading!&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>services</category>
      <category>ror</category>
      <category>api</category>
    </item>
    <item>
      <title>Ruby - With indifferent access</title>
      <dc:creator>Paul Marclay</dc:creator>
      <pubDate>Thu, 13 Apr 2023 13:10:37 +0000</pubDate>
      <link>https://dev.to/paulmarclay/ruby-with-indifferent-access-1k7e</link>
      <guid>https://dev.to/paulmarclay/ruby-with-indifferent-access-1k7e</guid>
      <description>&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flk43ss44vxqhvoxz78ea.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flk43ss44vxqhvoxz78ea.jpeg" alt="Image description" width="300" height="168"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You know that when you deal with external APIs you may encounter anything, even the Frankenstein's monster as a response... (even when they tell you they will response only an inoffensive JSON)&lt;/p&gt;

&lt;p&gt;I mean, in the response you could get a hash with some keys at different depths and perhaps in some cases you could have keys as strings or as symbols.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff9qbn714oxdk5i3yungf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff9qbn714oxdk5i3yungf.png" alt="Image description" width="800" height="326"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So you will be alternating among them in your code, something like&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6wy7dxglnxvt4gzqwc28.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6wy7dxglnxvt4gzqwc28.png" alt="Image description" width="800" height="260"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In other case, let's suppose you try access to the user name just using symbols or strings you will receive a nil response, let's check&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxn7op1sapileja8czxz0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxn7op1sapileja8czxz0.png" alt="Image description" width="800" height="326"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Don't say I didn't tell you, we couldn't access the information, fortunately, here is when ".with_indifferent_access" method comes to the rescue!&lt;br&gt;
It is a method of ActiveSupport::HashWithIndifferentAccess class.&lt;br&gt;
And let's check how can we use it modifying the above example&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl1sfu3tpwh641008sbb4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl1sfu3tpwh641008sbb4.png" alt="Image description" width="800" height="326"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Or you could just use it without dig&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj72nhihk7ndxqngtsbd9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj72nhihk7ndxqngtsbd9.png" alt="Image description" width="800" height="326"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And that's all Ruby mates,&lt;br&gt;
Thanks for reading!&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>ror</category>
    </item>
  </channel>
</rss>
