www.alextaya.com, ing.alextaya@outlook.com, Microsoft .Net, Visual Studio, Visual Basic, Visual C#, ASP.Net, Microsoft Office, Windows, etc.
viernes, noviembre 23, 2018
¿Qué es un Algoritmo?
Más información ==> https://m.facebook.com/story.php?story_fbid=779713015547334&id=332220556963251
viernes, noviembre 16, 2018
martes, noviembre 13, 2018
lunes, noviembre 05, 2018
sábado, noviembre 03, 2018
jueves, noviembre 01, 2018
lunes, octubre 29, 2018
sábado, octubre 27, 2018
miércoles, octubre 24, 2018
martes, octubre 23, 2018
lunes, mayo 29, 2017
Convertir Número a Letras en VB
Más de uno nos hemos roto en algún momento de nuestra vida la cabeza por querer desarrollar una rutina que nos permita convertir números a letras, es por ello que decidí compartir con todos uds. el presente artículo.
Consiste en una aplicación desarrollada en VB.Net apoyada con un módulo que recibe como parametro el valor ingresado y lo convierte en letras dependiendo si es positivo o negativo.
Consiste en una aplicación desarrollada en VB.Net apoyada con un módulo que recibe como parametro el valor ingresado y lo convierte en letras dependiendo si es positivo o negativo.
A continuación lo detallo en forma más específica:
Luego tenemos que crear el siguiente Módulo:Public Class FrmNumeroaletra Inherits System.Windows.Forms.Form '**************************************** 'Desarrollado por: Pedro Alex Taya Yactayo 'Email: alextaya@hotmail.com 'Web: http://es.geocities.com/wiseman_alextaya ' http://groups.msn.com/mugcanete '**************************************** #Region " Código generado por el Diseñador de Windows Forms " Public Sub New() MyBase.New() 'El Diseñador de Windows Forms requiere esta llamada. InitializeComponent() 'Agregar cualquier inicialización después de la llamada a InitializeComponent() End Sub 'Form reemplaza a Dispose para limpiar la lista de componentes. Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean) If disposing Then If Not (components Is Nothing) Then components.Dispose() End If End If MyBase.Dispose(disposing) End Sub 'Requerido por el Diseñador de Windows Forms Private components As System.ComponentModel.IContainer 'NOTA: el Diseñador de Windows Forms requiere el siguiente procedimiento 'Puede modificarse utilizando el Diseñador de Windows Forms. 'No lo modifique con el editor de código. Friend WithEvents Label1 As System.Windows.Forms.Label Friend WithEvents Label2 As System.Windows.Forms.Label Friend WithEvents CmdConvertir As System.Windows.Forms.Button Friend WithEvents TxtNumero As System.Windows.Forms.TextBox Friend WithEvents TxtLetra As System.Windows.Forms.TextBox Friend WithEvents Timer1 As System.Windows.Forms.Timer Friend WithEvents credito As System.Windows.Forms.Label
Private Sub InitializeComponent() Me.components = New System.ComponentModel.Container Dim resources As System.Resources.ResourceManager = New System.Resources.ResourceManager(GetType(FrmNumeroaletra)) Me.Label1 = New System.Windows.Forms.Label Me.Label2 = New System.Windows.Forms.Label Me.CmdConvertir = New System.Windows.Forms.Button Me.TxtNumero = New System.Windows.Forms.TextBox Me.TxtLetra = New System.Windows.Forms.TextBox Me.credito = New System.Windows.Forms.Label Me.Timer1 = New System.Windows.Forms.Timer(Me.components) Me.SuspendLayout() ' 'Label1 ' Me.Label1.AutoSize = True Me.Label1.Font = New System.Drawing.Font("Arial", 9.75!, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, CType(0, Byte)) Me.Label1.ForeColor = System.Drawing.Color.Navy Me.Label1.Location = New System.Drawing.Point(8, 8) Me.Label1.Name = "Label1" Me.Label1.Size = New System.Drawing.Size(38, 18) Me.Label1.TabIndex = 3 Me.Label1.Text = "Valor" ' 'Label2 ' Me.Label2.AutoSize = True Me.Label2.Font = New System.Drawing.Font("Arial", 9.75!, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, CType(0, Byte)) Me.Label2.ForeColor = System.Drawing.Color.FromArgb(CType(64, Byte), CType(0, Byte), CType(0, Byte)) Me.Label2.Location = New System.Drawing.Point(8, 40) Me.Label2.Name = "Label2" Me.Label2.Size = New System.Drawing.Size(66, 18) Me.Label2.TabIndex = 4 Me.Label2.Text = "En Letras" ' 'CmdConvertir ' Me.CmdConvertir.BackColor = System.Drawing.Color.Navy Me.CmdConvertir.Cursor = System.Windows.Forms.Cursors.Hand Me.CmdConvertir.FlatStyle = System.Windows.Forms.FlatStyle.Flat Me.CmdConvertir.ForeColor = System.Drawing.Color.White Me.CmdConvertir.Location = New System.Drawing.Point(232, 8) Me.CmdConvertir.Name = "CmdConvertir" Me.CmdConvertir.TabIndex = 1 Me.CmdConvertir.Text = "&Convertir" ' 'TxtNumero ' Me.TxtNumero.Location = New System.Drawing.Point(88, 8) Me.TxtNumero.Name = "TxtNumero" Me.TxtNumero.Size = New System.Drawing.Size(128, 20) Me.TxtNumero.TabIndex = 0 Me.TxtNumero.Text = "" ' 'TxtLetra ' Me.TxtLetra.Location = New System.Drawing.Point(88, 40) Me.TxtLetra.Name = "TxtLetra" Me.TxtLetra.ReadOnly = True Me.TxtLetra.Size = New System.Drawing.Size(456, 20) Me.TxtLetra.TabIndex = 2 Me.TxtLetra.Text = "" ' 'credito ' Me.credito.Font = New System.Drawing.Font("Microsoft Sans Serif", 6.75!, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, CType(0, Byte)) Me.credito.ForeColor = System.Drawing.Color.Green Me.credito.Location = New System.Drawing.Point(312, 64) Me.credito.Name = "credito" Me.credito.Size = New System.Drawing.Size(232, 40) Me.credito.TabIndex = 5 Me.credito.Text = "Desarrollado por: Pedro Alex Taya Yactayo alextaya@hotmail.com http:"//es.geociti" & _ "es.com/wiseman_alextaya" Me.credito.TextAlign = System.Drawing.ContentAlignment.TopRight ' 'Timer1 ' Me.Timer1.Enabled = True Me.Timer1.Interval = 500 ' 'FrmNumeroaletra ' Me.AcceptButton = Me.CmdConvertir Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13) Me.ClientSize = New System.Drawing.Size(550, 100) Me.Controls.Add(Me.credito) Me.Controls.Add(Me.TxtLetra) Me.Controls.Add(Me.TxtNumero) Me.Controls.Add(Me.CmdConvertir) Me.Controls.Add(Me.Label2) Me.Controls.Add(Me.Label1) Me.FormBorderStyle = System.Windows.Forms.FormBorderStyle.Fixed3D Me.Icon = CType(resources.GetObject("$this.Icon"), System.Drawing.Icon) Me.MaximizeBox = False Me.Name = "FrmNumeroaletra" Me.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen Me.Text = "Convertir Número a Letra" Me.ResumeLayout(False) End Sub #End Region Private Sub CmdConvertir_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles CmdConvertir.Click TxtLetra.Text = "" If IsNumeric(TxtNumero.Text) Then TxtLetra.Text = Letras(TxtNumero.Text) Else MessageBox.Show("Ingrese por favor números", "Aviso", MessageBoxButtons.OK, MessageBoxIcon.Information) End If TxtNumero.Focus() TxtNumero.SelectionStart = 0 TxtNumero.SelectionLength = TxtNumero.ToString.Length End Sub Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick Static cColor As String If cColor = "" Then credito.ForeColor = System.Drawing.Color.Blue cColor = "a" Else credito.ForeColor = System.Drawing.Color.Green cColor = "" End If End Sub End Class
Module aletras '**************************************** 'Desarrollado por: Pedro Alex Taya Yactayo 'Email: alextaya@hotmail.com 'Web: http://es.geocities.com/wiseman_alextaya ' http://groups.msn.com/mugcanete '**************************************** Public Function Letras(ByVal numero As String) As String '********Declara variables de tipo cadena************ Dim palabras, entero, dec, flag As String '********Declara variables de tipo entero*********** Dim num, x, y As Integer flag = "N" '**********Número Negativo*********** If Mid(numero, 1, 1) = "-" Then numero = Mid(numero, 2, numero.ToString.Length - 1).ToString palabras = "menos " End If '**********Si tiene ceros a la izquierda************* For x = 1 To numero.ToString.Length If Mid(numero, 1, 1) = "0" Then numero = Trim(Mid(numero, 2, numero.ToString.Length).ToString) If Trim(numero.ToString.Length) = 0 Then palabras = "" Else Exit For End If Next '*********Dividir parte entera y decimal************ For y = 1 To Len(numero) If Mid(numero, y, 1) = "." Then flag = "S" Else If flag = "N" Then entero = entero + Mid(numero, y, 1) Else dec = dec + Mid(numero, y, 1) End If End If Next y If Len(dec) = 1 Then dec = dec & "0" '**********proceso de conversión*********** flag = "N" If Val(numero) <= 999999999 Then For y = Len(entero) To 1 Step -1 num = Len(entero) - (y - 1) Select Case y Case 3, 6, 9 '**********Asigna las palabras para las centenas*********** Select Case Mid(entero, num, 1) Case "1" If Mid(entero, num + 1, 1) = "0" And Mid(entero, num + 2, 1) = "0" Then palabras = palabras & "cien " Else palabras = palabras & "ciento " End If Case "2" palabras = palabras & "doscientos " Case "3" palabras = palabras & "trescientos " Case "4" palabras = palabras & "cuatrocientos " Case "5" palabras = palabras & "quinientos " Case "6" palabras = palabras & "seiscientos " Case "7" palabras = palabras & "setecientos " Case "8" palabras = palabras & "ochocientos " Case "9" palabras = palabras & "novecientos " End Select Case 2, 5, 8 '*********Asigna las palabras para las decenas************ Select Case Mid(entero, num, 1) Case "1" If Mid(entero, num + 1, 1) = "0" Then flag = "S" palabras = palabras & "diez " End If If Mid(entero, num + 1, 1) = "1" Then flag = "S" palabras = palabras & "once " End If If Mid(entero, num + 1, 1) = "2" Then flag = "S" palabras = palabras & "doce " End If If Mid(entero, num + 1, 1) = "3" Then flag = "S" palabras = palabras & "trece " End If If Mid(entero, num + 1, 1) = "4" Then flag = "S" palabras = palabras & "catorce " End If If Mid(entero, num + 1, 1) = "5" Then flag = "S" palabras = palabras & "quince " End If If Mid(entero, num + 1, 1) > "5" Then flag = "N" palabras = palabras & "dieci" End If Case "2" If Mid(entero, num + 1, 1) = "0" Then palabras = palabras & "veinte " flag = "S" Else palabras = palabras & "veinti" flag = "N" End If Case "3" If Mid(entero, num + 1, 1) = "0" Then palabras = palabras & "treinta " flag = "S" Else palabras = palabras & "treinta y " flag = "N" End If Case "4" If Mid(entero, num + 1, 1) = "0" Then palabras = palabras & "cuarenta " flag = "S" Else palabras = palabras & "cuarenta y " flag = "N" End If Case "5" If Mid(entero, num + 1, 1) = "0" Then palabras = palabras & "cincuenta " flag = "S" Else palabras = palabras & "cincuenta y " flag = "N" End If Case "6" If Mid(entero, num + 1, 1) = "0" Then palabras = palabras & "sesenta " flag = "S" Else palabras = palabras & "sesenta y " flag = "N" End If Case "7" If Mid(entero, num + 1, 1) = "0" Then palabras = palabras & "setenta " flag = "S" Else palabras = palabras & "setenta y " flag = "N" End If Case "8" If Mid(entero, num + 1, 1) = "0" Then palabras = palabras & "ochenta " flag = "S" Else palabras = palabras & "ochenta y " flag = "N" End If Case "9" If Mid(entero, num + 1, 1) = "0" Then palabras = palabras & "noventa " flag = "S" Else palabras = palabras & "noventa y " flag = "N" End If End Select Case 1, 4, 7 '*********Asigna las palabras para las unidades********* Select Case Mid(entero, num, 1) Case "1" If flag = "N" Then If y = 1 Then palabras = palabras & "uno " Else palabras = palabras & "un " End If End If Case "2" If flag = "N" Then palabras = palabras & "dos " Case "3" If flag = "N" Then palabras = palabras & "tres " Case "4" If flag = "N" Then palabras = palabras & "cuatro " Case "5" If flag = "N" Then palabras = palabras & "cinco " Case "6" If flag = "N" Then palabras = palabras & "seis " Case "7" If flag = "N" Then palabras = palabras & "siete " Case "8" If flag = "N" Then palabras = palabras & "ocho " Case "9" If flag = "N" Then palabras = palabras & "nueve " End Select End Select '***********Asigna la palabra mil*************** If y = 4 Then If Mid(entero, 6, 1) <> "0" Or Mid(entero, 5, 1) <> "0" Or Mid(entero, 4, 1) <> "0" Or _ (Mid(entero, 6, 1) = "0" And Mid(entero, 5, 1) = "0" And Mid(entero, 4, 1) = "0" And _ Len(entero) <= 6) Then palabras = palabras & "mil " End If '**********Asigna la palabra millón************* If y = 7 Then If Len(entero) = 7 And Mid(entero, 1, 1) = "1" Then palabras = palabras & "millón " Else palabras = palabras & "millones " End If End If Next y '**********Une la parte entera y la parte decimal************* If dec <> "" Then Letras = palabras & "con " & dec Else Letras = palabras End If Else Letras = "" End If End Function End Module
Espero les sirva de algo esta rutina, si es así por favor colabora conmigo votando por este artículo.
Hasta la próxima oportunidad.....:=)
Descargar demo ==> https://1drv.ms/u/s!AmaJ_dzHcGkhhMZWrSaoscJSyEDodg
Intervalo de Tiempo
Introducción
Casi siempre tenemos problemas cuando queremos trabajar ya sea con fechas u horas, creo yo que más de uno, nos hemos roto la cabeza tratando de solucionar este tipo de inconveniente, por ello he tratado de desarrollar este pequeño pero ilustrativo artículo donde muestro como podemos trabajar con estos tipos de datos en Visual Basic .Net.
Primero vamos recordar que dentro de SYSTEM (Espacio de Nombres) podemos localizar una serie de miembros que pueden ser Clases, Interfaces, Estructuras, Delegados y Enumeraciones que nos permiten realizar una serie de tareas, pero de quien nos vamos a centrar en este artículo es TIMESPAN (Estructura).
TimeSpan (Estructura)
Nos permite representar un intervalo de tiempo tomando como base una Fecha/Hora Inicial y una Fecha/Hora Final, devuelve la cantidad de Días, Horas, Minutos, Segundos, etc., transcurridos entre este intervalo de tiempo.
Hay que recordar la siguiente jerarquía.
Estos son los requisitos para poder utilizarlo:
Espacio de Nombres: System
Plataformas: Windows 98, Windows NT 4.0, Windows Millennium Edition, Windows 2000, Windows XP Home Edition, Windows XP Professional Edition, Familia de Windows Server 2003, .NET Compact Framework - Windows CE .NET
Ensamblado: Mscorlib (en Mscorlib.dll)
....Bueno ahora vamos ha realizar lo que más nos agrada, que es un ejemplo de como hallar el intervalo de tiempo transcurrido entre dos fechas/horas:
Primero; realizamos lo que conocemos como Tiempo de Diseño:
Interfaz: Debemos de crear un formulario con los siguientes controles.

....Ahora vamos a ver como debe de quedar el Explorador de Soluciones:

Segundo; ahora vamos a tratar de describir lo que llamamos el Tiempo de Ejecución:
Me despido esperando haber contribuido con un granito de arena en este fascinante mundo .Net. Nos vemos líneas de código más adelante.
Casi siempre tenemos problemas cuando queremos trabajar ya sea con fechas u horas, creo yo que más de uno, nos hemos roto la cabeza tratando de solucionar este tipo de inconveniente, por ello he tratado de desarrollar este pequeño pero ilustrativo artículo donde muestro como podemos trabajar con estos tipos de datos en Visual Basic .Net.
Primero vamos recordar que dentro de SYSTEM (Espacio de Nombres) podemos localizar una serie de miembros que pueden ser Clases, Interfaces, Estructuras, Delegados y Enumeraciones que nos permiten realizar una serie de tareas, pero de quien nos vamos a centrar en este artículo es TIMESPAN (Estructura).
TimeSpan (Estructura)
Nos permite representar un intervalo de tiempo tomando como base una Fecha/Hora Inicial y una Fecha/Hora Final, devuelve la cantidad de Días, Horas, Minutos, Segundos, etc., transcurridos entre este intervalo de tiempo.
Hay que recordar la siguiente jerarquía.
| System | ||||
| System.Object | ||||
| System.ValueType | ||||
| System.TimeSpan | ||||
Espacio de Nombres: System
Plataformas: Windows 98, Windows NT 4.0, Windows Millennium Edition, Windows 2000, Windows XP Home Edition, Windows XP Professional Edition, Familia de Windows Server 2003, .NET Compact Framework - Windows CE .NET
Ensamblado: Mscorlib (en Mscorlib.dll)
....Bueno ahora vamos ha realizar lo que más nos agrada, que es un ejemplo de como hallar el intervalo de tiempo transcurrido entre dos fechas/horas:
Primero; realizamos lo que conocemos como Tiempo de Diseño:
Interfaz: Debemos de crear un formulario con los siguientes controles.
....Ahora vamos a ver como debe de quedar el Explorador de Soluciones:
Segundo; ahora vamos a tratar de describir lo que llamamos el Tiempo de Ejecución:
Public Class FrmInterDateTime Inherits System.Windows.Forms.Form Public flagestado As Boolean = False #Region " Código generado por el Diseñador de Windows Forms " Public Sub New() MyBase.New() 'El Diseñador de Windows Forms requiere esta llamada. InitializeComponent() 'Agregar cualquier inicialización después de la llamada a InitializeComponent() End Sub 'Form reemplaza a Dispose para limpiar la lista de componentes. Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean) If disposing Then If Not (components Is Nothing) Then components.Dispose() End If End If MyBase.Dispose(disposing) End Sub 'Requerido por el Diseñador de Windows Forms Private components As System.ComponentModel.IContainer 'NOTA: el Diseñador de Windows Forms requiere el siguiente procedimiento 'Puede modificarse utilizando el Diseñador de Windows Forms. 'No lo modifique con el editor de código. Friend WithEvents Label1 As System.Windows.Forms.Label Friend WithEvents Label2 As System.Windows.Forms.Label Friend WithEvents Label3 As System.Windows.Forms.Label Friend WithEvents Label4 As System.Windows.Forms.Label Friend WithEvents Label5 As System.Windows.Forms.Label Friend WithEvents Label6 As System.Windows.Forms.Label Friend WithEvents Label7 As System.Windows.Forms.Label Friend WithEvents TxtDTInicio As System.Windows.Forms.TextBox Friend WithEvents TxtDtFinal As System.Windows.Forms.TextBox Friend WithEvents TxtDia As System.Windows.Forms.TextBox Friend WithEvents TxtHra As System.Windows.Forms.TextBox Friend WithEvents TxtMin As System.Windows.Forms.TextBox Friend WithEvents TxtSeg As System.Windows.Forms.TextBox Friend WithEvents TimerTitulo As System.Windows.Forms.Timer Friend WithEvents TimerOpacity As System.Windows.Forms.Timer Friend WithEvents TimerOpacity2 As System.Windows.Forms.Timer Friend WithEvents CmdIntervalo As System.Windows.Forms.ButtonEspero poder publicar un artículo similar pero utilizando C# en los próximos días.Private Sub InitializeComponent() Me.components = New System.ComponentModel.Container Dim resources As System.Resources.ResourceManager = New System.Resources.ResourceManager(GetType(FrmInterDateTime)) Me.Label1 = New System.Windows.Forms.Label Me.Label2 = New System.Windows.Forms.Label Me.Label3 = New System.Windows.Forms.Label Me.Label4 = New System.Windows.Forms.Label Me.Label5 = New System.Windows.Forms.Label Me.Label6 = New System.Windows.Forms.Label Me.Label7 = New System.Windows.Forms.Label Me.TxtDTInicio = New System.Windows.Forms.TextBox Me.TxtDtFinal = New System.Windows.Forms.TextBox Me.CmdIntervalo = New System.Windows.Forms.Button Me.TxtDia = New System.Windows.Forms.TextBox Me.TxtHra = New System.Windows.Forms.TextBox Me.TxtMin = New System.Windows.Forms.TextBox Me.TxtSeg = New System.Windows.Forms.TextBox Me.TimerTitulo = New System.Windows.Forms.Timer(Me.components) Me.TimerOpacity = New System.Windows.Forms.Timer(Me.components) Me.TimerOpacity2 = New System.Windows.Forms.Timer(Me.components) Me.SuspendLayout() ' 'Label1 ' Me.Label1.AutoSize = True Me.Label1.Font = New System.Drawing.Font("Microsoft Sans Serif", 16.0!, CType((System.Drawing.FontStyle.Bold Or System.Drawing.FontStyle.Underline), System.Drawing.FontStyle), System.Drawing.GraphicsUnit.Point, CType(0, Byte)) Me.Label1.ForeColor = System.Drawing.Color.RoyalBlue Me.Label1.Location = New System.Drawing.Point(63, 8) Me.Label1.Name = "Label1" Me.Label1.Size = New System.Drawing.Size(210, 28) Me.Label1.TabIndex = 8 Me.Label1.Text = "Intervalo de Tiempo" ' 'Label2 ' Me.Label2.AutoSize = True Me.Label2.ForeColor = System.Drawing.Color.Crimson Me.Label2.Location = New System.Drawing.Point(16, 48) Me.Label2.Name = "Label2" Me.Label2.Size = New System.Drawing.Size(96, 16) Me.Label2.TabIndex = 1 Me.Label2.Text = "Fecha/Hora Inicial" ' 'Label3 ' Me.Label3.AutoSize = True Me.Label3.ForeColor = System.Drawing.Color.Crimson Me.Label3.Location = New System.Drawing.Point(176, 48) Me.Label3.Name = "Label3" Me.Label3.Size = New System.Drawing.Size(91, 16) Me.Label3.TabIndex = 2 Me.Label3.Text = "Fecha/Hora Final" ' 'Label4 ' Me.Label4.AutoSize = True Me.Label4.Font = New System.Drawing.Font("Microsoft Sans Serif", 8.25!, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, CType(0, Byte)) Me.Label4.ForeColor = System.Drawing.Color.Teal Me.Label4.Location = New System.Drawing.Point(61, 96) Me.Label4.Name = "Label4" Me.Label4.Size = New System.Drawing.Size(31, 16) Me.Label4.TabIndex = 3 Me.Label4.Text = "Días:" ' 'Label5 ' Me.Label5.AutoSize = True Me.Label5.Font = New System.Drawing.Font("Microsoft Sans Serif", 8.25!, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, CType(0, Byte)) Me.Label5.ForeColor = System.Drawing.Color.Teal Me.Label5.Location = New System.Drawing.Point(53, 120) Me.Label5.Name = "Label5" Me.Label5.Size = New System.Drawing.Size(39, 16) Me.Label5.TabIndex = 4 Me.Label5.Text = "Horas:" ' 'Label6 ' Me.Label6.AutoSize = True Me.Label6.Font = New System.Drawing.Font("Microsoft Sans Serif", 8.25!, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, CType(0, Byte)) Me.Label6.ForeColor = System.Drawing.Color.Teal Me.Label6.Location = New System.Drawing.Point(43, 144) Me.Label6.Name = "Label6" Me.Label6.Size = New System.Drawing.Size(49, 16) Me.Label6.TabIndex = 5 Me.Label6.Text = "Minutos:" ' 'Label7 ' Me.Label7.AutoSize = True Me.Label7.Font = New System.Drawing.Font("Microsoft Sans Serif", 8.25!, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, CType(0, Byte)) Me.Label7.ForeColor = System.Drawing.Color.Teal Me.Label7.Location = New System.Drawing.Point(32, 168) Me.Label7.Name = "Label7" Me.Label7.Size = New System.Drawing.Size(60, 16) Me.Label7.TabIndex = 6 Me.Label7.Text = "Segundos:" ' 'TxtDTInicio ' Me.TxtDTInicio.Location = New System.Drawing.Point(16, 64) Me.TxtDTInicio.Name = "TxtDTInicio" Me.TxtDTInicio.Size = New System.Drawing.Size(144, 20) Me.TxtDTInicio.TabIndex = 1 Me.TxtDTInicio.Text = "08/07/2004 08:00:00 a.m." ' 'TxtDtFinal ' Me.TxtDtFinal.Location = New System.Drawing.Point(176, 64) Me.TxtDtFinal.Name = "TxtDtFinal" Me.TxtDtFinal.Size = New System.Drawing.Size(144, 20) Me.TxtDtFinal.TabIndex = 2 Me.TxtDtFinal.Text = "" ' 'CmdIntervalo ' Me.CmdIntervalo.ForeColor = System.Drawing.Color.Navy Me.CmdIntervalo.Image = CType(resources.GetObject("CmdIntervalo.Image"), System.Drawing.Image) Me.CmdIntervalo.ImageAlign = System.Drawing.ContentAlignment.TopCenter Me.CmdIntervalo.Location = New System.Drawing.Point(208, 112) Me.CmdIntervalo.Name = "CmdIntervalo" Me.CmdIntervalo.Size = New System.Drawing.Size(72, 56) Me.CmdIntervalo.TabIndex = 0 Me.CmdIntervalo.Text = "&Hallar Intervalo" Me.CmdIntervalo.TextAlign = System.Drawing.ContentAlignment.BottomCenter ' 'TxtDia ' Me.TxtDia.Location = New System.Drawing.Point(96, 96) Me.TxtDia.Name = "TxtDia" Me.TxtDia.ReadOnly = True Me.TxtDia.Size = New System.Drawing.Size(60, 20) Me.TxtDia.TabIndex = 9 Me.TxtDia.Text = "" Me.TxtDia.TextAlign = System.Windows.Forms.HorizontalAlignment.Right ' 'TxtHra ' Me.TxtHra.Location = New System.Drawing.Point(96, 120) Me.TxtHra.Name = "TxtHra" Me.TxtHra.ReadOnly = True Me.TxtHra.Size = New System.Drawing.Size(60, 20) Me.TxtHra.TabIndex = 10 Me.TxtHra.Text = "" Me.TxtHra.TextAlign = System.Windows.Forms.HorizontalAlignment.Right ' 'TxtMin ' Me.TxtMin.Location = New System.Drawing.Point(96, 144) Me.TxtMin.Name = "TxtMin" Me.TxtMin.ReadOnly = True Me.TxtMin.Size = New System.Drawing.Size(60, 20) Me.TxtMin.TabIndex = 11 Me.TxtMin.Text = "" Me.TxtMin.TextAlign = System.Windows.Forms.HorizontalAlignment.Right ' 'TxtSeg ' Me.TxtSeg.Location = New System.Drawing.Point(96, 168) Me.TxtSeg.Name = "TxtSeg" Me.TxtSeg.ReadOnly = True Me.TxtSeg.Size = New System.Drawing.Size(60, 20) Me.TxtSeg.TabIndex = 12 Me.TxtSeg.Text = "" Me.TxtSeg.TextAlign = System.Windows.Forms.HorizontalAlignment.Right ' 'TimerTitulo ' Me.TimerTitulo.Enabled = True Me.TimerTitulo.Interval = 300 ' 'TimerOpacity ' ' 'TimerOpacity2 ' Me.TimerOpacity2.Enabled = True ' 'FrmInterDateTime ' Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13) Me.ClientSize = New System.Drawing.Size(336, 200) Me.Controls.Add(Me.TxtSeg) Me.Controls.Add(Me.TxtMin) Me.Controls.Add(Me.TxtHra) Me.Controls.Add(Me.TxtDia) Me.Controls.Add(Me.CmdIntervalo) Me.Controls.Add(Me.TxtDtFinal) Me.Controls.Add(Me.TxtDTInicio) Me.Controls.Add(Me.Label7) Me.Controls.Add(Me.Label6) Me.Controls.Add(Me.Label5) Me.Controls.Add(Me.Label4) Me.Controls.Add(Me.Label3) Me.Controls.Add(Me.Label2) Me.Controls.Add(Me.Label1) Me.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog Me.Icon = CType(resources.GetObject("$this.Icon"), System.Drawing.Icon) Me.MaximizeBox = False Me.Name = "FrmInterDateTime" Me.Opacity = 0 Me.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen Me.Text = "Intervalo de Tiempo - alextaya@hotmail.com " Me.ResumeLayout(False) End Sub #End Region Private Sub FrmInterDateTime_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load TxtDtFinal.Text = Now End Sub Private Sub VerInterDateTime(ByVal TSpan As TimeSpan) TxtDia.Text = TSpan.Days.ToString TxtHra.Text = TSpan.Hours.ToString TxtMin.Text = TSpan.Minutes.ToString TxtSeg.Text = TSpan.Seconds.ToString End Sub Private Sub TimerTitulo_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles TimerTitulo.Tick Me.Text = Microsoft.VisualBasic.Right(Me.Text, (Len(Me.Text) - 1)) + Microsoft.VisualBasic.Left(Me.Text, 1) End Sub Private Sub TimerOpacity_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles TimerOpacity.Tick Me.Opacity -= 0.05 If Me.Opacity = 0 Then flagestado = True Me.Close() End If End Sub Private Sub FrmInterDateTime_Closing(ByVal sender As Object, ByVal e As System.ComponentModel.CancelEventArgs) Handles MyBase.Closing If flagestado = False Then e.Cancel = True Me.TimerOpacity.Enabled = True End If End Sub Private Sub TimerOpacity2_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles TimerOpacity2.Tick Me.Opacity += 0.05 If Me.Opacity = 1 Then Me.TimerOpacity2.Enabled = False End If End Sub Private Sub CmdIntervalo_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles CmdIntervalo.Click Dim TSpan As TimeSpan Dim DTInicio As DateTime Dim DtFinal As DateTime DTInicio = DateTime.Parse(TxtDTInicio.Text) DtFinal = DateTime.Parse(TxtDtFinal.Text) TSpan = DtFinal.Subtract(DTInicio).Duration VerInterDateTime(TSpan) End Sub End Class Ahora simplemente nos queda ejecutar la solución para ver que todo este bien, espero que funcione sin ningún problema jeje ;).
Me despido esperando haber contribuido con un granito de arena en este fascinante mundo .Net. Nos vemos líneas de código más adelante.
Descargar demo ==> http://www.elguille.info/colabora/puntoNET/AlexTaya_InterDateTime.htm
Microsoft VBA (Visual Basic for Applications)
Microsoft VBA (Visual Basic for Applications) es el lenguaje de macros de Microsoft Visual Basic que se utiliza para programar aplicaciones Windows y que se incluye en varias aplicaciones Microsoft. VBA permite a usuarios y programadores ampliar la funcionalidad de programas de la suite Microsoft Office. Visual Basic para Aplicaciones es un subconjunto casi completo de Visual Basic 5.0 y 6.0.
Descargar demo ==> https://1drv.ms/p/s!AmaJ_dzHcGkhhMZUkYyCsJXTlsgc4Q
Saludos.
lunes, enero 09, 2017
Construye Elegantes Componentes Rails Con Plain Old Ruby Objects
BY EQBAL QURAN - FREELANCE SOFTWARE ENGINEER @ TOPTAL (TRANSLATED BY MARISELA ORDAZ) #Decoupling #RoR #Ruby #RubyOnRails
This article was originally written in English
No hay problema. Hoy vamos a discutir cómo usar las mejores prácticas POO para hacer tu código más limpio, aislado y separado.
¿Vale La Pena Refactorizar Tu Aplicación?
Comencemos por mirar como deberías decidir si tu aplicación es una buena candidata para refactorización.

Aquí hay una lista de métricas y preguntas que usualmente me hago para determinar si mis códigos necesitan o no refactorización.
- Unidades de prueba lentas. las unidades de prueba PORO normalmente corren rápido, con códigos bien aislados, así que las pruebas que corren lento pueden, a menudo, ser un indicador de un mal diseño o responsabilidades sobre-acopladas.
- FAT models or controllers. Un modelo o controlador con más de 200 líneas de código (LOC) es generalmente un buen candidato para refactorizar.
- Base de código excesivamente larga. Si tienes ERB/HTML/HAML con más de 30,000 LOC o código fuente Ruby (sin GEMs ) con más de 50,000 LOC, hay una gran posibilidad de que debas refactorizar.
Intenta usar algo así para saber cuántas líneas de código fuente Ruby tienes:
find app -iname "*.rb" -type f -exec cat {} \;| wc -l
Este comando buscará en todos los archivos con extensión .rb (archivos ruby) en la carpeta /app, luego imprime el número de líneas. Por favor, ten en cuenta que este número es solo un aproximado, ya que las líneas de comentario se incluirán en este total.
Otra opción más precisa e informativa es usar la tarea rake
stats de Rails, la cual expone un resumen rápido de líneas de código, número de clases, número de métodos, el ratio de métodos a clases y el ratio de líneas de código por método:*bundle exec rake stats*
+----------------------+-------+-----+-------+---------+-----+-------+
| Nombre | Líneas | LOC | Clase | Método | M/C | LOC/M |
+----------------------+-------+-----+-------+---------+-----+-------+
| Controladores | 195 | 153 | 6 | 18 | 3 | 6 |
| Helpers | 14 | 13 | 0 | 2 | 0 | 4 |
| Modelos | 120 | 84 | 5 | 12 | 2 | 5 |
| Mailers | 0 | 0 | 0 | 0 | 0 | 0 |
| Javascripts | 45 | 12 | 0 | 3 | 0 | 2 |
| Bibliotecas | 0 | 0 | 0 | 0 | 0 | 0 |
| Controlador specs | 106 | 75 | 0 | 0 | 0 | 0 |
| Helper specs | 15 | 4 | 0 | 0 | 0 | 0 |
| Modelo specs | 238 | 182 | 0 | 0 | 0 | 0 |
| Petición specs | 699 | 489 | 0 | 14 | 0 | 32 |
| Routing specs | 35 | 26 | 0 | 0 | 0 | 0 |
| Vista specs | 5 | 4 | 0 | 0 | 0 | 0 |
+----------------------+-------+-----+-------+---------+-----+-------+
| Total | 1472 |1042 | 11 | 49 | 4 | 19 |
+----------------------+-------+-----+-------+---------+-----+-------+
Código LOC: 262 Prueba LOC: 780 Ratio Código a Prueba: 1:3.0
- ¿Puedo extraer patrones recurrentes en mi base de código?
Separando en Acción
Comencemos con un ejemplo de la vida real.
Imagina que queremos escribir una aplicación que siga el tiempo para las personas que trotan; en la página principal, el usuario puede ver los tiempos que se introduzcan.

Cada una de las entradas de tiempo tienen fecha, distancia, duración, e información adicional relevante del “status” (ej.: clima, tipo de terreno, etc.), y una velocidad promedio que puede ser calculada cuando sea necesario.
Necesitamos un reporte que muestre la velocidad promedio y distancia por semana. Si la velocidad promedio en la entrada es mayor a la velocidad promedio en total, notificaremos al usuario a través un SMS (para este ejemplo, usaremos Nexmo RESTful API para enviar el SMS).
La página principal te permitirá seleccionar la distancia, fecha y tiempo en que se está trotando, para así crear una entrada similar a ésta:

También tenemos una página de
estadísticas, la cual es básicamente un reporte semanal que incluye la velocidad promedio y distancia cubierta por semana.
- Puedes ver la muestra online aquí.
El Código
La estructura del directorio de la
aplicación se ve similar a esto: ⇒ tree
.
├── assets
│ └── ...
├── controllers
│ ├── application_controller.rb
│ ├── entries_controller.rb
│ └── statistics_controller.rb
├── helpers
│ ├── application_helper.rb
│ ├── entries_helper.rb
│ └── statistics_helper.rb
├── mailers
├── models
│ ├── entry.rb
│ └── user.rb
└── views
├── devise
│ └── ...
├── entries
│ ├── _entry.html.erb
│ ├── _form.html.erb
│ └── index.html.erb
├── layouts
│ └── application.html.erb
└── statistics
└── index.html.erb
No voy a discutir el modelo
Usuario ya que no es nada fuera de lo común, dado que lo estamos usando con Devise para implementar autenticación.
En lo referente al modelo
Entrada, contiene la lógica de negocios para nuestra aplicación.
Cada
Entrada pertenece a un Usuario.
Validamos la presencia de los siguientes atributos para cada entrada
distancia, períodode_tiempo, fecha_hora y estatus.
Cada vez que creamos una entrada, comparamos la velocidad promedio del usuario con el promedio de todos los usuarios en el sistema, y le notificamos al usuario vía SMS, usando Nexmo(no discutiremos como se usa la biblioteca de Nexmo, aunque quería demostrar un caso en el que usamos una biblioteca externa).
Nota que la
Entrada modelo contiene más que la lógica de negocio sola. También maneja algunas validaciones y llamados.
La
entries_controller.rb tiene las acciones CRUD principales (aunque sin actualización). EntriesController#index obtiene las entradas para el usuario actual y ordena los records por fecha de creación, mientras que EntriesController#create crea una nueva entrada. No hay necesidad de discutir lo obvio ni las responsabilidades de EntriesController#destroy :
Mientras que
statistics_controller.rb es responsable de calcular el informe semanal, StatisticsController#index obtiene las entradas para el usuario conectado y los agrupa por semana, empleando el método #group_by el cual se encuentra en la clase Enumerable en Rails. Luego, intenta decorar los resultados al usar algunos métodos privados.
No discutimos mucho las vistas aquí ya que el código fuente se explica así mismo.

Debajo se encuentra la vista para enlistar las entradas para el usuario conectado (
index.html.erb). Este es el patrón que se usará para mostrar los resultados de la acción índice (método) en el controlador de entradas:
Nota que estamos usando
render @entries parciales, para llevar el código compartido a un patrón parcial _entry.html.erb para que así podamos mantener nuestro código DRY y reusable:
Lo mismo se aplica a una
_forma parcial. En vez de usar el mismo código con (nuevas y editadas) acciones, creamos una forma parcial reusable:
En lo que concierne a la vista de la página de informe semanal,
statistics/index.html.erb muestra algunas estadísticas, e informa sobre las actividades semanales del usuario al agrupar algunas entradas:
Y finalmente, el helper para las entradas,
entries_helper.rb, incluye dos helpers readable_time_period y readable_speed los cuales deberían hacer los atributos más fáciles de leer:
Nada muy complicado hasta ahora.
La mayoría de ustedes pueden argumentar que refactorizar esto va en contra del principio KISS y hará el sistema más complicado.
¿Entonces, esta aplicación, de verdad, necesita ser refactorizada?
Absolutamente no, pero lo consideraremos solo con el propósito de muestra.
Después de todo, si observas la siguiente sección y las características que indican que una aplicación necesita refactorización, se vuelve obvio que la aplicación en nuestro ejemplo, no es una candidata válida para refactorización.
El Ciclo De La Vida
Empecemos por explicar el patrón de estructura MVC en Rails.
Usualmente comienza por el buscador al hacer una petición como
https://www.toptal.com/jogging/show/1.
El servidor web recibe la petición y usa
rutas para definir qué controlador usar.
Los controladores hacen el trabajo de analizar las peticiones de usuarios, entregas de data, cookies, sesiones, etc., y luego pide al
modelo que obtenga la data.
Los
modelos son clases Ruby que le hablan a la base de datos, guardan y validan la data, ejecutan la lógica de negocio y hacen el trabajo pesado. Las vistas son lo que el usuario puede ver: HTML, CSS, XML, Javascript, JSON.
Si queremos mostrar la secuencia de una petición de ciclo de vida Rails, se vería como esto:

Lo que quiero conseguir es agregar más abstracción, usando POROs y hacer el patrón algo similar a lo siguiente, para las acciones
create/update:
Y algo similar a esto para las acciones
list/show :
Al agregar abstracciones POROs estamos asegurando una separación completa, entre responsabilidades SRP, algo que Rails no domina totalmente.
Directrices
Para alcanzar el nuevo diseño, usaré las directrices que se ven abajo, pero ten en cuenta que éstas no son reglas que debes seguir al pie de la letra. Piensa que son directrices flexibles que hacen refactorizar más fácil.
- Los modelos ActiveRecord pueden contener asociaciones y constantes, pero nada más. Eso significa que no habrá llamados (usa objetos de servicio y agrega los llamados ahí) y sin validaciones (usa Form objects para incluir nombres y validaciones para el modelo).
- Mantén a los Controladores como capas delgadas y siempre llama a objetos de Servicio. Algunos de ustedes se preguntarán ¿por qué usar controladores, si queremos seguir llamando objetos de servicio para contener la lógica? Bueno, los controladores son un buen lugar para tener el routing HTTP, análisis de parámetros, autenticación, negociación de contenidos, llamar al servicio correcto u objeto editor, atrapar excepciones, formato de respuestas y regresar el estado de código HTTP correcto.
- Los servicios deberían llamar objetos Query, y no almacenar estado. Usa métodos de instancia no de clase. Deben haber muy pocos métodos públicos guardados con SRP.
- Queries deberían hacerse con objetos query. Los métodos de objeto Query deben regresar un objeto, un hash o array, pero no una asociación ActiveRecord.
- Evita usar Helpers, mejor usa decoradores. ¿Por qué? Una dificultad común con helpers en Rails, es que se pueden convertir en un montón de funciones no-OO, las cuales comparten un nombre de espacio y se sobreponen entre ellas. Pero es mucho peor, el hecho no de que no se puede usar ningún polimorfismo con los helpers de Rails — al proveer diferentes implementaciones para diferentes contextos o tipos, y superación o sub-clasificación de helpers. Pienso que las clases de helper en Rails deberían usarse, generalmente, para métodos de utilidad, no para casos de uso específico; como formatear atributos modelo para cualquier lógica de presentación. Mantenlos ligeros y fáciles de seguir. Decoradores/Delegantes mejor.** ¿Por qué? Después de todo, las preocupaciones parecen ser parte de Rails, y pueden secar (DRY up) un código cuando se comparte entre múltiples modelos. Sin embargo, el problema mayor es que las preocupaciones no hacen al objeto modelo más cohesivo. Solo que el código está mejor organizado. En otras palabras, no hay un cambio real al API del modelo.
- Intenta extraer Objetos de Valor de los modelos para mantener tu código más limpio y agrupar atributos relacionados.
- Siempre pasa una variable de instancia por cada vista.
Refactorizar
Antes de empezar quiero discutir algo más. Cuando se comienza la refactorización, usualmente terminas preguntándote: “¿Es una buena refactorización?”
Si sientes que estás haciendo más separación o aislamientos entre responsabilidades (aunque eso signifique agregar más código y nuevos archivos) entonces, esto es algo bueno. Después de todo, separar una aplicación es una muy buena práctica y hace más fácil, para nosotros, hacer una prueba de unidad apropiada.
No voy a discutir cosas, como mover lógica desde los controladores a los modelos, ya que supongo que a estás haciendo eso y te sientes cómodo usando Rails (usualmente Controlador Flaca y modelo FAT).
Con el propósito de mantener este artículo conciso, no voy a discutir cómo hacer pruebas pero esto no significa que tú no deberías hacer pruebas.
Por el contrario, deberías empezar siempre con una prueba para asegurarte de que las cosas van bien, antes de avanzar. Esto es algo obligatorio, en especial cuando se hace refactorización.
Luego podemos implementar cambios y asegurarnos de que las pruebas pasen por las partes relevantes del código.
Extraer Objetos De Valor
Primero, ¿qué es un objeto de valor?
Martin Fowler explica:
Objeto de Valor es un objeto pequeño, como un objeto de dinero o rango de fechas. Su característica clave es que siguen semánticas de valor, en vez de semánticas referenciales.
En ocasiones, te puedes encontrar con una situación donde un concepto merece su propia abstracción y donde la igualdad de ésta no se basa en valores, sino en la identidad. Ejemplos de esto pueden ser: Ruby’s Date, URI y Pathname. La extracción de un objeto de valor (o modelo de dominio) es una gran conveniencia.
¿Para qué molestarse?
Una de las grandes ventajas de un objeto de valor, es que ayudan a obtener una expresividad en tu código. Tu código tendrá una tendencia a ser más claro, o al menos puede serlo si tienes buenas prácticas en cuanto a nombres. Ya que el Objeto de Valor es una abstracción, lleva a códigos más claros y menos errores.

Otra ganancia es la inmutabilidad. La inmutabilidad de objetos es muy importante. Cuando estamos almacenando ciertos sets de data, lo cual puede ser usado en un objeto de valor, usualmente no me gusta que la data sea manipulada.
¿Cuándo es esto útil?
No existe la respuesta perfecta a esta pregunta. Haz lo que sea más conveniente para ti y lo que tenga más sentido en una situación dada.
Más allá de esto, hay algunas directrices que uso para ayudarme a tomar esa decisión.
Si crees que un grupo de métodos está relacionado, bueno, con objetos de valor es más caro. Esta expresividad significa que un objeto de valor, debería representar un set de data distintivo, lo cual puede ser deducido por tu desarrollador promedio solo con ver el nombre del objeto.
¿Cómo se hace esto?
Los Objetos de Valor deberían seguir ciertas reglas:
- Los Objetos de Valor deberían tener múltiples atributos.
- Los atributos deberían ser inmutables, a través del ciclo de vida del objeto.
- La igualdad es determinada por los atributos del objeto.
En nuestro ejemplo, crearé un objeto de valor
EntryStatus, para abstraer los atributos Entry#status_weather y Entry#status_landform a su propia clase, lo cual se ve de esta manera:
Nota: Esto es solo un PORO (Plain Old Ruby Object), no se hereda de
ActiveRecord::Base. Hemos definido métodos de lector para nuestros atributos y estamos asignándolos al inicio. También, usamos una mezcla comparable, para equiparar objetos usando el método (<=>).
Podemos modificar el modelo
Entry para usar el objeto de valor que hemos creado:
También podemos modificar el método
EntryController#create para usar el nuevo objeto de valor en concordancia:Extrae Objetos de Servicio
¿Qué es un objeto de Servicio?
El trabajo de un objeto de Servicio es mantener el código durante un espacio particular de la lógica de negocio. A diferencia del estilo “modelo fat”, donde un número pequeño de objetos contienen muchos, muchos métodos, para toda la lógica necesaria, usando objetos de servicio da como resultado muchas clases, cada una de éstas sirviendo un propósito único.

¿Por qué? ¿Cuáles son los beneficios?
- Separar. Los Objetos de Servicio te ayudan a conseguir más aislaciones entre los objetos.
- Visibilidad. Los Objetos de Servicio (Si están bien nombrados) muestran lo que hace una aplicación. Puedo pasar la mirada por el directorio de servicios, para ver que capacidades provee una aplicación.
- Limpia modelos y controladores. Los controladores encienden la petición (params, sesión, cookies) en los argumentos, los pasa al servicio y los redirige o deja dependiendo de la respuesta del servicio. Mientras los modelos solo tratan con asociaciones y persistencia. Extraer código de los controladores/modelos hacia objetos de servicio apoyaría a SRP y separaría más el código. La responsabilidad del modelo sería solo tener que lidiar con asociaciones y guardar/eliminar registros, mientras el objeto de Servicio tendría una sola responsabilidad (SRP). Esto lleva a un mejor diseño y mejores unidades de prueba.
- DRY y Acepta el cambio. Yo mantengo objetos de servicio lo más simple y pequeños posible. Yo compongo objetos de servicio con otros objetos de servicio, y los reúso.
- Limpia y acelera tu suite de pruebas. Los servicios son fáciles y rápidos de probar, ya que son objetos Ruby pequeños con un punto de entrada (el método llamada). Los servicios complejos se componen con otros servicios, así que puedes separar tus pruebas fácilmente. También, usar objetos de servicio hace más fácil recuperar objetos relacionados sin necesidad de cargar el ambiente completo de rails.
- Rescatable desde cualquier parte. Los objetos de servicio serán llamados, seguramente, desde los controladores al igual que desde objetos de servicio, DelayedJob / Rescue / Sidekiq Jobs, Rake tasks, consola, etc.
Por otro lado, nada es perfecto. Una desventaja de los objetos de Servicio es que pueden ser un exceso para cada pequeña acción. En estos casos, puedes terminar complicando y no simplificando tu código.
¿Cuándo deberías extraer Objetos de Servicio?
Aquí tampoco hay una regla fija.
Normalmente, los objetos de Servicio son mejores para sistemas de medianos a grandes: aquellos con una cantidad decente de lógica, más allá de las operaciones CRUD estándares.
Así que cuando pienses que un trozo del código no pertenece en el directorio, en el lugar donde lo ibas a agregar, es buena idea reconsiderarlo y ver si sería mejor que fuese a un objeto de servicio.
Aquí están algunos indicadores de cuándo usar objetos de Servicio:
- La acción es compleja.
- La acción alcanza múltiples modelos.
- La acción interactúa con un servicio externo.
- La acción no es una preocupación primordial del modelo subrayado.
- Hay múltiples maneras de realizar la acción.
¿Cómo debes diseñar Objetos de Servicio?
Diseñar la clase para un objeto de servicio es relativamente directo, ya que no necesitas gems especiales, tampoco debes aprender un DLS nuevo, pero si puedes, más o menos, confiar en las habilidades de diseño software que ya posees.
Usualmente, utilizo las siguientes directrices y convenciones para diseñar el objeto de servicio:
- No almacenes el estado del objeto.
- Usa métodos de instancia, no métodos de clase.
- Debe haber muy pocos métodos públicos (preferiblemente uno que apoye *SRP.
- Los métodos deben regresar resultados de objeto ricos, no booleanos.
- Los servicios van debajo del directorio
app/services. Te aconsejo que uses subdirectorios, para dominios lógicas de negocio fuertes. Por ejemplo, el archivoapp/services/report/generate_weekly.rbdefiniráReport::GenerateWeeklymientras queapp/services/report/publish_monthly.rbdefiniráReport::PublishMonthly. - Los servicios comienzan con un verbo (y no terminan en servicio):
ApproveTransaction,SendTestNewsletter,ImportUsersFromCsv. - Los servicios responden al método llamada. Me di cuenta que usar otro verbo lo hace un poco redundante: ApproveTransaction.approve() no se lee bien. También, el método llamada, es el método de facto de lambda, procs, y objetos de método.
Si observas
StatisticsController#index, notarás un grupo de métodos (weeks_to_date_from, weeks_to_date_to, avg_distance, etc.) agrupados al controlador. Eso no es bueno. Considera las ramificaciones, si quieres generar el informe semanal fuera de statistics_controller.
En nuestro caso, vamos a crear
Report::GenerateWeekly y extraigamos el informe de lógica de StatisticsController:
Así que
StatisticsController#index ahora se ve más limpio:
Al aplicar el patrón del objeto de Servicio, agrupamos código alrededor de una acción compleja y específica y promovemos la creación de métodos pequeños y más claros.
Extrae Objetos Query De Los Controladores
¿Qué es un Objeto Query?
Un objeto Query es un PORO, el cual representa una base de datos de consulta. Puede ser reusada en diferentes lugares de la aplicación, mientras que, al mismo tiempo, esconde la lógica de consulta. También provee una buena unidad aislada para pruebas.
Deberías extraer consultas SQL/NoSQL complejas hacia sus propias clases.
Cada objeto Query es responsable de regresar un set de resultados basado en las reglas de criterio/negocio.

En este ejemplo, no tenemos ninguna consulta (query) compleja, así que usar objeto Query no sería eficiente. Sin embargo, con el fin de demostrar, extraeremos la consulta en
Report::GenerateWeekly#call y crearemos generate_entries_query.rb:
Y en
Report::GenerateWeekly#call, reemplacemos: def call
@user.entries.group_by(&:week).map do |week, entries|
WeeklyReport.new(
...
)
end
end
con:
def call
weekly_grouped_entries = GroupEntriesQuery.new(@user).call
weekly_grouped_entries.map do |week, entries|
WeeklyReport.new(
...
)
end
end
El patrón de objeto query (consulta) ayuda a mantener la lógica de tu modelo estrictamente relacionada a un comportamiento de clase, mientras que mantiene tus controladores flacas. Debido a que no son más que plain old Ruby classes, los objetos query no necesitan heredar de
ActiveRecord::Base, y deberían ser responsables por nada más que la ejecución de consultas.Extrae Crear Entrada A Un Objeto de Servicio
Ahora, vamos a extraer la lógica de crear una nueva entrada a un nuevo objeto de servicio. Vamos a usar la convención y creemos
CreateEntry:
Y ahora nuestro
EntriesController#create es de la siguiente manera: def create
begin
CreateEntry.new(current_user, entry_params).call
flash[:notice] = 'Entry was successfully created.'
rescue Exception => e
flash[:error] = e.message
end
redirect_to root_path
end
Más Validaciones A Un Objeto De Forma
Ahora las cosas comienzan a ponerse más interesantes.
Recuerda que en nuestras directrices acordamos que queríamos que los modelos tuvieran asociaciones y constantes, pero nada más (ni validaciones ni llamados). Así que empecemos por remover los llamados y usa un objeto de Forma en su lugar.
Un objeto de Forma es un PORO (Plain Old Ruby Object). Toma el mando del controlador/objeto de servicio cuando necesite hablar con la base de datos.
¿Por qué usar objetos de Forma?
Cuando necesites refactorizar tu aplicación, siempre es buena idea tener en mente, la responsabilidad única principal (SRP).
SRP te ayuda a tomar mejores decisiones de diseño, en cuanto a la responsabilidad que debe tener una clase.
Tu modelo de mesa de base de datos (un modelo ActiveRecord en el contexto de Rails), por ejemplo, representa un record de la base de datos único en código, así que no hay razón para que esté preocupado con nada que haga tu usuario.
Aquí es donde entra el objeto de Forma.
Un objeto de Forma es responsable de representar una forma en tu aplicación. Así que cada campo de entrada puede ser tratado como un atributo en la clase. Puede validar que esos atributos cumplen algunas reglas de validación, y puede pasar la data “limpia” a donde debe ir (ej., tu modelo de base de datos o tal vez tu constructor de búsqueda de consultas).
¿Cuándo deberías usar un objeto de Forma?
- Cuando quieras extraer las validaciones de los modelos Rails.
- Cuando múltiples modelos pueden ser actualizados por una sola forma de entrega, deberías crear un objeto de Forma.
Esto te permite poner toda la lógica de forma (nombrar convenciones, validaciones, y otros) en un solo lugar.
¿Cómo crear un objeto de Forma?
- Crea una clase simple Ruby.
- Incluye
ActiveModel::Model(en Rails 3, tienes que incluir Nombre, Conversión y Validación, en su lugar). - Empieza a usar tu nueva clase de forma, como si fuera un modelo regular de ActiveRecord, donde la mayor diferencia es que no puedes continuar con la data almacenada en este objeto.
Por favor, ten en cuenta que puedes usar la gem reform, pero siguiendo con PORO, crearemos
entry_form.rb lo cual se ve así:
Y modificaremos
CreateEntry para comenzar a usar el objeto de Formato EntryForm: class CreateEntry
......
......
def call
@entry_form = ::EntryForm.new(@params)
if @entry_form.valid?
....
else
....
end
end
end
Nota: Algunos de ustedes dirán que no hay necesidad de acceder al objeto de Forma desde el objeto de Servicio y que podemos llamar al objeto de Forma directamente desde el controlador, lo cual es un argumento válido. Sin embargo, preferiría tener un flujo claro, por eso siempre llamo al objeto de Forma desde objeto de Servicio.
Mueve los Llamados al Objeto de Servicio.
Como acordamos anteriormente, no queremos que nuestros modelos contengan validaciones y llamados. Extrajimos las validaciones usando objetos de Forma. Pero todavía estamos usando algunos llamados (
after_create en modelo Entry compare_speed_and_notify_user).
¿Por qué queremos remover los llamados de los modelos?
Desarrolladores Rails usualmente comienzan a notar un dolor con los llamados, durante las pruebas. Si no estás haciendo pruebas con tus modelos ActiveRecord, comenzarás a notar el dolor después, mientras crece tu aplicación y mientras se necesite más lógica para llamar o evitar los llamados.
después_* los llamados son usados primordialmente en relación a guardar o continuar con el objeto.
Una vez que el objeto es guardado, el propósito (ej. responsabilidad) del objeto ha sido cumplido. Así que, si todavía vemos llamados siendo invocados, después de que el objeto ha sido guardado, estos probablemente son llamados que buscan salir del área de responsabilidad de objetos, y ahí es cuando encontramos problemas.
En nuestro caso, estamos enviando un SMS al usuario, lo que no está relacionado con el dominio de Entrada.
Una manera simple de resolver el problema es, mover el llamado al objeto de servicio relacionado. Después de todo, enviar un SMS para el usuario correspondiente está relacionado al Objeto de Servicio
CreateEntry y no al modelo Entrada, como tal.
Al hacer esto, ya no tenemos que apagar, el método
compare_speed_and_notify_user en nuestras pruebas. Hemos hecho que esto sea un asunto sencillo, el crear una entrada sin que sea necesario enviar un SMS y estamos siguiendo un buen diseño de Objeto Orientado, al asegurarnos de que nuestras clases tengan una responsabilidad única (SRP).
Así que ahora
CreateEntry es algo similar a esto:Usa Decoradores En Vez De Helpers
Aunque podemos fácilmente usar la colección Draper de modelos de vista y decoradores, me quedo con PORO, por este artículo, como lo he estado haciendo hasta ahora.
Lo que necesito es una clase que llame métodos al objeto decorado.
Puedo usar
method_missing para implementar eso, pero usaré la biblioteca estándar de Ruby, SimpleDelegator. El siguiente código muestra cómo usar SimpleDelegator para implementar nuestro decorador base: % app/decorators/base_decorator.rb
require 'delegate'
class BaseDecorator < SimpleDelegator
def initialize(base, view_context)
super(base)
@object = base
@view_context = view_context
end
private
def self.decorates(name)
define_method(name) do
@object
end
end
def _h
@view_context
end
end
¿Por qué el método
_h?
Este método actúa como un proxy para contexto de vista. Por defecto, el contexto de vista es una instancia de una clase vista, siendo ésta
ActionView::Base. Puedes acceder a los helpers de vistas de la siguiente manera: _h.content_tag :div, 'my-div', class: 'my-class'
Para hacerlo más conveniente agregamos un método
decorado a ApplicationHelper: module ApplicationHelper
# .....
def decorate(object, klass = nil)
klass ||= "#{object.class}Decorator".constantize
decorator = klass.new(object, self)
yield decorator if block_given?
decorator
end
# .....
end
Ahora, podemos mover los helpers
EntriesHelper a los decoradores: # app/decorators/entry_decorator.rb
class EntryDecorator < BaseDecorator
decorates :entry
def readable_time_period
mins = entry.time_period
return Time.at(60 * mins).utc.strftime('%M Mins').html_safe if mins < 60
Time.at(60 * mins).utc.strftime('%H Hour %M Mins').html_safe
end
def readable_speed
"#{sprintf('%0.2f', entry.speed)} Km/H".html_safe
end
end
Y podemos usar
readable_time_period y readable_speed de la siguiente forma: # app/views/entries/_entry.html.erb
- <%= readable_speed(entry) %> </td>
+
<%= decorate(entry).readable_speed %>
>
- <%= readable_time_period(entry) %></td>
+
<%= decorate(entry).readable_time_period %>
>
Estructura Después De Refactorizar
Terminamos con más archivos, pero eso no es necesariamente algo malo (y recuerda esto, desde el comienzo, estábamos conscientes de que este ejemplo era con fines demostrativos y no era necesariamente un buen caso de uso para refactorización):
app
├── assets
│ └── ...
├── controllers
│ ├── application_controller.rb
│ ├── entries_controller.rb
│ └── statistics_controller.rb
├── decorators
│ ├── base_decorator.rb
│ └── entry_decorator.rb
├── forms
│ └── entry_form.rb
├── helpers
│ └── application_helper.rb
├── mailers
├── models
│ ├── entry.rb
│ ├── entry_status.rb
│ └── user.rb
├── queries
│ └── group_entries_query.rb
├── services
│ ├── create_entry.rb
│ └── report
│ └── generate_weekly.rb
└── views
├── devise
│ └── ..
├── entries
│ ├── _entry.html.erb
│ ├── _form.html.erb
│ └── index.html.erb
├── layouts
│ └── application.html.erb
└── statistics
└── index.html.erb
Conclusión
Aunque nos enfocamos en Rails en este blog post, RoR (Ruby on Rails) no es una dependencia de los objetos de servicio ni de otros POROs. Puedes usar este enfoque con cualquier framework web, móvil o aplicación de consola.
Al usar MVC como arquitectura, todo se mantiene junto y te hace ir más lento porque la mayoría de los cambios tienen un impacto en otras partes de la aplicación. También te obliga a pensar donde poner algunas lógicas de negocio – ¿debería ir en el modelo, el controlador o la vista?
Al usar un simple PORO, hemos movido la lógica de negocio a los modelos o servicios, que no heredan de
ActiveRecord, lo cual ya es una ganancia, sin mencionar que tenemos un código más claro, lo cual apoya SRP y pruebas de unidades más rápidas.
Una arquitectura limpia intenta poner las casillas de uso en el centro/parte superior de tu estructura para que veas fácilmente lo que hace tu aplicación. También facilita la adopción de cambios, porque es más modular y aislado. Espero haber demostrado como al Plain Old Ruby Objects y más abstracciones, separa preocupaciones, simplifica pruebas y ayuda a producir código limpio y fácil de mantener.
Referencia: https://www.toptal.com/ruby-on-rails/construye-elegantes-componentes-rails-con-plain-old-ruby-objects/es
Saludos.
Ing. Alex Taya
Suscribirse a:
Entradas (Atom)
¿Qué es un Algoritmo?
Más información ==> https://m.facebook.com/story.php?story_fbid=779713015547334&id=332220556963251
-
Hola amigos hoy dicté una charla sobre Windows 7 en mi alma mater la Universidad Ada Byron y les cuento que fue un éxito mas de 200 personas...
-
Hace pocos días se liberó la séptima edición del Reporte de Inteligencia de Seguridad sobre el estado de la Seguridad Informática a nivel m...
-
Más información ==> https://www.azulweb.net/ibm-compra-red-hat-por-34-billions-para-dominar-servicios-cloud/?utm_campaign=arteprogra...















