#11 Refactoring User Name Part 2
En el episodio anterior, hablamos sobre refactorear y como podemos mover código de las vistas a un método en el correspondiente modelo para eliminar duplicación de código. Aunque, podemos mejorar mas nuestro código en el modelo User
.
class User < ActiveRecord::Base def full_name name = first_name + ' ' name += "#{middle_initial}. " unless middle_initial.nil? name += last_name end end
Nuestro modelo User como estaba al terminar el último episodio.
Testing
Antes de comenzar a refactorear nuestro modelo User
, observemos un poco de testing. Testing y refactoreo van de la mano, ya que el propósito de refactorear es mejorar su código sin cambiar su comportamiento y tener el código testeado asegura que nuestro refactoreo no afecta la funcionalidad del código. Al crear un modelo en una aplicación Rails, automáticamente se crea un archivo de test. Veamos el archivo user_test.rb
que se encuentra en la carpeta /test/unit
.1
require 'test_helper' class UserTest < ActiveSupport::TestCase # Replace this with your real tests. test "the truth" do assert true end end
El código de testing predeterminado que Rails genera.
Hay un test predeterminado que vemos en el código de arriba. Simplemente hace una afirmación (assert) que true == true
(true: verdadero). Vamos a reemplazarlo con nuestro primer test. Crearemos un nuevo usuario sin su inicial del nombre del medio y verificaremos el nombre completo que esperamos que devuelva.
test "full name without middle initial" do user = User.new(:first_name => "John", :last_name => "Smith") assert_equal 'John Smith', user.full_name end
Nuestro primer test verifica que haya un nombre sin la inicial del segundo nombre.
Para poder ejecutar nuestros test, podríamos ir a la carpeta de la aplicación desde la linea de comando y deberíamos correr rake test. Pero vamos a utilizar AutoTest 2. Podemos instalarlo con la gema ZenTest (sudo gem install ZenTest
). AutoTest tiene la ventaja que continuamente correrá nuestros tests para que sea mas fácil ver cuando alguno falle.
Laa-Laa:ep11 eifion$ autotest loading autotest/rails /usr/local/bin/ruby -I.:lib:test -rtest/unit -e "%w[test/functional/users_controller_test.rb test/unit/user_test.rb].each { |f| require f }" | unit_diff -u Loaded suite -e Started . Finished in 0.046945 seconds. 1 tests, 1 assertions, 0 failures, 0 errors
Salida de AutoTest
Nuestro primer test paso!!. Ahora crearemos otro test para un nombre que tenga la inicial del segundo nombre. Para ello, agregaremos un método dentro de la clase UserTest
.
test "full name with middle initial" do user = User.new(:first_name => "Paul", :middle_initial => "P", :last_name => "Hughes") assert_equal 'Paul P. Hughes', user.full_name end
Al guardar el archivo de testing, AutoTest debería automáticamente volver a correr los tests. Ambos tests debieran ahora pasar.
Refactoreo
Ahora que sabemos que nuestro método full_name
se comporta de la forma esperada, podemos empezar a refactorearlo. El uso de la variable local en el método y la concatenación de cadenas dentro de un arreglo pareciera innecesario. Así que los sacaremos. En vez, podríamos poner cada parte del nombre de usuario en un arreglo y juntarlas mediante un join.
class User < ActiveRecord::Base def full_name [first_name, middle_initial, last_name].join(' ') end end
Primer refactoreo hecho a la clase User.
Cuando guardamos el archivo, los tests debieran correr automáticamente y deberíamos poder ver que ambos fallan.
1) Failure: test_full_name_with_middle_initial(UserTest) [./test/unit/user_test.rb:11:in `test_full_name_with_middle_initial' /usr/local/lib/ruby/gems/1.8/gems/activesupport-2.2.2/lib/active_support/testing/setup_and_teardown.rb:60:in `run']: --- /var/folders/yD/yDkhXjIsHAqkCTKsBbUlC++++TI/-Tmp-/expect31666.0 2009-01-04 11:23:35.000000000 +0000 +++ /var/folders/yD/yDkhXjIsHAqkCTKsBbUlC++++TI/-Tmp-/butwas31666.0 2009-01-04 11:23:35.000000000 +0000 @@ -1 +1 @@ -Paul P. Hughes +Paul P Hughes 2) Failure: test_full_name_without_middle_initial(UserTest) [./test/unit/user_test.rb:6:in `test_full_name_without_middle_initial' /usr/local/lib/ruby/gems/1.8/gems/activesupport-2.2.2/lib/active_support/testing/setup_and_teardown.rb:60:in `run']: --- /var/folders/yD/yDkhXjIsHAqkCTKsBbUlC++++TI/-Tmp-/expect31666.1 2009-01-04 11:23:35.000000000 +0000 +++ /var/folders/yD/yDkhXjIsHAqkCTKsBbUlC++++TI/-Tmp-/butwas31666.1 2009-01-04 11:23:35.000000000 +0000 @@ -1 +1 @@ -John Smith +John Smith 2 tests, 2 assertions, 2 failures, 0 errors
Salida de AutoTest mostrando dos unit test que fallan.
El primer test falla debido a que no hay un punto a continuación de la inicial del segundo nombre. El segundo test falla debido a que hay dos espacios en blanco entre el primer nombre y el apellido. Para empezar, intentaremos corregir el segundo caso. Hay dos espacios porque la inicial del nombre del medio es nil
y estamos uniendo cada parte del arreglo con un espacio. Podemos corregir esto haciendo una llamada al método compact
en el arreglo antes de hacer un join
(juntar) con las partes. (compact
elimina los valores nil
de un arreglo.)
def full_name [first_name, middle_initial, last_name].compact.join(' ') end
Ahora, solamente nuestro primer test falla: necesitamos el punto después de la inicial del segundo nombre. Vamos a corregirlo creando un nuevo método en la clase User
llamado middle_initial_with_full_stop
(inicial_del_medio_con_punto
)
def full_name [first_name, middle_initial_with_full_stop, last_name].compact.join(' ') end def middle_initial_with_full_stop "#{middle_initial}." unless middle_initial.blank? end
Nuestros tests ahora pasan. Pero hay una condición que no hemos testeado. Y si la inicial del segundo nombre es una cadena vacía?. Podemos escribir un test para eso y ver si pasa.
test "full name with empty middle initial" do user = User.new(:first_name => "John", :middle_initial => "", :last_name => "Jones") assert_equal 'John Jones', user.full_name end
Esto también pasa. El método blank?
en cadenas devuelve true
para tanto un valor nil
como para una cadena vacía. Por lo que nuestro nombre con una inicial vacía sigue funcionando como lo esperado.
Nuestro código en el modelo User
ahora se ve mucho mejor que antes. Existe aun duplicación en el código de testing, que veremos en el próximo episodio.
Código final
class User < ActiveRecord::Base def full_name [first_name, middle_initial_with_full_stop, last_name].compact.join(' ') end def middle_initial_with_full_stop "#{middle_initial}." unless middle_initial.blank? end end
La clase User después de refactorear.
require 'test_helper' class UserTest < ActiveSupport::TestCase test "full name without middle initial" do user = User.new(:first_name => "John", :last_name => "Smith") assert_equal 'John Smith', user.full_name end test "full name with middle initial" do user = User.new(:first_name => "Paul", :middle_initial => "P", :last_name => "Hughes") assert_equal 'Paul P. Hughes', user.full_name end test "full name with empty middle initial" do user = User.new(:first_name => "John", :middle_initial => "", :last_name => "Jones") assert_equal 'John Jones', user.full_name end end
Los tests para la clase User
.
Notas
- El Railscast en el que se basa este episodio esta basado en Rails 1. El código del episodio fue escrito utilizando Rails 2.2.
- http://rubyforge.org/projects/zentest/