window.pipedriveLeadboosterConfig = { base: 'leadbooster-chat.pipedrive.com', companyId: 11580370, playbookUuid: '22236db1-6d50-40c4-b48f-8b11262155be', versión: 2, } ;(function () { var w = window if (w.LeadBooster) { console.warn('LeadBooster ya existe') } else { w.LeadBooster = { q: [], on: function (n, h) { this.q.push({ t: 'o', n: n, h: h }) }, trigger: function (n) { this.q.push({ t: 't', n: n }) }, } } })() Optimización de código con objetos de consulta - The Codest
The Codest
  • Quiénes somos
  • Servicios
    • Desarrollo de software
      • Desarrollo Frontend
      • Desarrollo backend
    • Staff Augmentation
      • Desarrolladores frontales
      • Desarrolladores de backend
      • Ingenieros de datos
      • Ingenieros de la nube
      • Ingenieros de control de calidad
      • Otros
    • Asesoramiento
      • Auditoría y consultoría
  • Industrias
    • Fintech y Banca
    • E-commerce
    • Adtech
    • Tecnología sanitaria
    • Fabricación
    • Logística
    • Automoción
    • IOT
  • Valor para
    • CEO
    • CTO
    • Gestor de entregas
  • Nuestro equipo
  • Case Studies
  • Saber cómo
    • Blog
    • Meetups
    • Seminarios en línea
    • Recursos
Carreras profesionales Póngase en contacto
  • Quiénes somos
  • Servicios
    • Desarrollo de software
      • Desarrollo Frontend
      • Desarrollo backend
    • Staff Augmentation
      • Desarrolladores frontales
      • Desarrolladores de backend
      • Ingenieros de datos
      • Ingenieros de la nube
      • Ingenieros de control de calidad
      • Otros
    • Asesoramiento
      • Auditoría y consultoría
  • Valor para
    • CEO
    • CTO
    • Gestor de entregas
  • Nuestro equipo
  • Case Studies
  • Saber cómo
    • Blog
    • Meetups
    • Seminarios en línea
    • Recursos
Carreras profesionales Póngase en contacto
Flecha atrás VOLVER
2019-03-08
Desarrollo de software

Optimización del código con objetos de consulta

The Codest

Tomasz Szkaradek

Arquitecto de desarrollo

Es bastante probable que en el trabajo te hayas encontrado muchas veces con modelos sobrecargados y un gran número de llamadas en los controladores. Basándome en el conocimiento en el entorno Rails, en este artículo, voy a proponer una solución sencilla a este problema.

Un aspecto muy importante de la aplicación Rails es minimizar el número de dependencias redundantes, razón por la cual todo el entorno Rails ha estado promoviendo recientemente el enfoque de objetos de servicio y el uso del método PORO (Pure Old Ruby Object). Encontrarás una descripción de cómo utilizar una solución de este tipo en aquí. En este artículo, resolveremos el concepto paso a paso y lo adaptaremos al problema.

Problema

En una aplicación hipotética, estamos tratando con un complicado sistema de transacciones. Nuestro modelo, que representa cada transacción, tiene un conjunto de ámbitos, que le ayudan a obtener datos. Es una gran facilitación del trabajo, ya que se puede encontrar en un solo lugar. Sin embargo, esto no dura mucho tiempo. Con el desarrollo de la aplicación, el proyecto es cada vez más complicado. Los ámbitos ya no tienen simples referencias "dónde", nos faltan datos y empezamos a cargar relaciones. Después de un tiempo, recuerda a un complicado sistema de espejos. Y, lo que es peor, ¡no sabemos cómo hacer una lambda multilínea!

A continuación, encontrará un modelo de aplicación ya ampliado. En él se almacenan las transacciones del sistema de pago. Como se puede ver en el siguiente ejemplo:

clase Transacción  { where(visible: true) }

  scope(:active, lambda do
    joins(<<-SQL
      LEFT OUTER JOIN origen ON transacciones.origen_id = origen.id
      AND source.accepted_at IS NOT NULL
    SQL
  end)
fin

El modelo es una cosa, pero a medida que aumenta la escala de nuestro proyecto, los controladores también empiezan a hincharse. Veamos el ejemplo siguiente:

clase TransactionsController < ApplicationController
  def index
    @transacciones = Transacción.para_editores
                                   .activo
                                   .visible
                                   .joins("LEFT JOIN withdrawal_items ON withdrawal_items.transaction_id = transactions.id")
                                   .joins("LEFT JOIN retiradas ON retiradas.id = retiradas_items.retiradas_id OR
 (retiradas.id = fuente.resource_id AND fuente.resource_type = 'Retirada')")
                                   .order(:fecha_creada)
                                   .page(parámetros[:página])
                                   .per(parámetros[:página])
    @transacciones = apply_filters(@transacciones)
  fin
fin

Aquí podemos ver muchas líneas de métodos encadenados junto con joins adicionales que no queremos realizar en muchos sitios, sólo en este en particular. Los datos adjuntos son utilizados posteriormente por el método apply_filters, que añade el filtrado de datos apropiado, basado en los parámetros GET. Por supuesto, podemos transferir algunas de estas referencias al ámbito, pero ¿no es este el problema que en realidad estamos tratando de resolver?

Solución

Como ya sabemos que tenemos un problema, debemos resolverlo. Basándonos en la referencia de la introducción, utilizaremos aquí el enfoque PORO. En este caso concreto, este enfoque se denomina objeto de consulta, que es un desarrollo del concepto de objetos de servicio.

Vamos a crear un nuevo directorio llamado "services", situado en el directorio apps de nuestro proyecto. Allí crearemos una clase llamada TransactionsQuery.

clase TransactionsQuery
fin

Como siguiente paso, necesitamos crear un inicializador donde se creará una ruta de llamada por defecto para nuestro objeto

clase TransactionsQuery
  def initialize(ámbito = Transacción.todo)
    @ámbito = ámbito
  end
end

Gracias a esto, podremos transferir la relación del registro activo a nuestra instalación. Ahora podemos transferir todos nuestros ámbitos a la clase, que sólo se necesitan en el controlador presentado.

clase TransactionsQuery
  def initialize(ámbito = Transacción.todo)
    @ámbito = ámbito
  end

  privado

  def activo(ámbito)
    scope.joins(<<-SQL
      LEFT OUTER JOIN origen ON transacciones.origen_id = origen.id
      AND source.accepted_at IS NOT NULL
    SQL
  end

  def visible(ámbito)
    scope.where(visible: true)
  fin

  def para_editores(ámbito)
    scope.select("transacciones.*")
         .joins(:cuenta)
         .where("cuentas.tipo_propietario = 'Editor'")
         .joins("JOIN publishers ON owner_id = publishers.id")
  fin
fin

Aún nos falta la parte más importante, es decir, reunir los datos en una cadena y hacer pública la interfaz. El método donde pegaremos todo se llamará "llamada".

Lo realmente importante es que allí utilizaremos la variable de instancia @scope, donde se encuentra el ámbito de nuestra llamada.

clase TransactionsQuery
  ...
  def call
    visible(@ámbito)
        .then(&method(:active))
        .then(&method(:for_publishers))
        .order(:fecha_creación)
  end

  privado
  ...
end

Toda la clase se presenta como sigue:

clase TransactionsQuery
  def initialize(ámbito = Transacción.todo)
    @ámbito = ámbito
  end

  def call
    visible(@ámbito)
        .then(&method(:active))
        .then(&method(:for_publishers))
        .order(:fecha_creación)
  end

  privado

  def activo(ámbito)
    scope.joins(<<-SQL
      LEFT OUTER JOIN origen ON transacciones.origen_id = origen.id
      AND source.accepted_at IS NOT NULL
    SQL
  end

  def visible(ámbito)
    scope.where(visible: true)
  fin

  def para_editores(ámbito)
    scope.select("transacciones.*")
         .joins(:cuenta)
         .where("cuentas.tipo_propietario = 'Editor'")
         .joins("JOIN publishers ON owner_id = publishers.id")
  fin
fin

Tras nuestra limpieza, el modelo parece definitivamente más ligero. Ahí nos centramos únicamente en la validación de los datos y las relaciones entre otros modelos.

clase Transacción < ActiveRecord::Base
  belongs_to :cuenta
  has_one :elemento_retirada
end

El controlador ya ha implementado nuestra solución; hemos movido todas las consultas adicionales a una clase separada. Sin embargo, las llamadas, que no teníamos en el modelo, siguen siendo un problema sin resolver. Después de algunos cambios, nuestra acción de índice se ve así:

clase TransactionsController < ApplicationController
  def índice
    @transacciones = TransactionsQuery.new
                                     .call
                                     .joins("LEFT JOIN withdrawal_items ON withdrawal_items.accounting_event_id = transactions.id")
                                     .joins("LEFT JOIN retiradas ON retiradas.id = retiradas_items.retiradas_id OR
 (retiradas.id = fuente.resource_id AND fuente.resource_type = 'Retirada')")
                                     .order(:fecha_creada)
                                     .page(parámetros[:página])
                                     .per(parámetros[:página])
    @transacciones = apply_filters(@transacciones)
  fin
fin

Solución

En el caso de aplicar buenas prácticas y convenciones, una buena idea puede ser sustituir todas las apariciones similares de un problema determinado. Por lo tanto, trasladaremos la consulta SQL de la acción de índice al objeto de consulta independiente. Lo llamaremos TransactionsFilterableQuery clase. El estilo con el que prepararemos la clase será similar al presentado en TransactionsQuery. Como parte del código cambios, se introducirá de contrabando un registro más intuitivo de las consultas SQL de gran tamaño, utilizando cadenas de caracteres de varias líneas denominadas heredoc. La solución disponible la encontrará a continuación:

clase TransactionsFilterableQuery
  def initialize(ámbito = Transacción.todo)
    @ámbito = ámbito
  end

  def call
    retirada(@ámbito).then(&método(:elementos_retirada))
  end

  privado

  def retirada(ámbito)
    scope.joins(<<-SQL
      LEFT JOIN retiradas ON retiradas.id = retiradas_items.withdrawal_id OR
      (retiradas.id = fuente.resource_id AND fuente.resource_type = 'Retirada')
    SQL
  fin

  def elementos_retirada(ámbito)
    scope.joins(<<-SQL
      LEFT JOIN artículos_retirada ON artículos_retirada.accounting_event_id = transacciones.id
    SQL
  end
fin

En caso de cambios en el controlador, reducimos la masa de líneas añadiendo el objeto query. Es importante que separemos todo excepto la parte responsable de la paginación.

clase TransactionsController < ApplicationController
  def index
    @transactions = TransactionsQuery.new.call.then do |ámbito|
      TransactionsFilterableQuery.new(ámbito).call
    end.page(params[:page]).per(params[:page])

    @transacciones = aplicar_filtros(@transacciones)
  end
end

Resumen

El objeto de consulta cambia mucho el enfoque a la hora de escribir consultas SQL. En ActiveRecord, es muy fácil colocar toda la lógica de negocio y de base de datos en el modelo, ya que todo está en un solo lugar. Esto funcionará bastante bien para aplicaciones pequeñas. A medida que aumenta la complejidad del proyecto, colocamos la lógica en otros lugares. El mismo objeto de consulta permite agrupar las consultas de los miembros en un problema específico.

Gracias a esto, tenemos una fácil posibilidad de la herencia posterior del código y debido a duck typing, también puede utilizar estas soluciones en otros modelos. La desventaja de esta solución es una mayor cantidad de código y la fragmentación de la responsabilidad. Sin embargo, si queremos asumir tal reto o no, depende de nosotros y de lo mucho que nos molesten los modelos gordos.

Artículos relacionados

Desarrollo de software

Crear aplicaciones web preparadas para el futuro: ideas del equipo de expertos de The Codest

Descubra cómo The Codest destaca en la creación de aplicaciones web escalables e interactivas con tecnologías de vanguardia, ofreciendo experiencias de usuario fluidas en todas las plataformas. Descubra cómo nuestra experiencia impulsa la transformación...

EL MEJOR
Desarrollo de software

Las 10 mejores empresas de desarrollo de software de Letonia

Conozca las principales empresas de desarrollo de software de Letonia y sus innovadoras soluciones en nuestro último artículo. Descubra cómo estos líderes tecnológicos pueden ayudarle a mejorar su negocio.

thecodest
Soluciones para empresas y escalas

Fundamentos del desarrollo de software Java: Guía para externalizar con éxito

Explore esta guía esencial sobre el desarrollo de software Java outsourcing con éxito para mejorar la eficiencia, acceder a la experiencia e impulsar el éxito de los proyectos con The Codest.

thecodest
Desarrollo de software

La guía definitiva para subcontratar en Polonia

El auge de las outsourcing en Polonia está impulsado por los avances económicos, educativos y tecnológicos, que fomentan el crecimiento de las TI y un clima favorable a las empresas.

TheCodest
Soluciones para empresas y escalas

Guía completa de herramientas y técnicas de auditoría informática

Las auditorías informáticas garantizan sistemas seguros, eficientes y conformes. Obtenga más información sobre su importancia leyendo el artículo completo.

The Codest
Jakub Jakubowicz CTO y Cofundador

Suscríbase a nuestra base de conocimientos y manténgase al día de la experiencia del sector informático.

    Quiénes somos

    The Codest - Empresa internacional de desarrollo de software con centros tecnológicos en Polonia.

    Reino Unido - Sede central

    • Oficina 303B, 182-184 High Street North E6 2JA
      Londres, Inglaterra

    Polonia - Centros tecnológicos locales

    • Parque de oficinas Fabryczna, Aleja
      Pokoju 18, 31-564 Cracovia
    • Embajada del Cerebro, Konstruktorska
      11, 02-673 Varsovia, Polonia

      The Codest

    • Inicio
    • Quiénes somos
    • Servicios
    • Case Studies
    • Saber cómo
    • Carreras profesionales
    • Diccionario

      Servicios

    • Asesoramiento
    • Desarrollo de software
    • Desarrollo backend
    • Desarrollo Frontend
    • Staff Augmentation
    • Desarrolladores de backend
    • Ingenieros de la nube
    • Ingenieros de datos
    • Otros
    • Ingenieros de control de calidad

      Recursos

    • Hechos y mitos sobre la cooperación con un socio externo de desarrollo de software
    • De EE.UU. a Europa: ¿Por qué las startups estadounidenses deciden trasladarse a Europa?
    • Comparación de los polos de desarrollo de Tech Offshore: Tech Offshore Europa (Polonia), ASEAN (Filipinas), Eurasia (Turquía)
    • ¿Cuáles son los principales retos de los CTO y los CIO?
    • The Codest
    • The Codest
    • The Codest
    • Privacy policy
    • Condiciones de uso del sitio web

    Copyright © 2025 por The Codest. Todos los derechos reservados.

    es_ESSpanish
    en_USEnglish de_DEGerman sv_SESwedish da_DKDanish nb_NONorwegian fiFinnish fr_FRFrench pl_PLPolish arArabic it_ITItalian jaJapanese ko_KRKorean nl_NLDutch etEstonian elGreek es_ESSpanish