Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Daniel
Daniel

Posted on

     

Rails API + Cache + Design Patterns + RSpec

Hey que tal el dia hoy vengo a presentarles un tutorial de como crear un API en Ruby On Rails, sin embargo en este tutorial quiero salirme un poco fuera de la caja de la simple creacion de un CRUD y por lo tanto lo que crearemos lo describo enseguida:

Configuracion de la aplicacion.

Conectarnos a un API externa para obtener elementos por medio de:

  • Faraday: Con esta gema crearemos la conexion y llamadas posteriores a el cliente.
  • VCR: Aqui gracias a este registraremos las llamadas HTTP a el cliente y utilizaremos cassettes (archivos generados por esta gema en formato yml) para poder crear pruebas de nuestras peticiones.

Pruebas en la aplicacion:

  • RSpec: Una de mis gemas favoritas.

El api al que nos conectaremos sera al de pokemon:

Agregaremos el patron de diseño proxy con la finalidad de guardar en cache la consulta al cliente por un periodo de 24 horas.

De igual forma vamos utilizar el Factory Method Pattern para poder crear las respuestas que retornaremos sean exitosas o fallidas. -https://refactoring.guru/design-patterns/factory-method

El repositorio de la aplicacion puede ser encontrado enseguida:

Pokemon Client API

Comentado lo anterior empezaremos por crear nuestra aplicacion, indicando que desamos crear la aplicacion sin el suite de pruebas por defecto y ademas que sera una API con el siguiente comando:

rails new api_project —api -T

Una vez creada la aplicacion procederemos a configurar nuestra aplicacion para poder crear pruebas y empezar a escribir codigo.
Empezaremos por abrir nuestro Gemfile y agregar las siguientes gemas en su ambiente respectivo como se mira enseguida:

group:testdogem'vcr','~> 6.3','>= 6.3.1'endgroup:development,:testdo# See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gemgem"debug",platforms:%i[ mri windows ]gem'rspec-rails','~> 7.1'end# HTTP CLIENTgem'faraday','~> 2.12','>= 2.12.1'# Redis - quitar el comentario de esta lineagem"redis",">= 4.0.1"
Enter fullscreen modeExit fullscreen mode

Procederemos a instalar nuestras gemas con unbundle install y posteriormente a configurar RSpec y VCR.

  • Configuracion de RSpec.

    Utilizaremos el comandorails generate rspec:install lo cual nos creara una serie de archivos para configurar nuestra suite de pruebas y asi poder empezar a generar pruebas para nuestra aplicacion.

  • Configuracion VCR.

    Una vez instalada RSpec procederemos a abrir nuestro archivorails_helper.rb y justo arriba del bloque de configuracion de RSpec procederemos a agregar el siguiente codigo:

require'vcr'VCR.configuredo|c|c.cassette_library_dir='spec/vcr_cassettes'c.hook_into:faradayc.configure_rspec_metadata!c.default_cassette_options={record: :new_episodes}end
Enter fullscreen modeExit fullscreen mode
  • Por ultimo debemos de activar el cache en nuestro ambiente de desarrollo ya que solo esta habilitado en produccion por defecto y esto lo haremos en la terminal ejecutando el siguiente comando:rails dev:cache

Una vez que terminamos esta configuracion basica es momento de empezar a tirar codigo y esto lo haremos no de la manera convencional de TDD es decir crear pruebas, que pase el codigo y despues refactorizar (red - green -refactor), debido a que en lo personal yo me siento mas comodo creando primero el codigo y despues generando las pruebas (por mi falta de experiencia posiblemente).

Sea como seasiempre debemos crear pruebas para nuestro codigo, en lo personal a mi siempre me gusta tener pruebas para reducir el gap de errores que pueden presentarse una vez que tengamos nuestra aplicacion lista para ser utilizada.

Dicho lo anterior empezaremos por agregar una nueva ruta en nuestra aplicacion dentro del archivoroutes.rb como se muestra enseguida:

namespace:apidonamespace:v1doget'/pokemon',to:'pokemons#pokemon'endend
Enter fullscreen modeExit fullscreen mode

Como vemos aqui estamos creando un namespace tanto para api como para la version de esta y esto lo hacemos como buena practica en caso de que en un futuro quisieramos tener una version 2 de la api con nuevas caracteristicas.

Despues procederemos a crear el controlador pokemons dentro deapp/controllers/api/v1/pokemons_controller.rb con la siguiente estructura:

moduleApimoduleV1classPokemonsController<ApplicationControllerdefpokemonifparams[:pokemon_name].present?response=get_pokemon(pokemon_name:params[:pokemon_name])renderjson:response,status: :okelserenderjson:{'error'=>'please provide a valid parameter'},status: :unprocessable_entityendendprivatedefget_pokemon(pokemon_name:)::V1::GetPokemonService.new(pokemon_name:).callendendendend
Enter fullscreen modeExit fullscreen mode

Aqui estamos creando nuestro metodopokemon el cual revisa que desde donde hagamos la peticion este presente el parametropokemon_name al momento de hacer una busqueda y en caso omiso retornaremos un error indicando que el parametro no es valido.

Es decir la unicaURI valida sera la siguiente:
http://localhost:3000/api/v1/pokemon?pokemon_name=nombre_de_pokemon

Siguiendo con el flujo de nuestra api estamos mandando a llamar a un metodo privado con el nombre deget_pokemon el cual acepta el parametro pokemon_name.

Este es pasado a una nueva instancia del servicioGePokemonService el cual como es un servicio es llamado directamente con el metodocall.

Esta clase debera de estar dentro del directorioservices/v1/get_pokemon_service.rb y tendra la siguiente estructura:

moduleV1classGetPokemonServiceattr_reader:pokemon_namedefinitialize(pokemon_name:)@pokemon_name=pokemon_nameenddefcallget_pokemonendprivatedefget_pokemonclient=WebServices::PokemonConnection.newproxy=PokemonProxy.new(client)proxy.get_pokemon(pokemon_name:)endendend
Enter fullscreen modeExit fullscreen mode

En este punto llegamos a algo interesante y esto es el uso del patron proxy el cual nos permite tener un sustituto de un objeto para controlar su acceso.

Pero primero lo primero, tenemos una variable llamadaclient la cual es una instancia de la claseFaraday para conectarnos a el cliente externo con una configuracion especifica que pasamos dentro del un bloque. Esta clase debera de estar dentro del directorioweb_services/pokemon_connection.rb

moduleWebServicesclassPokemonConnectiondefclient(read_timeout=30)Faraday.new(url:'https://pokeapi.co/api/v2/')do|conn|conn.options.open_timeout=30conn.options.read_timeout=read_timeoutconn.request:jsonconn.response:logger,nil,{headers:false,bodies:false,errors:false}conn.response:jsonconn.adapter:net_httpendenddefget_pokemon(pokemon_name:)response=client.get("pokemon/#{pokemon_name}")rescueFaraday::Error=>e{'error'=>e}endendend
Enter fullscreen modeExit fullscreen mode

Si analizamos un poco el codigo en este metodo hacemos una conexion directa al api mediante la instanciacion de Faraday pasandole como parametro la url a la que nos queremos conectar. Es necesario aclarar que este metodo no se ejecutara hasta que nosotros lo decidamos y en este caso sera mediante la llamada a el metodoget_pokemon cuando hacemos uso del metodoclient.get.

Si les interesa conocer mas detalles de Faraday pueden revisar su documentacion en el siguiente vinculo:

https://lostisland.github.io/faraday

El metodoget_pokemon toma el nombre del pokemon y lo que hacemos enseguida es mandar a llamar aclient.get(”/pokemon/pikachu”). Por ejemplo en donde el valor de client antes de hacer uso del metodo get serahttps://pokeapi.co/api/v2/.
client.get("pokemon/pikachu")

Cuando estamos ejecutando el codigo anterior dentro de nuestro metodo get_pokemon en realidad estamos haciendo un get hacia la siguiente URI:
GET https://pokeapi.co/api/v2/pokemon/pikachu

Si todo es correcto obtendremos una respuesta con toda la informacion del pokemon pikachu y lo podemos probar justo en una ventana de nuestro navegador agregando la direccionhttps://pokeapi.co/api/v2/pokemon/pikachu con la finalidad de revisar el resultado de lo que nos respondera el API externo.

Acto seguido contamos con nuestro proxy el cual debe de tener la misma interfaz que la clasePokemonConnection esto quiere decir que deberemos de tener el metodoget_pokemon dentro de ella. Su ubicacion sera dentro deapp/proxies/pokemon_proxy.rb y tendra el siguiente contenido:

classPokemonProxyEXPIRATION=24.hours.freezeattr_reader:client,:cachedefinitialize(client)@client=client@cache=Rails.cacheenddefget_pokemon(pokemon_name:)returnpokemon_response('on_cache',cache.read("pokemon_cached/#{pokemon_name}"))ifcache.exist?("pokemon_cached/#{pokemon_name}")response=client.get_pokemon(pokemon_name:)ifresponse.status==200response.body['consulted_at']=consulted_atcache.write("pokemon_cached/#{pokemon_name}",response,expires_in:EXPIRATION)pokemon_response('client_call',response)elsepokemon_error_response(response)endendprivatedefconsulted_atTime.now.utc.strftime('%FT%T')enddefpokemon_response(origin,response){'origin':origin,'name':response.body['name'],'weight':response.body['weight'],'types':type(response.body['types']),'stats':stats(response.body['stats']),'consulted_at':response.body['consulted_at']}enddefstats(stats)stats.each_with_object([])do|stat,array|array<<"#{stat.dig('stat','name')}:#{stat['base_stat']}"endenddeftype(types)types.map{|type|type.dig('type','name')}enddefpokemon_error_response(response){'error':response.body,'status':response.status}endend
Enter fullscreen modeExit fullscreen mode

Desde mi punto de vista esta clase esta haciendo demasiado y tiene mas de una responsabilidad; sin embargo para terminos de enseñanza considero que esta bien ya que al programar tambien debemos de refactorizar y esto lo haremos mas adelante ya que terminemos de crear las pruebas para nuestra API.

Empezaremos por explicar el metodo inicializador este cuenta con un cliente que le pasamos desde su llamada en la clase anterior y ademas agregamos a la variable de instancia@cache inicializando Rails.cache.

# Llamada de clase anteriorclient=WebServices::PokemonConnection.newproxy=PokemonProxy.new(client)definitialize(client)@client=client@cache=Rails.cacheend
Enter fullscreen modeExit fullscreen mode

Despues tenemos el metodo get_pokemon que como comentamos anteriormente esta clase Proxy debe de tener la misma interfaz que el cliente y la explicaremos enseguida.

returnpokemon_response('on_cache',cache.read("pokemon_cached/#{pokemon_name}"))ifcache.exist?("pokemon_cached/#{pokemon_name}")response=client.get_pokemon(pokemon_name:)ifresponse.status==200response.body['consulted_at']=consulted_atcache.write("pokemon_cached/#{pokemon_name}",response,expires_in:EXPIRATION)pokemon_response('client_call',response)elsepokemon_error_response(response)endend
Enter fullscreen modeExit fullscreen mode

En la primera linea lo que hacemos es retornar unpokemon_response (veremos a continuacion el metodo) siempre y cuando la llave para cache (en este caso“pokemon_cached/pikachu”) exista con los argumentoson_cache y la llave anteriormente mencionada en cache.

En caso de que no exista esa llave para cache lo que hacemos es hacer uso el client que pasamos por parametro al momento de inicializar nuestra instancia y mandamos a llamar al metodo get_pokemon, en donde si el estatus de la respuesta es un200 entonces a la respuesta que es un hash le agregaremos un nuevo elemento llamadoconsulted_at que sera la fecha y hora en la que se esta haciendo la consulta al cliente.

Posteriormente procedemos a guardar la respuesta de la llamada al cliente con la llavepokemon_cached/nombre_del_pokemon y ademas pasamos una tiempo de expiracion para esta respuesta con el fin de que durante 24 horas este disponible en cache y asi en futuras llamadas no vayamos a el cliente a preguntarle sobre el pokemon en cuestion.

Enseguida mandamos a llamar a el metodopokemon_response con los argumentosclient_call y la respuesta obtenida por parte del cliente.

En caso de que el estatus de la respuesta del cliente no haya sido favorable (diferente a 200) retornaremos unpokemon_error_response con la respuesta del cliente como argumento.

defconsulted_atTime.now.utc.strftime('%FT%T')enddefpokemon_response(origin,response){'origin':origin,'name':response.body['name'],'weight':response.body['weight'],'types':type(response.body['types']),'stats':stats(response.body['stats']),'consulted_at':response.body['consulted_at']}enddefpokemon_error_response(response){'error':response.body,'status':response.status}end
Enter fullscreen modeExit fullscreen mode

Haciendo referencia a los metodos anteriormente explicados tenemos en primer lugar aconsulted_at el cual su unica funcion es brindarnos la fecha y hora cuando es llamado.

pokemon_response cuenta con los parametros origin y response con los cuales construye un objeto del tipo Hash. La primera invocacion a este metodo nos debera de arrojar lo siguiente:

{:origin=>"client_call",:name=>"pikachu",:weight=>60,:types=>["electric"],:stats=>["hp: 35","attack: 55","defense: 40","special-attack: 50","special-defense: 50","speed: 90"],:consulted_at=>"2024-11-21T02:00:20"}
Enter fullscreen modeExit fullscreen mode

En una segunda y futuras invocaciones a este metodo tendremos como resultado lo siguiente:

{:origin=>"on_cache",:name=>"pikachu",:weight=>60,:types=>["electric"],:stats=>["hp: 35","attack: 55","defense: 40","special-attack: 50","special-defense: 50","speed: 90"],:consulted_at=>"2024-11-21T02:00:20"}
Enter fullscreen modeExit fullscreen mode

Mientras que nuestro metodo error_response nos dara el siguiente resultado:

{"error":"Not Found","status":404}
Enter fullscreen modeExit fullscreen mode

Hasta este punto nuestro API ya estara funcional y si lo queremos probar lo podemos hacer de la siguiente manera:

  • Iniciamos nuestro servidor de Rails con el comandorails s.
  • En otra terminal abriremos una consola de Rails y agregaremos lo siguiente:
require'net/http'require'uri'url='http://localhost:3000/api/v1/pokemon?pokemon_name=pikachu'uri=URI(url)response=Net::HTTP.get_response(uri)response.body# Resultado"{\"origin\":\"client_call\",\"name\":\"pikachu\",\"weight\":60,\"types\":[\"electric\"],\"stats\":[\"hp: 35\",\"attack: 55\",\"defense: 40\",\"special-attack: 50\",\"special-defense: 50\",\"speed: 90\"],\"consulted_at\":\"2024-11-21T02:13:03\"}"
Enter fullscreen modeExit fullscreen mode

Etapa De Pruebas

Para poder generar las pruebas debemos de pensar en la funcionalidad que actualmente tenemos y que nos da como resultado. Por lo tanto si analizamos nuestro codigo contamos con los siguientes escenarios:

1.- Tendremos un casoexitoso cuando el nombre del pokemon sea valido:

  • La primera peticion a este endpoint debe de traernos el origin como client_call.
  • La segunda peticion a este endpoint debe de traernos el origin como on_cache.

2.- Tendremos un casofallido cuando:

  • El nombre del pokemon sea invalido.
  • El parametro pokemon_name no este presente como query params.

Basado en esto que conocemos de nuestra aplicacion procederemos a crear la siguiente prueba dentro despec/requests/pokemons_spec.rb:

require'rails_helper'RSpec.describeApi::V1::PokemonsController,type: :request,vcr:{record: :new_episodes}dolet(:memory_store){ActiveSupport::Cache.lookup_store(:memory_store)}let(:pokemon_name){'pikachu'}beforedoallow(Rails).toreceive(:cache).and_return(memory_store)enddescribe'GET /api/v1/pokemon'docontext'with a valid pokemon'doit'returns data from the client on the first request and caches it for subsequent requests'do# first call - fetch data from the clientget"/api/v1/pokemon?pokemon_name=#{pokemon_name}"expect(response.status).toeq(200)pokemon=parse_response(response.body)expect(pokemon['origin']).toeq('client_call')pokemon_information(pokemon)expect(Rails.cache.exist?("pokemon_cached/#{pokemon_name}")).toeq(true)# second call - fetch data from cacheget"/api/v1/pokemon?pokemon_name=#{pokemon_name}"pokemon=parse_response(response.body)expect(pokemon['origin']).toeq('on_cache')pokemon_information(pokemon)endendcontext'with an invalid pokemon'doit'returns an error'doget'/api/v1/pokemon?pokemon_name=unknown_pokemon'error=parse_response(response.body)expect(error['error']).toeq('Not Found')expect(error['status']).toeq(404)endendcontext'with invalid parameters'doit'returns an error'doget'/api/v1/pokemon?pokemon_namess=unknown_pokemon'error=parse_response(response.body)expect(error['error']).toeq('Invalid Parameters')endendend# Helper methodsdefpokemon_information(pokemon)expect(pokemon['name']).toeq('pikachu')expect(pokemon['weight']).toeq(60)expect(pokemon['types']).toeq(['electric'])expect(pokemon['stats']).toeq(['hp: 35','attack: 55','defense: 40','special-attack: 50','special-defense: 50','speed: 90'])expect(pokemon['consulted_at']).tobe_presentenddefparse_response(response)JSON.parse(response)endend
Enter fullscreen modeExit fullscreen mode

En la primera linea nos encontraremos con el uso deVCR en donde solo se utilizara para las peticiones que hagamos a el cliente de pokemon en donde esperamos una respuesta exitosa o fallida segun como nos responda este.

Despues empezamos por crear dos lets los cuales se explican enseguida:

  • memory_store: Esta creara una instancia de cache.
  • pokemon_name: Definimos el nombre del pokemon que utilizaremos para mantener nuestro codigo DRY.

Enseguida contamos con un bloque before en donde unicamente stubeamos Rails.cache con la finalidad de que nos retorne una instancia de cache.

Y ahora si empezamos a probar nuestro endpoint que creamos con el siguiente bloque de codigo:

it'returns data from the client on the first request and caches it for subsequent requests'do# first call - fetch data from the clientget"/api/v1/pokemon?pokemon_name=#{pokemon_name}"expect(response.status).toeq(200)pokemon=parse_response(response.body)expect(pokemon['origin']).toeq('client_call')pokemon_information(pokemon)expect(Rails.cache.exist?("pokemon_cached/#{pokemon_name}")).toeq(true)# second call - fetch data from cacheget"/api/v1/pokemon?pokemon_name=#{pokemon_name}"pokemon=parse_response(response.body)expect(pokemon['origin']).toeq('on_cache')pokemon_information(pokemon)end
Enter fullscreen modeExit fullscreen mode

Lo que hacemos en esta prueba es bien sencillo:

  • Mandamos a llamar a nuestro endpoint pokemon en donde le pasamos el query param pokemon_name con el nombre del pokemon (pikachu).
  • Esperamos que la respuesta obtenida sea un 200.
  • Parseamos el body de la respuesta obtenida.
  • El campo de nuestro json con el valor de origin debera de ser igual aclient_call.
  • Posterior a esto validamos que el json retornado sea justo como nosotros lo deseamos, es decir:
defpokemon_information(pokemon)expect(pokemon['name']).toeq('pikachu')expect(pokemon['weight']).toeq(60)expect(pokemon['types']).toeq(['electric'])expect(pokemon['stats']).toeq(['hp: 35','attack: 55','defense: 40','special-attack: 50','special-defense: 50','speed: 90'])expect(pokemon['consulted_at']).tobe_presentend
Enter fullscreen modeExit fullscreen mode
  • Enseguida esperamos que se haya almacenado la respuesta obtenida con la llavepokemon_cached/pikachu.
  • Volvemos a llamar a nuestro endpoint pokemon justo con las mismas caracteristicas de la primera peticion.
  • Ahora esperamos que el campo de nuestro json con el valor de origin sea igual aon_cache.
  • Por ultimo validamos de nuevo que el json retornado cumpla con las caracteristicas necesarias.

Ahora vamos con los casos fallidos y el primero de estos nos retornara un 404 debido a que no se encontro el pokemon que le pasamos por parametro, no hay mucho que hablar esta es una prueba muy sencilla y esto es lo que esperamos.

context'with an invalid pokemon'doit'returns an error'doget'/api/v1/pokemon?pokemon_name=unknown_pokemon'error=parse_response(response.body)expect(error['error']).toeq('Not Found')expect(error['status']).toeq(404)endend
Enter fullscreen modeExit fullscreen mode

Posteriomente contamos con el ultimo caso que valida que si el parametro es invalido nos retorne un error en especifico.

context'with invalid parameters'doit'returns an error'doget'/api/v1/pokemon?pokemon_namess=unknown_pokemon'error=parse_response(response.body)expect(error['error']).toeq('Invalid Parameters')endend
Enter fullscreen modeExit fullscreen mode

Lo ultimo que tenemos es un par de helper method para evitar estar repitiendo codigo en nuestra spec.

Ahora procederemos a ejecutar nuestras pruebas conbundle exec rspec y esto nos dara como resultado que nuestras pruebas pasaran sin problema.

Ademas de esto se crearan unos archivos en formatoYAML los cuales son los registros guardados dentro despec/vcr_cassettes de las peticiones que hagamos al cliente de pokemon y estos nos permitiran si asi lo deseamos usar estos mismos para mas pruebas que deseemos hacer en un futuro con la respuesta obtenida de parte del cliente.

Una vez que hayamos terminado de ejecutar nuestraas pruebas sera necesario cambiar el hash del simbolo vcr denew_episodes anone como se muestra enseguida.

RSpec.describeApi::V1::PokemonsController,type: :request,vcr:{record: :none}
Enter fullscreen modeExit fullscreen mode

Refactorizando
Hay especificamente dos partes que considero debemos de refactorizar la primera de ellas es el controlador de la siguiente manera:

moduleApimoduleV1classPokemonsController<ApplicationControllerdefpokemonifparams[:pokemon_name].present?response=get_pokemon(pokemon_name:params[:pokemon_name])renderjson:response.pokemon_body,status:response.statuselserenderjson:{'error':'Invalid Parameters'},status: :unprocessable_entityendendprivatedefget_pokemon(pokemon_name:)::V1::GetPokemonService.new(pokemon_name:).callendendendend
Enter fullscreen modeExit fullscreen mode

Si nos damos cuenta dentro de nuestra respuesta al servicioGetPokemon estamos accediendo a pokemon_body y status de esta manera nos olvidamos de hardcodear en especifido el status de la peticion que le hagamos al cliente.

Pues bien para que esto funcione deberemos de aplicar una segunda refactorizacion en el proxy que hemos creado anteriormente de la siguiente manera:

classPokemonProxyEXPIRATION=24.hours.freezeattr_reader:client,:cachedefinitialize(client)@client=client@cache=Rails.cacheenddefget_pokemon(pokemon_name:)returnWebServices::FactoryResponse.create_response(origin:'on_cache',response:cache.read("pokemon_cached/#{pokemon_name}"),type:'success')ifcache.exist?("pokemon_cached/#{pokemon_name}")response=client.get_pokemon(pokemon_name:)ifresponse.status==200response.body['consulted_at']=consulted_atcache.write("pokemon_cached/#{pokemon_name}",response,expires_in:EXPIRATION)WebServices::FactoryResponse.create_response(origin:'client_call',response:,type:'success')elseWebServices::FactoryResponse.create_response(origin:'client_call',response:,type:'failed')endendprivatedefconsulted_atTime.now.utc.strftime('%FT%T')endend
Enter fullscreen modeExit fullscreen mode

Anteriormente contabamos con metodos para retornar una respuesta exitosa o fallida dependiente de como nos respondia el cliente, sin embargo ahora estamos haciendo uso del patron de diseño Factory Method el cual practicamente lo utilizaremos para crear objetos (respuesta) basados en el tipo que le pasamos como argumentos.

Dicho lo anterior primero crearemos nuestroFactoryResponse con el siguiente contenido:

moduleWebServicesclassFactoryResponsedefself.create_response(origin:,response:,type:)casetypewhen'success'PokemonResponse.new(origin:,response:)when'failed'PokemonFailedResponse.new(response:)endendendend
Enter fullscreen modeExit fullscreen mode

Basado en el tipo que recibe esta clase mediante el metodo de clase create_response determinaremos el tipo de respuesta que debemos de retornar, en donde si es un caso exitoso retornaremos un PokemonResponse con el siguiente contenido:

moduleWebServicesclassPokemonResponseattr_reader:origin,:responsedefinitialize(origin:,response:)@origin=origin@response=response@pokemon_body=pokemon_bodyenddefpokemon_body{'origin':origin,'name':response.body['name'],'weight':response.body['weight'],'types':type(response.body['types']),'stats':stats(response.body['stats']),'consulted_at':response.body['consulted_at']}enddefstatusresponse.statusendprivatedefstats(stats)stats.each_with_object([])do|stat,array|array<<"#{stat.dig('stat','name')}:#{stat['base_stat']}"endenddeftype(types)types=types.map{|type|type.dig('type','name')}endendend
Enter fullscreen modeExit fullscreen mode

En caso contrario lo que retornaremos un PokemonFailedResponse con el siguiente contenido:

moduleWebServicesclassPokemonFailedResponseattr_reader:responsedefinitialize(response:)@response=response@pokemon_body=pokemon_bodyenddefpokemon_body{'error':response.body,'status':response.status}enddefstatusresponse.statusendendend
Enter fullscreen modeExit fullscreen mode

Con esto logramos que seguir el principioS deSOLID (Single Responability) y de esta manera podemos modificar en un futuro la respuesta exitosa o fallida en su clase correspondiente.

Ahora bien si ejecutamosbundle exec rspec nuestras pruebas deberan de pasar sin ningun problema y abremos terminado con la creacion del proyecto.

Por ultimo espero que este pequeño proyecto les haya gustado y si tienen alguna duda o comentario por favor haganmelo saber y con todo gusto estare ahi para responderles, buen dia a ti que estas leyendo y happy coding.

Top comments(0)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

Que tal mi nombre es Daniel - "Born and Raise on the happiest place on earth according to The Simpsons - Tijuana!!"
  • Location
    Tijuana Baja California Mexico
  • Work
    Ruby On Rails Developer
  • Joined

More fromDaniel

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp