From 68bd1054fd6b443f990e61e5f2000aa916d42d12 Mon Sep 17 00:00:00 2001 From: Oscar Tellez Date: Mon, 2 Mar 2020 17:23:47 -0400 Subject: [PATCH 01/17] Chapter 0 to spanish --- rails6/es/api_on_rails.adoc | 55 ++++++++++++ rails6/es/chapter00-before.adoc | 46 ++++++++++ rails6/es/img/cover.svg | 155 ++++++++++++++++++++++++++++++++ rails6/es/img/data_model.png | Bin 0 -> 6644 bytes rails6/es/img/logo.svg | 139 ++++++++++++++++++++++++++++ rails6/es/img/pow_running.png | Bin 0 -> 331720 bytes 6 files changed, 395 insertions(+) create mode 100644 rails6/es/api_on_rails.adoc create mode 100644 rails6/es/chapter00-before.adoc create mode 100644 rails6/es/img/cover.svg create mode 100644 rails6/es/img/data_model.png create mode 100644 rails6/es/img/logo.svg create mode 100644 rails6/es/img/pow_running.png diff --git a/rails6/es/api_on_rails.adoc b/rails6/es/api_on_rails.adoc new file mode 100644 index 0000000..a3b1320 --- /dev/null +++ b/rails6/es/api_on_rails.adoc @@ -0,0 +1,55 @@ += API on Rails 6 +Alexandre Rousseau +v6.0.5, 2020-01-09 +:doctype: book +:toc: +:imagesdir: img +:title-logo-image: image:logo.svg[] +:homepage: https://github.com/madeindjs/api_on_rails/ +:source-highlighter: rouge +// epub tags +:copyright: CC-BY-SA 4.0, MIT +:keywords: Rails, API, Ruby, Software +:lang: es +:author: Alexandre Rousseau +:description: Learn best practice to build an API using Ruby on Rails 5 +:front-cover-image: image:cover.svg[] +:revdate: 2020-01-09 + +include::chapter00-before.adoc[] + +<<< + +include::chapter01-introduction.adoc[] + +<<< + +include::chapter02-api.adoc[] + +<<< + +include::chapter03-presenting-users.adoc[] + +<<< + +include::chapter04-athentification.adoc[] + +<<< + +include::chapter05-user-products.adoc[] + +<<< + +include::chapter06-improve-json.adoc[] + +<<< + +include::chapter07-placing-orders.adoc[] + +<<< + +include::chapter08-improve-orders.adoc[] + +<<< + +include::chapter09-optimization.adoc[] diff --git a/rails6/es/chapter00-before.adoc b/rails6/es/chapter00-before.adoc new file mode 100644 index 0000000..58e9cb0 --- /dev/null +++ b/rails6/es/chapter00-before.adoc @@ -0,0 +1,46 @@ +[#chapter00-before] += Before + +== Foreword + +"API on Rails 6" esta basado en http://apionrails.icalialabs.com/book/["APIs on Rails: Building REST APIs with Rails"]. Fue públicado inicialmente en 2014 por https://twitter.com/kurenn[Abraham Kuri] bajo la licencia http://opensource.org/licenses/MIT[MIT] y http://people.freebsd.org/~phk/[Beerware]. + +La primera version no es mantenida y fue planeada para Ruby on Rails 4 la cual no https://guides.rubyonrails.org/maintenance_policy.html#security-issues[recive mas actualizaciaciones de seguridad]. He buscado actualizar este excelente libro, adaptandolo a nuevas versiones de Ruby on Rails. Este libro esta por lo tanto disponible para Ruby on Rails en sus versiones 5.2 y 6.0 (el cual te encuentras leyendo). + +NOTE: Este libro tambien esta disponible en el lenguaje Molière (Esto significa francés). + +== Acerca del autor + +Mi nombre es http://rousseau-alexandre.fr[Alexandre Rousseau] y soy un desarrollador en Rails con mas de 4 años de experiencia (al momento de escribirlo). Actualmente soy socio en una compañia (https://isignif.fr[iSignif]) donde construyo y mantengo un producto SAAS usando Rails. Tambien contibuyo a lacomunidad Ruby produciendo y manteniendo algunas gemas que puedes consular en https://rubygems.org/profiles/madeindjs[my Rubygems.org profile]. La mayoria de mis proyectos estan en GitHub asi que no dudes en http://github.com/madeindjs/[seguirme]. + +Todo el codigo fuente de este libro en formato https://asciidoctor.org/[Asciidoctor] disponible en https://github.com/madeindjs/api_on_rails[GitHub]. Por lo tanto sientete libre de hacer https://github.com/madeindjs/api_on_rails/fork[fork] al proyecto si tu quieres mejorarlo o corregir errores que no noté. + +== Derechos de autor y licencia + +Este libro está bajo la http://opensource.org/licenses/MIT[licencia MIT]. Todo el codigo fuente del libro esta en el formato https://fr.wikipedia.org/wiki/Markdown[Markdown] disponible en https://github.com/madeindjs/api_on_rails[GitHub] + +.MIT license +**** +Copyright 2019 Alexandre Rousseau + +Por la presente se concede permiso, libre de cargos, a cualquier persona que obtenga una copia de este software y de los archivos de documentación asociados (el "Software"), a utilizar el Software sin restricción, incluyendo sin limitación los derechos a usar, copiar, modificar, fusionar, publicar, distribuir, sublicenciar, y/o vender copias del Software, y a permitir a las personas a las que se les proporcione el Software a hacer lo mismo, sujeto a las siguientes condiciones: + +El aviso de copyright anterior y este aviso de permiso se incluirán en todas las copias o partes sustanciales del Software. +EL SOFTWARE SE PROPORCIONA "COMO ESTÁ", SIN GARANTÍA DE NINGÚN TIPO, EXPRESA O IMPLÍCITA, INCLUYENDO PERO NO LIMITADO A GARANTÍAS DE COMERCIALIZACIÓN, IDONEIDAD PARA UN PROPÓSITO PARTICULAR E INCUMPLIMIENTO. EN NINGÚN CASO LOS AUTORES O PROPIETARIOS DE LOS DERECHOS DE AUTOR SERÁN RESPONSABLES DE NINGUNA RECLAMACIÓN, DAÑOS U OTRAS RESPONSABILIDADES, YA SEA EN UNA ACCIÓN DE CONTRATO, AGRAVIO O CUALQUIER OTRO MOTIVO, DERIVADAS DE, FUERA DE O EN CONEXIÓN CON EL SOFTWARE O SU USO U OTRO TIPO DE ACCIONES EN EL SOFTWARE. +**** + +"API on Rails 6" por https://github.com/madeindjs/api_on_rails[Alexandre Rousseau] es compartido de acuerdo a http://creativecommons.org/licenses/by-sa/4.0/[Creative Commons Attribution - Attribution-ShareAlike 4.0 International]. Construido sobre este libro http://apionrails.icalialabs.com/book/. + +La portada de este libro usa una hermosa foto tomada por https://unsplash.com/@siloine?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText[Yoann Siloine] quien publicó en https://unsplash.com[Unsplash]. + +== Agradecimientos + +Un gran "gracias" a todos los contribuidores de GitHub quienes mantienen este libro vivo. En orden alfabetico: + +* https://github.com/airdry[airdry] +* https://github.com/Landris18[Landris18] +* https://github.com/lex111[lex111] +* https://github.com/cuilei5205189[cuilei5205189] +* https://github.com/franklinjosmell[franklinjosmell] +* https://github.com/notapatch[notapatch] +* https://github.com/tacataca[tacataca] diff --git a/rails6/es/img/cover.svg b/rails6/es/img/cover.svg new file mode 100644 index 0000000..be9d63c --- /dev/null +++ b/rails6/es/img/cover.svg @@ -0,0 +1,155 @@ + + + + + + + + image/svg+xml + + + + + + + + + + Alexandre Rousseau + + + "APIonRails":6 + + + + + + + { + } + + + + diff --git a/rails6/es/img/data_model.png b/rails6/es/img/data_model.png new file mode 100644 index 0000000000000000000000000000000000000000..468185da35d7185102056d262dffbe3d914ccea3 GIT binary patch literal 6644 zcmdT}cUV))n?|^xf?%OZs8Uo!N+2KsLkFdVPEi~ju07_`!NQaTj=+gIA>2@&5b?F97I86@ z3q<^eGE@QTt&2dSt_S-ftb$FgorB$-!7k#O8e&%i)ffga2pn825aZ#ARSSfO|7ll^ zk^fySFD~{c1m_MB|5s2qq2^+`UcLx1WjSS8X9Y!|*cGrGP#FXU0xydxDgc$_feP}9 zU|Aqg4Rl2fs3P{)A*A_rsb}z)FGdL>j>O@-)#T*^0s`a$l;pg8-QK zD{nPjUj!WI+7p11A+QVIto`*Rdufzs4D2`09Eys{^sg=Is0J{p18lc zF8|3@`A6<=EnvJEp7jvED1U^Dfv*=v?9Vod7uV&VV*&jmSDq1!{O^(d zFC+bT6+=G1v;WpDqwsI#BRm;$_hsnXs7R=XF~RPcn^^1H+S)cZH=j9k#>dC!&Ye4p zi;EKz6MlYv%gf7nJbrzBor8np_U+rNtE+~FhMSw4-@kvKnwqMut(~8rud1q&l9Fm{ zY#bdORaI5Bu&{tK6a#_4V6b1metr4!rKhJyUtjqoSdA*#6%Ytm!YAd_V)I^zP^Eh0TPK68XB6HmzSQN z9vK;l!C)#XDuRQ9V`F18Gc!|CQiO$t>+0(A^YdFR5rF@ zl{GRd))9`_KRkT!h;VRlu)n`QH#fJnwY9sudw6g-Ha0dpJNxzP*S)>HgTuq0KYxyo zk3Wcx-PzfB7#&Ba(<7q^k&hnx`3HMod>9ZftiP+9``X&t{rmSH#t;Gm!{p`V4;akD z!_x#;873y4aYH?A>%g()>}cKbmvtQ_ZJxyBcTY~GnkQ-`jp@eK~R%-V8>)hms&hGjaro=j4MaX~J*PkHX%PW*<&3lnK7k`fpDVBQ# zaO|)29vd?w|2zHt=wB)0(SL&(=&S$^j}VfbMk?EOpB%r{|NM;OQF6n5w@WW`1MnTU zgZeHAS4Tyhm)MlTx`n+D9i=2oymeGj;+kJdi`_%UAXOD-4WBDLJdl^ql}@I#q=-;S zSmZ0X@`N1>t6}M)C2@h+$`X{DaOtfU3rNd~Qv3()4Vn?`SDhM&b;w$O@>$tdIcMN3Ih~I z*)BS5e)-g+BszGk=_m{<2uk~sX96xSG>nI33Laf6+3!&@Lw$8hY1BlH+I*H)50I6y ziU_EkpIIh-sKr45{)DCtMWWUkxubldzv5u$6}gdl)*s0MdLpy2V(nNr;jVe>wMo8; zyK)xiNk6(xIed)--$t6EW6!772KYCd2&#v@Z>Qh0wM)yJ`kn{BTAHn_;E|9Du7uqU z;x9l+@XHbt#!{pNsTTI<2FA(lb(L$y+7=K`Wewuq#-LWy6xzj)tXnBq8OCE_M>tuC zgo8b0-EmHzPO!P8cwvn1_jxK^<1Bs1Izvx8Z|^Z z$E+39_j{C_u4?9uO$V7czUH-sJ6+hHUmp9O3-^e3m_y{{gfbHv{U5oEWQ>P}?|#a9 zPLy7(-svU`Nz#Vg@WDr+-!AVc_HUw-O(+@on;d@aveGH7=rKvHK?L@BCo9r2w~AR& zYMh;ugB(N}M~QNu*3nsa_$FjbpU}NR&BRm3A4-<^u=HgL5r2v10bqvr9+q$h38#i1 zfyYyPGK7hg2ShgVOCmI6;#)^_{v7Q&J7kU)Tzug%>aH%qXUd2>h~LDb{6t$OT*`Z- z-|U!iUH>;+LH5pZd|}-8Mvy@yy%|_*NG=k$+|TI%Yw~>tatp8cI6zx$FJ!+GOk|@< zDCyOdTGiOTl?72$PsM(8tKb01FI;f1+@2h~4DbTiLYmw9+_a(v1T~NIpX)fKZ_YQw zMS1Vq9w&(m)LW>lXI|L>YSs`fWK7`~r16CgRmRP?r`>Y&+Cpo6oa!p!gJHsD`Fh71 z2T32;<6%CMfOE<(rA`td&=+6kDm22z*d;S5?6_pp<4C+YEtX>u3Ek|TqFvr99R8*i zkti&v(UNzQ{{1pE?DfZD4YBH7d#FtrnrHvKnO)m<(kbbK?w*SU7naYqbbt4rA+A=I zT<;t+0UcH5A;cF*4!&R`7M*(fb zr7^3w!_w+bW!d1m_@{Dlth(>b)ZBuhO13$hQ8O_0+S)G2hsuUxJ8Dca)|07MQ0nE6 zu&)N#RKX6RO9hD}xTb3BD*~OZ3A}4W==M@(o$QoA%4yxR??NTVbyk)MC(mAUf6$!i;xT)K%;($mRN+g4Vz2&qr&5qETv%{Va7B+$@03{>#mb2-4LHV>=k^s#4E<4E77ODxw zie!~@Ers`gao0=EsbUZAEIL(k;5vv7x5_N%Nc2136}c|(;{{+}1S(|%O@A833OL$N z8C00-H^Lxhc;^I%z7_i;4Yma9TI(f1+in=LBl$FvZ9d5l|Go9sx@xf;muMwwr}{`h z%t-3^9p$(@piTu$8R?;CbN9`!AF*hPS=u}ys878LYp$KvKU7?(8(La%6V5co@B?Bj za40Y&=$BsZxtftE9(A6_lJ&n#(X=rX43#N>TvoZB@pUh$5xr7YVpfAZOH++JUxKow ztkvwiVkf*=ID1K-8E^4)vo(-M*XrZ%xzC9r&w@@D zaS0-C8uJ;n^w(6;6mos~8K1*;^yE?so3Oa+ zNu}-k>7i;%30zaLtTJ?W46<2?^y#LiDDflEF2qstsybz)JiYPmGl)>Ve8})AhbvbU z9>T5?L%sZyM$2Dk_CcBO!>W<=!aV==RjnDP&s7sr*N2@WApQB6BH1u6j^>ZnR95b`nk@Z!$4pVh>GvXbQxum2 z?VZE$_dL1OyB`)rMc<8dF6+_)nHxt%3|}u=)%LN!8U_^P*bDlxK&x0eX^jwKya!UG2vs{8Qf9To!$sZRG!G2cdSjM-=#+Y%%o?KNaTHZcyJ>h7D#L8bSt9LVcwmjZnk zWrmL4FT}C5Jv6ran$?E(ED3wykXl+6F7YDaE!wlcYjm}&IZ}XEj&OupUTZ#}{n<9k zwNdjA&}}2sg;$%ZYAovx&(_m|P4(~J-|$~kCZa5f#sUh!&jTZPI^Sy9iH$AW>ys?R z%q1_wqDQl`oi+=!B%7@<^m46VP}yQ7{bxsZ4=9^h@)EAa&_ah^(y|hg8fwjyl>zc zaN?sq7&~FKqgqBc;!H)Ii_I$7GpQ2JZP1x@)Hwg7*5NBFC~e}_#~T-e^mdeT8;Z*( zED^Kr(E?`X(_A!rnyBSvVQyM67qV|jwdtXKP*L4;#6Sq1t`-2Bs(5#4Ka3d^WLva3 zQ$3gdQ4W)6tRYjLjQr_K-b-z{e*U^>#ITFu+0`=3oY-At0XNcvYPDm1=A@J4%yn+B zI5q%jPrnyouDLL&7=A(eYY}xJmo_*##cX7ftFHP&qAMGn^OK@dz*d@chYcn^rJiXYv0`0SJE|%p6&?zeub)G z4)U-sx~3S=7~*Fe{scK{Dk}ALR@~lJSc>K30Qw*iG!uj&6<;m0=G7JfH77PHSvb7^ zYVxcXX`1iv7KAr?b02xrVkC28#srph`jGPN;7}x0H%VouA$NU%u)flRH`o+-RrTf_ z(cW>hA>Lpr=coR@+v=_){Rrrg5=!AIIuzSktAQNHdw2pCcD`xv{gTf{G23 z=Sa#fqm?=bVD-0Am#7mDVwJecM_aS5&h@!mtFcKG(DcpS#)ZGU?GJKt>aK}$Qr_Q~ zFh>6BO5D}HCyDHvvkC`Q@4t(&l(I*I%B#pwYLqDyvE#QE9)&&JXbKWD{cUSR+r4jRM=H>-IE0{4+ zV5g!ul27^TWB~PLUi^??!?Cg@88gp5QJMA`;K~ab2_iK2DJ02Oj_W|tJ&uvA9P499gutH3v=jPRQ+K6cOi<2k0cE!r1{0|L1XZHkzEhb z@#-g=2cBO8M<+tZ+e6&;IG-`&dS3xD?ydre=NID-vWu+;ZbNo*yJf@E$9K}vs+_>aw4>9O$bsZ`m-R0xRM1~i9 zFqr)~)@91|gdb|-DN8DoFSW^Q{igYD=#tW0iz6k7qhKU*{cL9}EC!nCJo1dbGi8@V zzZYUY+#aGCH_Y`x*f!}!vh>okD=`1qbZ6s|s;m6XvtKMyo|c7Xyl4Gmz&`|V+zy<| zPi-%_a(`t0_W5Gh=JRP-Ip4nJtol)za}_t&u6y#CFY<;A?B@m!x;$y53O^11qycc> zpMJV9ba^rV{lx+imw?1|OLOpymqbN%+{+HUwxS{f)<1^)7NE#<4C(?hwGoi@Nsy zVEYKZBke$PXb+X)U>H|;_{CVAynJKoxTuZaxj70Y=<$BJoj69HY};C#Af0eqxBSbw z_B$aeJ7ix`YE{acd_S6AP0d-0=tLiUVQ)`v!hj|#(Z6S*;44xi^YPVL*5>^N<%@GC z>T$q1Vsu9^-dd+7UaFm(=nFUn*E-&B?P&DL!er%a$&^Z;0&Qq8HY}X1HAo7ZI`ohh z0~2K-Ys&4-2O?9huRCq#8}RS?!0PdZ>t~4OLtRVt9&x?*T$SbVE#vVgy)u_JWqg{Z zWZ6M$HvX^YqhtQ3?0c-u4B65BzAXHxi{CEj=CaAcK$N5qhvNs+>c^=;}lIg{FD*E@f+cPX~5w`{cTJR2R5+gFi22 zrJZAX zD$z_y33d1DQ2H;JXN`iVnA7 zQ$J0(?Ne7@gn2$LQe{uK2mv}Oku8_ zu{pEidr**n_sP2M!LR^%+d;)DRsMu`#EF^on=(E`A)Sr4{dyQ?0v4%)??4H=uAb7I z_)2@Ny1l#>Akgm-PDwU#MXB+ysq$x}^BS}sgRb(APTZq^qh;h96Za<-_n;lK)qpM^ q^8dO&_5&r>uLs4S@ literal 0 HcmV?d00001 diff --git a/rails6/es/img/logo.svg b/rails6/es/img/logo.svg new file mode 100644 index 0000000..7e46d25 --- /dev/null +++ b/rails6/es/img/logo.svg @@ -0,0 +1,139 @@ + + + + + + + + + + image/svg+xml + + + + + + + + "APIonRails":6 + + + + + + + { + } + + + + diff --git a/rails6/es/img/pow_running.png b/rails6/es/img/pow_running.png new file mode 100644 index 0000000000000000000000000000000000000000..9c6ffde35a39d5f7063f3c71dc04d27eba9b5640 GIT binary patch literal 331720 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

>!>X8A_8E6-K=nR{#5m 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 U2Ps_EpF@8{XlzL^0 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

NG1kNs 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⪙9;ydK<;@l_O)BdK#t32rTc{ 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 literal 0 HcmV?d00001 From 264f4910a2e3a6c6264764d14e4989860b6abb4b Mon Sep 17 00:00:00 2001 From: Oscar Tellez Date: Mon, 2 Mar 2020 20:11:47 -0400 Subject: [PATCH 02/17] Chapter 1 to spanish --- rails6/es/chapter01-introduction.adoc | 248 ++++++++++++++++++++++++++ 1 file changed, 248 insertions(+) create mode 100644 rails6/es/chapter01-introduction.adoc diff --git a/rails6/es/chapter01-introduction.adoc b/rails6/es/chapter01-introduction.adoc new file mode 100644 index 0000000..901c52f --- /dev/null +++ b/rails6/es/chapter01-introduction.adoc @@ -0,0 +1,248 @@ +[#chapter01-introduction] += Introduction + +Bienvenido a API on Rails 6, un tutorial con esteroides para enseñarte el mejor camino para construir tú siguiente API con Rails. El proposito de este libro es proveer una metodología comprensiva para desarrollar una API RESTful siguiendo las mejores prácticas. + +Al finalizar este libro, tu podrás crear tu propia API e integrarla con cualquier cliente como un navegador web o aplicación movil. El código generado esta codeado con Ruby on Rails 6.0 que es la versión actual. + +El proposito de este libro no es solamente enseñarte como construir un API con Rails sino mas bien para enseñarte como construir una API *evolutiva* y *mantenible* con Rails. Esto es, mejorar tu conocimiento actual con Rails. En esta sección, tú aprenderás a: + +- Usar Git para control de versiones +- Construir respuestas JSON +- Probar tus end-points con tests unitarios y funcionales +- Configurar autenticación con JSON Web Tokens (JWT) +- Usar la especificación JSON:API +- Optimizar y hacer cache de la API + +Recomiendo energicamente que sigas todos los pasos en este libro. Intenta no saltarte capitulos porque doy algunos tips y trucos para improvisar tus habilidades a travez del libro. Puedes considerarte a ti mismo el personaje principal de un videojuego que gana un nivel en cada capitulo. + + +En este primer capítulo explicaré como configurar tu entorno de desarrollo (en caso que aún no lo sepas). Luego vamos a crear una aplicacion llamada `market_place_api`. Me aseguraré que te enseño las mejores practicas que he aprendido durante mi experiencia. Esto significa que vamos a iniciar usando *Git* justo despues de inicializar el proyecto. + +Vamos a crear la aplicación siguiendo un metodo simple de trabajo que usé a diario en los siguientes capitulos. Vamos a desarrollar una aplicación completa usando Test Driven Development(TDD). Tambien explicaré el interés de usar una API para tu siguiente proyecto y eligiendo un adecuado formato de respuesta como JSON o XML. Mas allá, vamos a tener nuestras manos sobre el código y completar lo basico de la aplicacion construyendo todos los caminos necesarios. Tambien vamos a implementar acceso seguro a la API implementando autenticacion por interambio de cabeceras HTTP. Finalmente, en el último capítulo, vamos a añadir tecnicas de optimización para mejorar la estructura y tiempos de respuesta del servidor. + +La aplicación final rozaŕa la superficie de iniciar una tienda donde los usuario pueden realizar ordenes, subir productos y mas. Hay muchas opciones allá afuera para echar a andar una tienda en linea, como http://shopify.com[Shopify], http://spreecommerce.com/[Spree] ó http://magento.com[Magento]. + + +== Convenciones en este libro + +Las convenciones en este libro estan basadas en este http://www.railstutorial.org/book/beginning#sec-conventions[Tutorial de Ruby on Rails]. En esta seccion vamos a mencionar algunas que tal vez no son muy claras. + +Utilizaré muchos ejemplos usando la linea de comandos. No intentare con windows `cmd` (lo siento chic@s), asi que basare todos los ejemplos usando el estilo Unix, como a continuación se observa: + +[source,bash] +---- +$ echo "A command-line command" +A command-line command +---- + +Estare usando algunas pautas relacionadas al lenguaje, y me refiero a lo siguiente: + +* *Evitar* significa que no debes hacerlo +* *Preferir* indica que las 2 opciones, la primera es mejor +* *Usar* significa que eres bueno apra usar el recurso + + +Si por alguna razón encuentras errores cuando ejecutas un comando, en lugar de tratar de explicar cada resultado posible, te recomiendo 'googlearlo', lo cual no lo considero una mala practica. Pero si te gusta tomar una cerveza o tienes problemas con el tutorial siempre puedes mailto:contact@rousseau-alexandre.fr[escribirme]. + +== Entornos de desarrollo + +Una de las partes mas dolorosas para casi todo desarrollador es configurar el entorno de desarrollo, pero mientras lo hagas, los siguientes pasos pueden ser una pieza del pastel y una buena recompenza. Asi que voy a guiarte para que te sientas motivado. + +=== Editores de texto y terminal + +Hay muchos casos en que los entornos de desarrolo pueden diferir de computadora a computadora. Este no es el caso con los editores de texto o IDE's. Pienso que para el desarrolo en Rails un IDE es demasiado, pero alguien podria encontrarlo como la mejor forma de hacerlo, asi que si es tú caso te recomiendo que lo hagas con http://www.aptana.com/products/radrails[RadRails] ó http://www.jetbrains.com/ruby/index.html[RubyMine], ambos están bien soportados y vienen con muchas integraciones 'out of the box'. + +*Editor de texto*: En lo personal uso http://www.vim.org/[vim] como mi editor por defecto con https://github.com/carlhuda/janus[janus] el cual puede añadir y manejar muchos de los plugins que probablemente vas a utilizar. En caso que no sea un fande _vim_ como yo, hay muchas otras soluciones como http://www.sublimetext.com/[Sublime Text] que es multi plataforma, facil de aprender y customizable(este es probablemente ru mejor opción), esta altamente inspirado por http://macromates.com/[TextMate] (solo disponible para Mac OS). Una tercera opcion es usando un muy reciente editor de texto de los chicos de http://gitub.com[GitHub] llamado https://atom.io/[Atom], es un prometedor editor de texto echo con JavaScript, es facil de extender y personalizar para satisfacer tus necesidades, dale una oporrunidad. Cualquiera de los editores que te presento harán del trabajo, asi que te dejo elejir cual se ajusta a tu ojo. + +*Terminal*: Si decides seguir con http://icalialabs.github.io/kaishi/[kaishi] para configurar el entorno, notarás que pone pro defecto el shell con `zsh`, lo cual recomiendo bastante. Para la terminal, no soy fan de applicaciones de _Terminal_ que traen imporvisaciones si estas en Mac OS, así que mira http://www.iterm2.com/#/section/home[iTerm2], Que es un remplazo de la terminal para Mac OS. Si estas en Linux probablemente ya tienes una linda terminal, pero la que viene por defecto puede funcionar bien. + +=== Navegadores + +Cuando se trata de navegadores diria http://www.mozilla.org/en-US/firefox/new/[Firefox] inmediatamente, pero algunos otros desarrolladores pueden decir https://www.google.com/intl/en/chrome/browser/[Chrome] o incluso https://www.apple.com/safari/[Safari]. Cualquiera de ellos ayudara a construir la aplicacion que buscas, ellos vienen con un buen inspector no justamente para el DOM pero para el analisis de red y muchas otras caracteristicas que ya conoces. + +=== Manejador de paquetes + +* *Mac OS*: Hay muchas opciones para gestionasr como instalar tus paquetes en tu Mac, como el https://www.macports.org/[Mac Ports] ó http://brew.sh/[Homebrew], ambos son buenas opciones pero yo elegiría la ultima, he encontrado menos problemas cuando instalo software y lo administro. Para instalar `brew` solo ejecuta en la consola lo siguiente: + +[source,bash] +---- +$ /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" +---- + +* *Linux*: Estas listo!, realmente no es mucho problema si tu estas usando `apt`, `pacman`, `yum` siempre que te sientas comodo con ello sepas como instalar paquetes para poder seguir avanzando. + +=== Git + +Usaremos Git bastante, y puedes usarlo no solo para el proposito de este tutorial sino para cada proyecto independiente. + +* en Mac OS: `$ brew install git` +* en Linux: `$ sudo apt-get install git` + +=== Ruby + +Son muchos los caminos en que puedes instalar y gestionar ruby, y ahora tú puedes tener probablemente alguna version instalada si estas en Mac OS, para ver la version que tienes, solo ejecuta: + +[source,bash] +---- +$ ruby -v +---- + +Rails 6.0 requiere la instalacion de la version 2.5 o mayor. + +Yo recomiento usar http://rvm.io/[Ruby Version Manager (RVM)] ó http://rbenv.org/[rbenv] para instalarlo. Vamos a usar RVM en este tutorial pero no hay problema con cual de las 2 utilices. + +El principio de esta herramienta es permitirte instalar varias versiones de Ruby en el mismo equipo, en un entorno hermetico con una posible versión instalada en tu sistema operativo y luego tener la habilidad de cambiar de una a otra version facilmente. + +Para instalar RVM, ve a https://rvm.io/ e instala la huella de la llave GPG: [La huella de la llave GPG te permite verificar la identidad del autor o del origen de la descarga.]. Para realizarlo ejecutamos: + +[source,bash] +---- +$ gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB +$ \curl -sSL https://get.rvm.io | bash +---- + +Ahora instalaremos ruby: + +[source,bash] +---- +$ rvm install 2.6 +---- + +Ahora es momento de instalar el resto de dependencias que vamos a usar. + +==== Gemas, Rails & Librerias faltantes + + +Primero actualizamos las gemas en el sistema: + +[source,bash] +---- +$ gem update --system +---- + +En algunos casos si estas en Mac OS, necesitarás instalar algunas librerias extras: + +[source,bash] +---- +$ brew install libtool libxslt libksba openssl +---- + +Luego instalamos las gemas necesarias e ingoramos la documentación para cada una: + +[source,bash] +---- +$ gem install bundler +$ gem install rails -v 6.0.0 +---- + +Revisamos que todo funcina correctamente: + +[source,bash] +---- +$ rails -v +Rails 6.0.0 +---- + +==== Base de datos + +Recomiento altamante que instales http://www.postgresql.org/[Postgresql] para gestionar tus bases de datos. Pero aquí usaremos http://www.sqlite.org/[SQlite] por simplicidad. Si estas usando Mac OS estas listo para continuar, en caso que uses Linux, no te preocupes solo nos faltan unos pasos más: + +[source,bash] +---- +$ sudo apt-get install libxslt-dev libxml2-dev libsqlite3-dev +---- + +ó + +[source,bash] +---- +$ sudo yum install libxslt-devel libxml2-devel libsqlite3-devel +---- + +== Inicializando el proyecto + +Inicializar una aplicación Rails puede ser muy sencillo para ti. Si no es el caso aqui tienes un tutorial super rápido. + +Estos son los comandos: + +[source,bash] +---- +$ mkdir ~/workspace +$ cd ~/workspace +$ rails new market_place_api --api +---- + +NOTE: La opción `--api` aparecio en la version 5 de Rails. Ésta te permite limitar las librerías y _Middleware_ incluido en la aplicación. Esto tambien evita generar vistas HTML cuando se usan los generadores de Rails. + +Como puedes adivinar, los anteriores comandos gerarán los huesos desnudos de tu aplicación Rails. + +== Versionado + +Recuerda que Git te ayuda a dar seguimiento y mantener el historial de tu código. Ten en mente que el codigo fuente de la aplicación es publicado en GitHub. Puedes seguir el proyecto en https://github.com/madeindjs/api_on_rails_6[GitHub]. + +Ruby on Rails inicializa el directorio Git por tí cuando usas el comando `rails new`. Esto significa que no necesitas ejecutar el comando `git init`. + +Sin embargo es necesario configurar la informacion del autor de los _commits_. Si aún no lo has echo, ve al directorio de proyecto y corre los siguientes comandos: + +[source,bash] +---- +$ git config --global user.name "Aquí pon tu nombre" +$ git config --global user.email "Aquí pon tu email" +---- + +Rails tambien provee un archivo _.gitignore_ para ignorar algunos archivos a los que no queramos dar seguimiento. El archivo _.gitignore_ por defecto puede lucir como se ve a continuación: + +..gitignore +---- +# Ignore bundler config. +/.bundle + +# Ignore the default SQLite database. +/db/*.sqlite3 +/db/*.sqlite3-journal + +# Ignore all logfiles and tempfiles. +/log/* +/tmp/* +!/log/.keep +!/tmp/.keep + +# Ignore uploaded files in development. +/storage/* +!/storage/.keep +.byebug_history + +# Ignore master key for decrypting credentials and more. +/config/master.key +---- + +Despues de modificar el archivo _.gitignore_ unicamente necesitamos añadir los archivos y hacer _commit_ de los cambios, para ello usamos los siguientes comandos: + +[source,bash] +---- +$ git add . +$ git commit -m "Commit Inicial" +---- + +TIP: He encontrado que el mensaje del commit deberia iniciar con un verbo en tiempo presente, describiendo lo que el commit hace y no lo que hizo, ayuda cuando estás explorando el historial del proyecto. Encontre esto mas natural para leer y entender. Seguiremos esta práctica hasta el final del tutorial. + +Por ultimo y como un paso opcional configuramos el proyecto en GitHub y hacemos _push_ de nuestro código al servidor remoto: Pero primero añadimos el _remoto_: + +[source,bash] +---- +$ git remote add origin git@github.com:madeindjs/market_place_api_6.git +---- + +Entonces hacemos _push_(empujamos) el código: + +[source,bash] +---- +$ git push -u origin master +---- + +A medida que avanzamos con el tútorial, usaré las practicas que uso a diario, esto incluye trabajar con `branches`(ramas), `rebasing`, `squash` y algo mas. Por ahora no debes preocuparte si algunos terminos no te suenan familiares, te guiaré en ello con el tiempo. + +== Conclusión + +Ha sido un largo camino a travéz de este capítulo, si has llegado hasta aquí déjame felicitarte y asegurarte que a partir de este punto las cosas mejorarán. Asi que vamos a ensuciarnos las manos y comenzar a escribir algo de código! From d77bc3d0ee1d11c26c512433f7dad0629156f723 Mon Sep 17 00:00:00 2001 From: Oscar Tellez Date: Tue, 3 Mar 2020 16:11:08 -0400 Subject: [PATCH 03/17] chapter 2 to spanish --- rails6/es/chapter02-api.adoc | 218 +++++++++++++++++++++++++++++++++++ 1 file changed, 218 insertions(+) create mode 100644 rails6/es/chapter02-api.adoc diff --git a/rails6/es/chapter02-api.adoc b/rails6/es/chapter02-api.adoc new file mode 100644 index 0000000..3374661 --- /dev/null +++ b/rails6/es/chapter02-api.adoc @@ -0,0 +1,218 @@ +[#chapter02-api] += La API + +En esta sección resumiré la applicación. Hata aquí ya debiste leer el capítulo anterior. Si no lo has leído te recomiento que lo hagas. + +Puedes clonar el proyecto hasta este punto con: + +[source,bash] +---- +$ git checkout tags/checkpoint_chapter02 +---- + +Resumiendo, simplemente generamos nuestra aplicación Rails e hicimos el primer commit. + +== Planificando la aplicación + +Como queremos que la aplicacion sea sencilla, esta consistirá de 5 modelos. No te preocupes si no entiendes completamente que estamos haciendo. Vamos a revisar y a construir cada uno de los recursos a medida que avancemos con el tutorial. + +image:data_model.png[Esquema conexiones entre los modelos] + +Resumiendo, el `user`(usuario) podrá ralizar muchas `orders`(ordenes/pedidos), subir multiples `products`(productos) los cuales pueden tener muchas `images`(imágenes) ó `comments`(comentarios) de otros usuarios de la applicación. + +No construiremos vistas para mostrar o interactuar con la API, asi que no hagas de esto un gran tutorial. Para ello hay muchas opciones allá afuera como los frameworks de javascript (https://angularjs.org/[Angular], https://vuejs.org/[Vue.js], https://reactjs.org/[React.js]). + +Hasta este punto deberías preguntarte: + +> ¿Esta bien, pero, yo necesito explorar ó visualizar como va la construccion del API? + + +Y eso es justo. Probablemente si googleas algo relacionado con explarar un api, aparecerá una aplicacion llamada https://www.getpostman.com/[Postman]. Este es un gran software pero no lo utilizaremos porque usaremos *cURL* que permite a cualquiera reproducir peticiones en cualquier computadora. + +== Configurar la API + +Una API es definida por http://en.wikipedia.org/wiki/Application_programming_interface[wikipedia] como _La interfaz de programación de aplicaciones(API), es un conjunto de subrutinas, funciones y procedimientos que ofrece cierta biblioteca para ser utilizado por otro software como una capa de abstracción._ En otras palabras la forma en que el sistema interactua entre sí mediante una interfaz común, en nuestro caso un servicio web construido con JSON. Hay otros protocolos de comunicación como SOAP, pero no lo cubriremos aquí. + +JSON, como tipo estandar en Internet, es ampliamente aceptado, legible, extensible y facil de implementar. +Muchos de los frameworks actuales consumen APIs JSON por defecto (https://angularjs.org/[Angular] ó https://vuejs.org/[Vue.js] por ejemplo). Tambien hay grandes bibliotecas para Objetive-C como https://github.com/AFNetworking/AFNetworking[AFNetworking] ó http://restkit.org/[RESTKit]. Probablemente hay buenas soluciones para Android pero por mi falta de experiencia en esa plataforma, podría no ser la persona adecuada para recomendarte alguna. + +Muy bien. Asi que vamos a construir nuestra API con JSON. Hay muchos caminos para logarlo. Lo primero que me viene a la mente es justamente iniciar añadiendo rutas definiendo los _end points_. Pero puede ser mala idea porque no hay un http://www.w3.org/2005/Incubator/wcl/matching.html[patrón URI] suficientemente claro para saber que recurso esta expuesto. El protocolo o estructura del que estoy hablando es http://en.wikipedia.org/wiki/Representational_state_transfer[REST] que significa Transferencia de Estado Representacional(Representational state transfer) según la definición de Wikipedia. + +[source,soap] +---- +aService.getUser("1") +---- + +Y en REST puedes llamar una URL con una peticion HTTP especifica, en este caso con una peticion GET: + +La APIs RESTful debe seguir al menos tres simples pautas: + +* Una base http://en.wikipedia.org/wiki/Uniform_resource_identifier[URI], como es `http://example.com/resources/`. +* Un tipo multimedia de Internet para representar los datos, es comunmente JSON y es comunmente definido mediente el intercambio de cabeceras. +* Sigue el estandar http://en.wikipedia.org/wiki/HTTP_method#Request_methods[Metodos HTTP] como son GET, POST, PUT, DELETE. +** *GET*: Lee el recurso o recursos definidos por el patrón URI +** *POST*: Crea una nueva entrada en la colección de recursos +** *PUT*: Actualiza una colección o un miebro de los recursos +** *DELETE*: Destruye una colección o miembro de los recursos + +Esto podría no ser suficientemente claro o podría parecer mucha información para digerir, pero como vamos avanzando en el tutorial, con suerte conseguiras entender con mayor fácilidad. + +=== Restricciones de Rutas y Espacios de Nombres + +Antes de comenzar a escribir código, preparamos el código con git. Vamos a estar usando una rama por capítulo, la subiremos a GitHub y entonces la fusionaremos con la rama master. Asi que vamos a a iniciar abriendo la terminal, `cd` hacia el directorio `market_place_api` y tecleamos lo siguiente: + +[source,bash] +---- +$ git checkout -b chapter02 +Switched to a new branch 'chapter02' +---- + +Unicamente vamos a estrar trabajando en `config/routes.rb`, ya que solo vamos a establecer las restricciones y el `formato` de respuesta predeterminado para cada respuesta. + +[source,ruby] +.config/routes.rb +---- +Rails.application.routes.draw do + # ... +end +---- + +Primero que todo borra todo el código comentado que viene en el archivo, no lo vamos a necesitar. Entonces haz un commit, solo como un calentamiento: + +[source,bash] +---- +$ git add config/routes.rb +$ git commit -m "Removes comments from the routes file" +---- + +Vamos a aislar los controladores del API bajo un espacio de nombres. Con Rails esto es bastante simple: solo tienes que crear un folder en `app/controllers` llamado `api`. El nombre es importante porque es el espacio de nombres que usaremos para gestionar los controladores para los endpoints del api. + +[source,bash] +---- +$ mkdir app/controllers/api +---- + +Entonces agregamos el nombre de espacio dentro de nuestro archivo _routes.rb_: + +[source,ruby] +.config/routes.rb +---- +Rails.application.routes.draw do + # Api definition + namespace :api do + # We are going to list our resources here + end +end +---- + +Por definicion un espacio de nombres en el archivo `routes.rb`. Rails automaticamente mapeara que espacio de nombres corresponde al folder de los _controlladores_, en nuestro caso el directorio `api/``. + +.Archivos multimedia soportados por Rails +**** +Rails soporta 35 tipos diferentes de archivos multimedia, puedes listarlos accediendo a la clase SET del modulo Mime: + +[source,bash] +---- +$ rails c +2.6.3 :001 > Mime::SET.collect(&:to_s) + => ["text/html", "text/plain", "text/javascript", "text/css", "text/calendar", "text/csv", "text/vcard", "text/vtt", "image/png", "image/jpeg", "image/gif", "image/bmp", "image/tiff", "image/svg+xml", "video/mpeg", "audio/mpeg", "audio/ogg", "audio/aac", "video/webm", "video/mp4", "font/otf", "font/ttf", "font/woff", "font/woff2", "application/xml", "application/rss+xml", "application/atom+xml", "application/x-yaml", "multipart/form-data", "application/x-www-form-urlencoded", "application/json", "application/pdf", "application/zip", "application/gzip"] +---- +**** + +Esto es importante porque vamos a trabajar con JSON, uno de los http://en.wikipedia.org/wiki/Internet_media_type[tipos MIME] aceptados por Rails, solo necesitamos especificar que este es el formato por defecto: + +[source,ruby] +.config/routes.rb +---- +Rails.application.routes.draw do + # Api definition + namespace :api, defaults: { format: :json } do + # We are going to list our resources here + end +end +---- + +Hasta este punto no hemos hecho nada loco. Ahora lo que queremos es una _base_uri_ que incluye la version de la API. Pero hagamos commit antes de ir a la siguiente sección: + +[source,bash] +---- +$ git add config/routes.rb +$ git commit -m "Set the routes constraints for the api" +---- + +== Versionado Api + +Hasta este punto deberiamos tener un buen mapeado de rutas usando espacio de nombres. Tu archivo `routes.rb` debería lucir como esto: + +[source,ruby] +.config/routes.rb +---- +Rails.application.routes.draw do + # Api definition + namespace :api, defaults: { format: :json } do + # We are going to list our resources here + end +end +---- + +Ahora es tiempo de confugurar algunas otras restricciones para propositos de versionado. Deberias preocuparte por versionar tú aplicación desde el inicio pues le dara una mejor estrutura a tu api, y cuando hagas cambios, puedes dar a los desarrolladores que estan consumento tu api la oportunidad de adaptar las nuevas caraceristicas mientras las viejas quedan obsoletas. Este es un exelente http://railscasts.com/episodes/350-rest-api-versioning[railscast] explicando esto. + +Para establecer la version del API, primero necesitamos agregar otro directorio en el de `api` que antes creamos: + +[source,bash] +---- +$ mkdir app/controllers/api/v1 +---- + +De esta forma podemos definir espacio de nombres a nuesra api con diferentes versiones facilmente, ahora solo necesitamos añadir el codigo necesario al archivo `routes.rb`: + +[source,ruby] +.config/routes.rb +---- +Rails.application.routes.draw do + # Api definition + namespace :api, defaults: { format: :json } do + namespace :v1 do + # We are going to list our resources here + end + end +end +---- + +Hasta este punto, el API puede ser alcanzada a travéz de la URL. Por ejemplo con esta configuracion un end-point para reuperar un producto podría ser algo como: . + + +.Patrones Comunes del API +**** +Puedes encontrar muchas forma de configurar un _base_uri_ cuando construimos un api siguiendo diferentes patrones, asumiendo que estamos versionando nuestra api: + +* `api.example.com/`: En mi opinion este es el camino a seguir, te da una mejor interfaz y aislamiento, y a largo plazo puede ayudarte a http://www.makeuseof.com/tag/optimize-your-dns-for-faster-internet/[escalar rapidamente] +* `example.com/api/`: Este patrón es muy común, y es actualmente un buen camino a seguir cuando no quieres poner bajo espacio de nombres tu api en un subdominio +* `example.com/api/v1`: parece buena idea, poniendo la version del api mediante la URL, parece como un patrón descriptivo, pero esta forma te forza a incluir la URL en cada petición, asi que si en algún momento decides cambiar este patrón, se convierte en un problema de mantenimiento a largo plazo. + +Estas son algunas practicas en la construccion de una API que recomiendan no versionar el API a travez de la URL. Es verdad. El desarrollador no debería conocer la version que esta usando. En terminos de simplicidad, he decidido dejar esta convención, que podremos aplicar en una segunda fase. +**** + +Es tiempo de hacer _commit_: + +[source,bash] +---- +$ git commit -am "Set the versioning namespaces for API" +---- + +Estamos en lo ultimo del capitulo. Por lo tanto es tiempo de aplicar nuestras modificaciones a la rama master haciendo un _merge_. Para hacerlo, nos cambiamos a la rama `master` y hacemos _merge_ de `chapter02`: + +[source,bash] +---- +$ git checkout master +$ git merge chapter02 +---- + +== Conclusión + +Ha sido un largo camino, lo se, pero lo hiciste, no te rindas esto solo es un pequeño escalón para cualquier cosa grande, asi que sigue. Mientras tanto y si te sientes curioso hay algunas gemas que pueden manejar este tipo de confuguración: + +* https://github.com/Sutto/rocket_pants[RocketPants] +* https://github.com/bploetz/versionist[Versionist] + +No cubriré eso en este libro, ya que estamos intentando aprender a implementar este tipo de funcionalidades, pero es bueno saberlo. Por cierto el código hasta este punto está https://github.com/madeindjs/market_place_api_6/releases/tag/checkpoint_chapter03[aquí]. From fca7ddd1c261894d76f7ca34b11e8f2984f44dd5 Mon Sep 17 00:00:00 2001 From: Oscar Tellez Date: Wed, 4 Mar 2020 20:22:06 -0400 Subject: [PATCH 04/17] Chapter 3 to spanish --- rails6/es/chapter03-presenting-users.adoc | 702 ++++++++++++++++++++++ 1 file changed, 702 insertions(+) create mode 100644 rails6/es/chapter03-presenting-users.adoc diff --git a/rails6/es/chapter03-presenting-users.adoc b/rails6/es/chapter03-presenting-users.adoc new file mode 100644 index 0000000..04474b0 --- /dev/null +++ b/rails6/es/chapter03-presenting-users.adoc @@ -0,0 +1,702 @@ +[#chapter03-presenting-users] += Presentando a los usuarios + +En el último capítulo configuramos el esqueleto para la configuracion de los enpoints en nuestra aplicación. + +En un próximo capítulo manejaremos autenticación de usuarios mediante autenticación con tokens configurando permisos para poner límites de acceso preguntando que usuario esta autenticado. En capitulos venideros vamos a relacionar `products` (productos) a usuarios y dar la habilidad de generar órdenes. + +Puedes clonar el proyecto hasta este punto con: + +[source,bash] +---- +$ git checkout tags/checkpoint_chapter03 +---- + +Como ya estarás imaginando hay muchas soluciones de autenticación para Rails, https://github.com/binarylogic/authlogic[AuthLogic], https://github.com/thoughtbot/clearance[Clearance] y https://github.com/plataformatec/devise[Devise]. + +Estas libreriras son soluciones como llave en mano, por ejemplo ellas te permiten gestionar un montón de cosas como autenticación, olvido de contraseña, validación, etc.. Sin embargo vamos a usar la gema https://github.com/codahale/bcrypt-ruby[bcrypt] para generar un hash para la contraseña del usuario. + +Este capítulo estará completo. Puede ser largo pero intentare cubrir el mayor numero de temas posibles. +Sientete libre de tomar un café y vamos. Al final de este capítulo tendrás construida la logica del usuario asi como la validación y manejo de errores. + +Es un buen momento para crear una nueva rama: + +[source,bash] +---- +$ git checkout -b chapter03 +---- + +NOTE: Asegurate que estas en la rama `master` antes de hacer _checkout_. + +== Modelo usuario + +=== Generación de el modelo `User` + +Comenzaremos por generar nuestro modelo `User`. Este modelo será realmente basico y trendrá solo dos campos: + +- `email` el cual sera único y permitira conectar con la aplicación +- `password_digest` el cual contiene la version *hasheada* de la contraseña (los discutiremos mas tarde en este capítulo) + +Genereamos nuestro modelo `User` usando el comando _generate model_ provisto por Ruby on Rails. Es muy facil de usar: + +[source,bash] +---- +$ rails generate model User email:string password_digest:string +invoke active_record + create db/migrate/20190603195146_create_users.rb + create app/models/user.rb + invoke test_unit + create test/models/user_test.rb + create test/fixtures/users.yml +---- + +NOTE: El _modelo_ es el elemento que contiene la información o datos asi como la logica relacionada a esa información: validacion, lectura y guardado. + +Este comando genera un monton de archivos! No te preocupes revisaremos uno por uno. + +El archivo de migración contenido en el forder `db/migrate` conteiene la *migración* que describe los cambios que realizará en la base de datos. Este archivo puede lucir así: + +.db/migrate/20190603195146_create_users.rb +[source,ruby] +---- +class CreateUsers < ActiveRecord::Migration[6.0] + def change + create_table :users do |t| + t.string :email + t.string :password_digest + + t.timestamps + end + end +end +---- + +NOTE: La fecha insertada al inicio del nombre del archivo de migración debiera ser diferente para ti ya que corresponde a la fecha de creación de la migración. + +Haremos un pequeño cambio a la migracion a fin de añadir algunas validaciones a la base de datos. Con rails es una práctica común hacer validaciones directamente en el modelo Ruby. Es buena práctica hacer algo en el esquema de la base de datos. + +Por lo tanto haremos dos restricciones adicionales: + +- email es forzoso: usaremos la propiedad `null: false`. +- email debe ser único: añadiremos un indice para la columna email con la propiedad `unique: true`. +- password es forzoso: usamos la propiedad `null: false`. + +La migración quedaría así: + +.db/migrate/20190603195146_create_users.rb +[source,ruby] +---- +# ... +create_table :users do |t| + t.string :email, null: false + t.index :email, unique: true + t.string :password_digest, null: false + # ... +end +---- + +Una vez completa la migración, podemos correr los camibos con el siguiente comando: + +.db/migrate/20190603195146_create_users.rb +[source,ruby] +---- +$ rake db:migrate +== 20190603195146 CreateUsers: migrating ====================================== +-- create_table(:users) + -> 0.0027s +== 20190603195146 CreateUsers: migrated (0.0028s) ============================= +---- + +NOTE: Este comando convertirá nuestra migracion en una consulta SQL que actualizara la base de datos SQLite3 almacenada en el folder _db_. + +==== Modelo + +Asi definimos nuestro esquema de la base de datos. El siguiente paso es actualizar nuestro modelo para definir *reglas de validación*. Estas reglas estan definidas en el modelo localizado en el folder`app/models`. + +Ruby on Rails provee un mecanismo completo que puedes encontrar en https://guides.rubyonrails.org/active_record_validations.html[su documentación oficial]. En nuestro caso buscamos validar solo 3 cosas: + +. que el email tenga un formato válido +. que el email sea único +. que la contraseña siempre contenga algo + +Estas tres reglas son definidas por el siguiente código: + +.app/models/user.rb +[source,ruby] +---- +class User < ApplicationRecord + validates :email, uniqueness: true + validates_format_of :email, with: /@/ + validates :password_digest, presence: true +end +---- + +Ahi tienes. Rails una sintaxis simple y el código es muy legible. + +.Validación del Email +**** +Habrás notado que la validación del email es muy simplista solo validando la presencia de una `@`. + +Es normal. + +Hay infinidad de excepciones en la dirección de un correo electrónico +There are infinite exceptions to the email address so well https://davidcel.is/posts/stop-validating-email-addresses-with-regex/[que incluso `Mira todos estos espacios!@example.com` es una dirección de correo valida]. Por lo tanto es mejor para favorecer un enfoque sencillo y confirmar la direccion de correo enviando un email. +**** + +==== Pruebas unitarias + +Finalizamos con las pruebas unitarias. Aqui usaremos Minitest un framework de +pruebas que es proporcionado por defecto con Rails. + +Minitest esta basado en _Fixtures_ que te permiten llenar tu base de datos con datos *predefinidos*. Los _Fixtures_ estan definidos en un archivo YAML en el directorio `tests/fixtures`. Hay un archivo por plantilla. + + +Debemos por lo tanto iniciar actualizando nuestros `tests/fixtures`. + +NOTE: _fixtures_ no estan diseñados para crear todas los datos que tus pruebas necesitan. Solo te permiten definir los datos basicos que tu aplicación necesita. + +Asi que comenzamos por crear un _fixture_ definiendo un usuario: + +.test/fixtures/users.yml +[source,yaml] +---- +one: + email: one@one.org + password_digest: hashed_password +---- + +Ahora podemos crear tres pruebas: + +- 1. Verifica que un usuario con datos correctos es váido: + +.test/models/user_test.rb +[source,ruby] +---- +# ... +test 'user with a valid email should be valid' do + user = User.new(email: 'test@test.org', password_digest: 'test') + assert user.valid? +end +---- + +- 2. Verifica que un usuario con un email erroneo no es válido: + +.test/models/user_test.rb +[source,ruby] +---- +# ... +test 'user with invalid email should be invalid' do + user = User.new(email: 'test', password_digest: 'test') + assert_not user.valid? +end +---- + +- 3. Verifica que un nuevo usuario con email no es válido. Asi que usamos el mismo email que creamos en el _fixture_. + +.test/models/user_test.rb +[source,ruby] +---- +# ... +test 'user with taken email should be invalid' do + other_user = users(:one) + user = User.new(email: other_user.email, password_digest: 'test') + assert_not user.valid? +end +---- + +Ahi lo tienes. Podemos validar que nuestra implementación es correcta simplemente corriendo las pruebas unitarias que creamos: + +[source,bash] +---- +$ rake test +... +3 runs, 3 assertions, 0 failures, 0 errors, 0 skips +---- + +I think it's time to do a little _commit_ to validate our progress: + +[source,bash] +---- +$ git add . && git commit -m "Create user model" +---- + +=== Hash de la contraseña + +Previamente implementamos el almacenamiento de los datos del usuario. Pero seguimos teniendo un problema por resolver: *el almacenamiento de la contraseña esta en texto plano*. + +> Si almacenas la contraseña de los usuarios en texto plano, entonces un atacante que roba una copia de tu base de datos tiene una lista gigante de emails y contraseñas. Alguno de tus usuarios podria tener únicamente una contraseña -- para su cuenta de email, para sus cuentas de banco, para su aplicación. Un simple hackeo puede escalar en un robo masivo de identidad. - https://github.com/codahale/bcrypt-ruby#why-you-should-use-bcrypt[fuente - Porque deberias usar bcrypt(en inglés)] + +Asi que vamos a usar la gema bcrypt para *hashear* la contraseña. + +NOTE: Hashear es el proceso de transformar un arreglo de caracteres en un _Hash_. Este _Hash_ no te permite encontrar el arreglo de caracteres original. Pero como sea, podemos facilmente usarlo para encontra si un arreglo de caracteres dado coincide con el _hash_ que almacenamos. + +Primero debemos agregar la gema Bcrypt al _Gemfile_. Podemos usar el comando `bundle add`. Que hará: + +1. añadir la gema al Gemfile recuperando la versión más reciente +2. ejecutar el comando `bundle install` el cual instalará la gema y actualizara el archivo _Gemfile.lock_ "bloqueando" la versión actual de la gema + +Por lo tanto, ejecutamos el siguiente comando: + +[source,bash] +---- +$ bundle add bcrypt +---- + +Una vez que el comando es ejecutado, la siguiente linea es añadidad al final del _Gemfile_: + +[source,ruby] +.Gemfile +---- +gem "bcrypt", "~> 3.1" +---- + +NOTE: La versión 3.1 de bcrypt es la versión actual al momento de escribir. Esto podría por lo tanto variar en tú caso. + +Active Record nos ofrece un metodo https://github.com/rails/rails/blob/6-0-stable/activemodel/lib/active_model/secure_password.rb#L61[`ActiveModel::SecurePassword::has_secure_password`] que hara interfaz con Bcrypt y nos ayudará con la contraseña lo que lo hace mas fácil. + +[source,ruby] +.app/models/user.rb +---- +class User < ApplicationRecord + # ... + has_secure_password +end +---- + +`has_secure_password` agrega las siguintes validaciones: + +* La contraseña debe estar presente en la creación. +* La longitud de la contraseña debe ser menor o igual a 72 bytes. +* La confirmación de la contraseña usa el traributo `password_confirmation` (si es enviado) + +En adición, este metodo añadirá un atributo `User#password` que sera automaticamente hasheado y guardado en el atributo `User#password_digest`. + +Vamos a intentarlo ahora mismo en la consola de Rails. Abre una consola con `rails console`: + +[source,ruby] +---- +2.6.3 :001 > User.create! email: 'toto@toto.org', password: '123456' + =># +---- + +Puedes ver que cuando llamas al metodo `User#create!` , el atributo `password` es hasheado y guardado en `password_digest`. Vamos a enviar tambien un atributo `password_confirmation` que ActiveRecord comparará con `password`: + +[source,ruby] +---- +2.6.3 :002 > User.create! email: 'tata@tata.org', password: '123456', password_confirmation: 'azerty' +ActiveRecord::RecordInvalid (Validation failed: Password confirmation doesn t match Password) +---- + +Todo esta trabajando como lo planeamos! Vamos a hacer un _commit_ para mantener la historia concisa: + +[source,bash] +---- +$ git commit -am "Setup Bcrypt" +---- + +== Build users + +Es tiempo de hacer nuestro primer "entry point". Iniciaremos por construir la acción `show` que responderá concon información de un usuario único en formato JSON. Los pasos son: + +1. generar el controlador `users_controller`. +2. añadir las pruebas correspondientes +3. construir el código real. + +Vamos a enfocarnos primero en generar el controlador y las pruebas funcionales. + +En orden para respetar la vista de nuestra API, vamos a cortar nuestra aplicación usando *modules* (módulos). La sintaxis por lo tanto es la siguiente: + +[source,bash] +---- +$ rails generate controller api::v1::users +---- + +Este comando creará el archivo `users_controller_test.rb`. Antes de ir mas lejos hay dos cosas que queremos probar en nuestra API: + +* La estructura JSON que devuelve el servidor +* El codigo de la respuesta HTTP que devuelve el servidor + +.Códigos HTTP más comunes +**** +El primer dígito de el codigo de estado especifica una de las 5 clases de respuesta. El minimo indispensable para un cliente HTTP es que este una de estas 5 clases. Esta es una lista de los códigos HTTP comunemten usados: + +* `200`: Respuesta estandar para una solicitud HTTP exitosa. Usualmente en solicitudes `GET` +* `201`: La petición fue recibida y resulta en la creacion de nu nuevo recurso. Despues de una solicitud `POST` +* `204`: El servidor tiene una petición procesada con éxito, pero no se regresó ningun contenido. Esto es usual en una solicitud `DELETE` exitosa. +* `400`: La petición no se peude ejecutadar debido a una sintaxis incorrecta. Puede suceder para cualquier tipo de solicitud. +* 401: Similar al 403, pero especialmente usada al solicitar autenticación y ha fallado o aún no se ha proporcionado. Puede suceder en cualquier tipo de solicitud. +* `404`: El recurso solicitado no fue encontrado pero podría estar disponible en el futuro. Usualmente concierne a la petición `GET`. +* 500: Un mensaje de error generico, dado cuando una condición inesperada ha sido encontrada y ningun otro mensaje especifico es apropiado. + +Para una liesta completa de codigos HTTP, mira este https://en.wikipedia.org/wiki/List_of_HTTP_status_codes[articulo de Wikipedia (en inglés)]. +**** + +Por lo tanto vamos a implementar la prueba funcional que verifica el acceso a el metodo `Users#show`. + + +[source,ruby] +.test/controllers/api/v1/users_controller_test.rb +---- +# ... +class Api::V1::UsersControllerTest < ActionDispatch::IntegrationTest + setup do + @user = users(:one) + end + + test "should show user" do + get api_v1_user_url(@user), as: :json + assert_response :success + # Test to ensure response contains the correct email + json_response = JSON.parse(self.response.body) + assert_equal @user.email, json_response['email'] + end +end +---- + + +Entonces simplemente agrega la accion a tu controlador. Es extremadamente simple: + +[source,ruby] +.app/controllers/api/v1/users_controller.rb +---- +class Api::V1::UsersController < ApplicationController + # GET /users/1 + def show + render json: User.find(params[:id]) + end +end +---- + +Si corres la prueba con `rails test` obtienes el siguiente error: + +[source,bash] +---- +$ rails test + +...E + +Error: +UsersControllerTest#test_should_show_user: +DRb::DRbRemoteError: undefined method \`api_v1_user_url' for # (NoMethodError) + test/controllers/users_controller_test.rb:9:in `block in ' +---- + +Este tipo de error es muy común cuando generaste tus recursos manualmente! En efecto, nos hemos olvidad por completo de *la ruta*. Asi que vamos a añadirla: + +[source,ruby] +.config/routes.rb +---- +Rails.application.routes.draw do + namespace :api, defaults: { format: :json } do + namespace :v1 do + resources :users, only: [:show] + end + end +end +---- + +Las pruebas ahora deberían pasar: + +---- +$ rails test +.... +4 runs, 5 assertions, 0 failures, 0 errors, 0 skips +---- + +Como siempre, despues de añadir una caracteristica que nos satisface, vams a hacer un _commit_: + +[source,bash] +---- +$ git add . && git commit -m "Adds show action to the users controller" +---- + +=== Prueba tu recurso con cURL + +Asi que finalmente tenemos un recurso para probar. Tenemos muchas soluciones para probarlo. La primera que se me viene a la mente es hacer uso de cURL, el cual esta integrado en la mayoria de distribuciones Linux. Asi que vamos a probarlo: + +Pirmero inicializamos el servidor de rails en una nueva terminal. +[source,bash] +---- +$ rails s +---- + +Entonces cambia de nuevo a tu otra terminal y corre: + +[source,bash] +---- +$ curl http://localhost:3000/api/v1/users/1 +{"id":1,"email":"toto@toto.org", ... +---- + +Encontramos el usuario que creamos con la consola de Rails en la seccion previa. Ahora tienes una entrada en el API para registro de usuarios. + +=== Crear usuarios + +Ahora que tenemos mejor entendimiento de como contruir "entry points" (puntos de entrada), es tiempo de extender nuestra API. Una de las caracteristicas mas importantes es darles a los usuarios que puedan crear un perfil en nuestra aplicación. Como siempre, vamos a escribir nuestras pruebas antes de implementar nuestro código para extener nuestro banco de pruebas. + +Asegura que tu directoruo de Git esta limpio y que no tienes algun archivo en _staging_. Si es asi hasles _commit_ que vamos a empezar de nuevo. + +Asiq eu vamos a iniciar por escribir nuestra prueba añadiendo una entrada para crear un usuario en el archivo `users_controller_test.rb`: + +[source,ruby] +.test/controllers/users_controller_test.rb +---- +# ... +class Api::V1::UsersControllerTest < ActionDispatch::IntegrationTest + # ... + test "should create user" do + assert_difference('User.count') do + post api_v1_users_url, params: { user: { email: 'test@test.org', password: '123456' } }, as: :json + end + assert_response :created + end + + test "should not create user with taken email" do + assert_no_difference('User.count') do + post api_v1_users_url, params: { user: { email: @user.email, password: '123456' } }, as: :json + end + assert_response :unprocessable_entity + end +end +---- + +Es un monton de codigo. No te preocupes explicarŕe todo: + +* En el primer test revisamos la creacion de un usuario enviando una peticion POST valida. Entonces, revisamos que un usuario adiconal ahora existe en la base de datos y que el codigo HTTP de respuesta es `created` (código de estado 201) +* En el segundo test revisamos que el usuario no es creado usando una direccion de correo que ya está en uso. Entonces, revisamos que el codigo HTTP de respuesta es `unprocessable_entity` (código de estado 422) + +Hasta este punto, la prueba deberia de fallar (como esperabamos): + +[source,bash] +---- +$ rails test +...E +---- + +Asi que es tiempo de implementar el código para que nuestra prueba sea exitosa: + +[source,ruby] +.app/controllers/api/v1/users_controller.rb +---- +class Api::V1::UsersController < ApplicationController + # ... + + # POST /users + def create + @user = User.new(user_params) + + if @user.save + render json: @user, status: :created + else + render json: @user.errors, status: :unprocessable_entity + end + end + + private + + # Only allow a trusted parameter "white list" through. + def user_params + params.require(:user).permit(:email, :password) + end +end +---- + +Recuerda que cada vez que agregamos una entrada en nuestra API debemos agregar esta acción en nuestra archivo `routes.rb`. + +[source,ruby] +.config/routes.rb +---- +Rails.application.routes.draw do + namespace :api, defaults: { format: :json } do + namespace :v1 do + resources :users, only: %i[show create] + end + end +end +---- + +Como puesdes ver, la implementacion es bastante simple. Tambien hemos añadido el metodo privado `user_params` para proteger de la asignacion masiva de atributos. Ahora nuestra prueba debería de pasar: + +[source,bash] +---- +$ rails test +...... +6 runs, 9 assertions, 0 failures, 0 errors, 0 skips +---- + +Yeah! Hagamos _commit_ de los cambios y a continuar construyendo nuestra aplicación: + +[source,bash] +---- +$ git commit -am "Adds the user create endpoint" +---- + +=== Actualizar usuarios + +El esquema para actualizar usuarios es muy similar a la de creación. Si eres un desarrollador Rails experimentado, ya sabes las diferencias entre estas dos acciones: + +* La accion update (actualizar) responde a una petición PUT/PATCH. +* Unicamente un usuario conectado deberia ser capáz de actualizar su información. Esto significa que tendremos que forzar a un usuario a autenticarse. Discutiremos esto en el capítulo 5. + +Como siempre, empezamos escribiendo nuestra prueba: + +[source,ruby] +.test/controllers/users_controller_test.rb +---- +# ... +class Api::V1::UsersControllerTest < ActionDispatch::IntegrationTest + # ... + test "should update user" do + patch api_v1_user_url(@user), params: { user: { email: @user.email, password: '123456' } }, as: :json + assert_response :success + end + + test "should not update user when invalid params are sent" do + patch api_v1_user_url(@user), params: { user: { email: 'bad_email', password: '123456' } }, as: :json + assert_response :unprocessable_entity + end +end +---- + +Para que la prueba se exitosa, debemos construir la accion update en el archivo `users_controller.rb` y agrgar la ruta al archivo `routes.rb`. Como puedes ver, tenemos mucho código duplicado, vamos a rediseñar nuestra prueba en el capítulo 4. Primero añadimos la accion al archivo `routes.rb`: + +[source,ruby] +.config/routes.rb +---- +Rails.application.routes.draw do + # ... + resources :users, only: %i[show create update] + # ... +end +---- + +Entonces implementamos la acción update en el controlador del usuario y corremos las pruebas: + +[source,ruby] +.app/controllers/api/v1/users_controller.rb +---- +class Api::V1::UsersController < ApplicationController + before_action :set_user, only: %i[show update] + + # GET /users/1 + def show + render json: @user + end + + # ... + + # PATCH/PUT /users/1 + def update + if @user.update(user_params) + render json: @user, status: :ok + else + render json: @user.errors, status: :unprocessable_entity + end + end + + private + # ... + + def set_user + @user = User.find(params[:id]) + end +end + +---- + +Todas nuestras pruebas deberian pasar: + +[source,bash] +---- +$ rails test +........ +8 runs, 11 assertions, 0 failures, 0 errors, 0 skips +---- + +Hacemos un _commit_ ya que todo funciona: + +[source,bash] +---- +$ git commit -am "Adds update action the users controller" +---- + +=== Delete the user + +Hasta aquí, hemos echo un monton de acciones en el controlador del usuario con sus propias pruebas pero no hemos terminado. Solo necesitamos una cosa mas, que es la acción de destruir. Asi que vamos a crear la prueba: + +[source,ruby] +.test/controllers/users_controller_test.rb +---- +# ... +class Api::V1::UsersControllerTest < ActionDispatch::IntegrationTest + # ... + + test "should destroy user" do + assert_difference('User.count', -1) do + delete api_v1_user_url(@user), as: :json + end + assert_response :no_content + end +end +---- + +Como puedes ver, la prueba es muy simple. Unicamente respondemos con estado *204* que significa `No Content` (Sin contenido). Tambien podriamos devolver un código de estado *200*, pero encuentro mas natural la respuesta `No Content` (Sin contenido) en este caso porque eliminamos un recurso y una respuesta exitosa podria ser bastante. + +La implementación de la acción de destruccion es muy simple: + +[source,ruby] +.app/controllers/api/v1/users_controller.rb +---- +class Api::V1::UsersController < ApplicationController + before_action :set_user, only: %i[show update destroy] + # ... + + # DELETE /users/1 + def destroy + @user.destroy + head 204 + end + + # ... +end +---- + +No olvides añadir la accion `destroy` en el archivo `routes.rb`: + +[source,ruby] +.config/routes.rb +---- +Rails.application.routes.draw do + # ... + resources :users, only: %i[show create update destroy] + # ... +end +---- + +Las pruebas deberian de pasar si todo es correcto: + +[source,bash] +---- +$ rails test +......... +9 runs, 13 assertions, 0 failures, 0 errors, 0 skips +---- + +Recuerda que despues de hacer algunos cambios en nuestro código, es buena practica hacerles _commit_ asi podremos tener un historial segmentado correctamente. + +[source,bash] +---- +$ git commit -am "Adds destroy action to the users controller" +---- + +Y a medida que lleamos al final de nuestro capítulo, es tiempo de aplicar nuestra modificaciones a la rama master haciendo un _merge_: + +[source,bash] +---- +$ git checkout master +$ git merge chapter03 +---- + +== Conclusión + +Oh, ahi tienes! Bien echo! Se que probablemente fue un largo tiempo, pero no te rindas! Asegurate de entender cada pieza del código, las cosas mejorarán, en el siguiente capítulo, vamos a rediseñar nuestras pruebas para hace nusetro código mas legible y mantenible. Entonces quedate conmigo! From b5152329f3a492cd36f1125693123abdb6f82039 Mon Sep 17 00:00:00 2001 From: Oscar Tellez Date: Sat, 14 Mar 2020 08:03:40 -0400 Subject: [PATCH 05/17] Chapter 4 to spanish --- rails6/es/chapter04-athentification.adoc | 554 +++++++++++++++++++++++ 1 file changed, 554 insertions(+) create mode 100644 rails6/es/chapter04-athentification.adoc diff --git a/rails6/es/chapter04-athentification.adoc b/rails6/es/chapter04-athentification.adoc new file mode 100644 index 0000000..2b66b82 --- /dev/null +++ b/rails6/es/chapter04-athentification.adoc @@ -0,0 +1,554 @@ +[#chapter05-athentification] += Autenticando al usuario + +Ha sido un largo tiempo desde que iniciamos. Espero que te guste este viaje tanto como a mí. + +En el capítulo anterior configuramos las entradas de recursos para los usuarios. Si te saltaste este capítulo ó si no entendiste todo, te recomiendo encarecidamente que lo mires. Éste cubre las primeras bases de las pruebas y es una introducción a respuestas JSON. + +Puedes clonar el proyecto hasta este punto: + +[source,bash] +---- +$ git checkout tags/checkpoint_chapter04 +---- + +En este capítulo las cosas se pondrán muy intersantes porque vamos a configurar el mecanismo de autenticación. En mi opinión es uno de los capítulos mas interesantes. Introduciremos un montón de terminos nuevos y terminarás con un simple pero poderoso sistema de autenticación. No sientas panico vamos por ello. + +La primera cosa es que primero (y como es usual cuando iniciamos un nuevo capítulo) vamos a crear una nueva rama: + +[source,bash] +---- +$ git checkout -b chapter04 +---- + +== Sesion sin estado + +Antes de que hagamos algo, algo debe estar claro: *una API no maneja sesiones*. Si no tienes experiencia construyendo este tipo de aplicaciones puede sonar un poco loco pero quedate conmigo. Un API puede ser sin estado lo cual significa por definición _es una que provee una respuesa despues de tú peticion, y luego no requiere más atención_. Lo cual significa que un estado previo o un estado futuro no es requerido para que esl sistema trabaje. + +El flujo para autenticar al usuario mediante una API es muy simple: + +. La petición del cliente para el recurso `sessions` con las correspondientes credenciales (usualmente email y password) +. El server regresa el recurso `user` junto con su correspondiente token de autenticación +. Para cada página que requiere autenticación el cliente tiene que enviar el `token de autenticación` + +Por supuesto estos no son los únicos 3 pasos a seguir, y en el paso 2 debería pensar, bien yo realmente necesito responder con la información del usuario o solo el `token de autenticación`? Yo podría decir que eso realmente depende de tí, pero a mí me gusta regresar el usuario completo, de esta forma puedo mapearlo de inmediato en mi cliente y guardar otra posible solicitud que haya sido echa. + +En esta sección y la siguiente vamos a enfocarnos en construir un controllador de sesiones junto a sus acciones correspondientes. Vamos entonces a completar el flujo de solicitudes agregando los accesos de autorización necesarios. + + +=== Presentación de JWT + +Cuando nos acercamos a los tokens de autenticación, tenemos un estandar: el JSON Web Token (JWT). + +> JWT es un estandar abierto definido en RFC 75191. Este permite el intercambio seguro de tokens entre varias partes. - https://wikipedia.org/wiki/JSON_Web_Token_Web_Token[Wikipedia] + +En general un token JWT se compone de tres partes: + +- un *header* estructurado en JSON contiene por ejemplo la fecha de validación del token. +- un *payload* estructurado en JSON puede contener *cualquier dato*. En nuestro caso, contiene el indetificador del usuario "conectado". +- un *signature* que nos permite verificar que el token fue encriptado por nuestra aplicación y es por lo danto válido. + +Estas tres partes son cada una codificadas en base64 y entonces concatenadas usando puntos (`.`). Lo cual nos da algo como: + +.Un token JWT válido +---- +eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c +---- + +Una ves decodificado, este token nos da la siguiente información: + +.La cabecera del tojen JWT +[source,json] +---- +{ "alg": "HS256", "typ": "JWT" } +---- + +.El payload de el token JWT +[source,json] +---- +{ "sub": "1234567890", "name": "John Doe", "iat": 1516239022 } +---- + +NOTE: Para mas información sobre tokens JWT te invito a visiar https://jwt.io[jwt.io] + +Esto tien muchas ventajas justo como enviar información en payload de tokens. Por ejemplo, podemos elegir integrar información del usuario en el _payload_. + +=== Configurando el token de autenticación + +El estandar JWT tiene muchas implementaciones en varios lenguajes y librerias. Por supuesto, hay una gema de Ruby en este tema: https://github.com/jwt/ruby-jwt[ruby-jwt]. + +Asi que vamos a comenzar instalandola: + +[source,bash] +---- +$ bundle add jwt +---- + +Una vez completada la siguiente linea es añadida a tu _Gemfile_: + +[source,ruby] +---- +gem "jwt", "~> 2.2" +---- + +La librería es muy simple. Hay dos metodos: `JWT.encode` y `JWT.decode`. Vamos a abrir una terminal con `console rails` y a correr algunas pruebas: + +[source,ruby] +---- +2.6.3 :001 > token = JWT.encode({message: 'Hello World'}, 'my_secret_key') +2.6.3 :002 > JWT.decode(token, 'my_secret_key') + => [{"message"=>"Hello World"}, {"alg"=>"HS256"}] +---- + +En la primera linea codificamos un _payload_ con la llave secreta `my_secret_key`. Asi obtenemos un token que podemos decodificar de manera simple. La segunda linea decodifica el token y vemos que podemos encontrar sin dilema nuestro _payload_. + +Vamos a incluir toda la logica en una clase `JsonWebToken` en un nuevo archivo localizado en `lib/`. Esto nos permite evitar el código duplicado. Esta clase justamente codificará y decodificará los tokens JWT. Así que aqui esta la implementación. + +.lib/json_web_token.rb +[source,ruby] +---- +class JsonWebToken + SECRET_KEY = Rails.application.secrets.secret_key_base.to_s + + def self.encode(payload, exp = 24.hours.from_now) + payload[:exp] = exp.to_i + JWT.encode(payload, SECRET_KEY) + end + + def self.decode(token) + decoded = JWT.decode(token, SECRET_KEY).first + HashWithIndifferentAccess.new decoded + end +end +---- + +Yo se que es un monton de código pero lo revisaremos juntos. + +- el método `JsonWebToken.encode` se encarga de codificar el _payload_ añadiendo una fecha de expiración de 24 horas por defecto. Ademas usamos la misma llave de encriptación que viene configurada con Rails. +- el método `JsonWebToken.decode` decodifica el token JWT y obtiene el _payload_. Entonces usamos la clase https://api.rubyonrails.org/classes/ActiveSupport/HashWithIndifferentAccess.html[`HashWithIndifferentAccess`] proveída por Rails la cual nos permite recuperar un valor de un `Hash` con un `Symbol` ó `String`. + +Ahí tienes. Para cargar el archivo en tú aplicación, necesitas especificar el directorio `lib` en la lista de _autoload de Ruby on rails. Para hacerlo, agrega la siguiente configuración al archivo `application.rb`: + +.config/application.rb +[source,ruby] +---- +# ... +module MarketPlaceApi + class Application < Rails::Application + # ... + config.eager_load_paths << Rails.root.join('lib') + end +end +---- +Y eso es todo. Ahora es tiempo de hacer un commit: + +[source,bash] +---- +$ git add . && git commit -m "Setup JWT gem" +---- + + +=== Controlador de Token + +Tenemos sin embargo que configurar el sistema para generar un token JWT. Es ahora tiempo de crear una ruta que generará este token. Las acciones que implementaremos seran administradas como servicios _RESTful_: la conexión sera gestionada por una peticion POST a la acción `create`. + +Para empezar, iniciaremos creando el controlador y el método `create` en el _namespace_ `/api/v1`. Con Rails, una orden es suficiente: + + +[source,bash] +---- +$ rails generate controller api::v1::tokens create +---- + +Modificaremos la ruta un poco para respetar las convenciones _REST_: + +.config/routes.rb +[source,ruby] +---- +Rails.application.routes.draw do + namespace :api, defaults: { format: :json } do + namespace :v1 do + # ... + resources :tokens, only: [:create] + end + end +end +---- + + +Vamos a construir pruebas funcionales antes de ir mas lejos. El comportamiento deseado es el siguiente: + +- Yo recibo un token si envio un email valido junto con el password +- de otro modo el server responde un `forbidden` + +Las pruebas por lo tanto se materilizan de la siguiente forma: + +.test/controllers/api/v1/tokens_controller_test.rb +[source,ruby] +---- +require 'test_helper' + +class Api::V1::TokensControllerTest < ActionDispatch::IntegrationTest + setup do + @user = users(:one) + end + + test 'should get JWT token' do + post api_v1_tokens_url, params: { user: { email: @user.email, password: 'g00d_pa$$' } }, as: :json + assert_response :success + + json_response = JSON.parse(response.body) + assert_not_nil json_response['token'] + end + + test 'should not get JWT token' do + post api_v1_tokens_url, params: { user: { email: @user.email, password: 'b@d_pa$$' } }, as: :json + assert_response :unauthorized + end +end +---- + +Te estarás preguntando: "pero como puedes saber la contraseña del usuario?". Simplemente usa el método `BCrypt::Password.create` en los _fixtures_ de `users`: + +.test/fixtures/users.yml +[source,yaml] +---- +one: + email: one@one.org + password_digest: <%= BCrypt::Password.create('g00d_pa$$') %> +---- + +En este preciso momento, si corres las pruebas obtendrás dos errores: + +[source,bash] +---- +$ rake test + +........E + +Error: +Api::V1::TokensControllerTest#test_should_get_JWT_token: +JSON::ParserError: 767: unexpected token at '' + + +Failure: +Expected response to be a <401: unauthorized>, but was a <204: No Content> +---- + +Es normal. Ahora es tiempo de implementar la logica para crear el token JWT. Es muy sencillo. + +.app/controllers/api/v1/tokens_controller.rb +[source,ruby] +---- +class Api::V1::TokensController < ApplicationController + def create + @user = User.find_by_email(user_params[:email]) + if @user&.authenticate(user_params[:password]) + render json: { + token: JsonWebToken.encode(user_id: @user.id), + email: @user.email + } + else + head :unauthorized + end + end + + private + + # Only allow a trusted parameter "white list" through. + def user_params + params.require(:user).permit(:email, :password) + end +end +---- + +Es un montón de codigo pero es muy simple: + +. Siempre filtramos los parametros con el metodo `user_params`. +. Recuperamos el usuario con el método `User.find_by_email` (que es un metodo "mágico" de _Active Record_ mientras el campo `email` este presente en la base de datos) y recuperamos el usuario +. Usamos el método `User#authenticate` (el cual existe gracias a la gema `bcrypt`) con la contraseña como un parameto. Bcrypt hará un _hash_ de la contraseña y verifica si coincide con el atributo `password_digest`. La funcion regresa `true` si todo salio bien, `false` si no. +. Si la contraseña corresponde al _hash_, un JSON conteniendo el _token_ generado con la clase `JsonWebToken` es devuelto. De otro modo, una respuesta vacía es devuelta con una cabecera `unauthorized` + +Estas hasta aquí? No te preocupes, esta terminado! Ahora tus pruebas deberían pasar. + +[source,bash] +---- +$ rake test + +........... + +Finished in 0.226196s, 48.6304 runs/s, 70.7351 assertions/s. +11 runs, 16 assertions, 0 failures, 0 errors, 0 skips +---- + +Muy bien! Es tiempo de hacer un commit que contendra todos nuestros cambios: + +[source,bash] +---- +$ git add . && git commit -m "Setup tokens controller" +---- + + +== Usuario logueado + +Entonces ya implementamos la siguiente lógica: la API retorna el token de autenticación a el cliente si las credenciales son correctas. + +Pero ahora implementaremos la siguiente lógica: encontraremos el usuario correspondiente del token de autenticación proporcionado en la cabecera HTTP. Necesitamos hacerlo cada vez que este cliente solicite un `entry point` que requiera permisos. + +Usaremos la cabecera HTTP `Authorization` que a menudo es usada para este proposito. Tambien podemos usar un parametro GET llamado `apiKey` pero prefiero usar una cabecera HTTP porque da contexto a la petición sin contaminar la URL con parametros adicionales. + +Por lo tanto crearemos un método `current_user` para satisfacer nuestras necesidades. Este encontrará el usuario gracias a su token de autenticación que es enviado en cada petición. + +Cuando se trata de autenticación, me gusta añadir todos los metodos asociados en un archivo separado. Entonces simplemente incluimos el archivo `ApplicationController`. De este modo, es muy facil para probar de forma aislada. Vamos a crear el archivo en el directorio `controllers/concerns` con un metodo `current_user` que implementaremos despues: + +[source,ruby] +.app/controllers/concerns/authenticable.rb +---- +module Authenticable + def current_user + # TODO + end +end +---- + +Entonces, vamos a crear un directorio `concerns` en `tests/controllers/` y un archivo `authenticable_test.rb` para nuestras pruebas de a autenticación: + + +[source,bash] +---- +$ mkdir test/controllers/concerns +$ touch test/controllers/concerns/authenticable_test.rb +---- + +Como es usual, iniciamos por escribir nuestra prueba. En este caso, nuestro método `current_user` buscará un usuario por el token de autenticación en la cabecera HTTP `Authorization`. La prueba es muy básica: + +[source,ruby] +.test/controllers/concerns/authenticable_test.rb +---- +# ... +class AuthenticableTest < ActionDispatch::IntegrationTest + setup do + @user = users(:one) + @authentication = MockController.new + end + + test 'should get user from Authorization token' do + @authentication.request.headers['Authorization'] = JsonWebToken.encode(user_id: @user.id) + assert_equal @user.id, @authentication.current_user.id + end + + test 'should not get user from empty Authorization token' do + @authentication.request.headers['Authorization'] = nil + assert_nil @authentication.current_user + end +end +---- + +Te estaras preguntando, "De donde viene el controlador `MockController`?", De echo, éste es un _Mock_, por ejemplo una clase que imita el comportamiento de otra para probar un comportamiento + +Podemos definir la clase `MockController` justo sobre nuestra prueba: + +[source,ruby] +.test/controllers/concerns/authenticable_test.rb +---- +# ... +class MockController + include Authenticable + attr_accessor :request + + def initialize + mock_request = Struct.new(:headers) + self.request = mock_request.new({}) + end +end +# ... +---- + +La clase `MockController` simplemente incluye nuestro módulo `Authenticable` que probaremos. Este contiene un atributo `request` que contiene un simple https://ruby-doc.org/core-2.6.3/Struct.html[`Struct`] que imita el comportamiento de una petición Rails conteniendo un atributo `headers` de tipo `Hash`. + +Entonces podemos implementar nuestras dos pruebas ahora + +[source,ruby] +.test/controllers/concerns/authenticable_test.rb +---- +# ... +class AuthenticableTest < ActionDispatch::IntegrationTest + setup do + @user = users(:one) + @authentication = MockController.new + end + + test 'should get user from Authorization token' do + @authentication.request.headers['Authorization'] = JsonWebToken.encode(user_id: @user.id) + assert_not_nil @authentication.current_user + assert_equal @user.id, @authentication.current_user.id + end + + test 'should not get user from empty Authorization token' do + @authentication.request.headers['Authorization'] = nil + assert_nil @authentication.current_user + end +end +---- + +Nuestra prueba debería fallar. Asi que vamos a implementar el código para que ésta pase: + +[source,ruby] +.app/controllers/concerns/authenticable.rb +---- +module Authenticable + def current_user + return @current_user if @current_user + + header = request.headers['Authorization'] + return nil if header.nil? + + decoded = JsonWebToken.decode(header) + + @current_user = User.find(decoded[:user_id]) rescue ActiveRecord::RecordNotFound + end +end +---- + +Ahí tienes! Obtenemos el token desde la cabecera `Authorization` y buscamos el usuario correspondiente. Nada tan mágico. + +Nuestra prueba debería pasar: + +[source,bash] +---- +$ rake test +............. +13 runs, 18 assertions, 0 failures, 0 errors, 0 skips +---- + +Todo lo que tenemos que hacer es incluir el módulo `Authenticable` en la clase `ApplicationController`: + +[source,ruby] +.app/controllers/application_controller.rb +---- +class ApplicationController < ActionController::API + # ... + include Authenticable +end +---- + +Y ahora es tiempo de hacer _commit_ a nuestros cambios: + +[source,bash] +---- +$ git add . && git commit -m "Adds authenticable module for managing authentication methods" +---- + +== Autenticación con el token + +La autorización juega un papel importante en la construcción de aplicaciones porque nos ayuda a definir que usuario tiene permisos para continuar. + +Tenemos una ruta para actualizar el usuario pero es un problema: cualquiera puede actualizar cualquier usuario. En esta seccion, vamos a implementar un metodo que requerirá al usuario estar logueado para prevenir accesos no autorizados. + +=== Acciones de autorización + +Es tiempo ahora de actualizar nuestro archivo `users_controller.rb` para negar el acceso a ciertas acciones. Vamos tambien a implementar el método `current_user` en las acciones `update` y `destroy` para asegurarnos que el usuario que esta logueado solo podrá actualizar sus datos y puede unicamente borrar (y solo) su cuenta. + +Por lo tanto dividimos nuestra prueba en dos pruebas _should update user_ y _should destroy user_. + +Iniciamos por actualizar la prueba _should update user_. + +.test/controllers/api/v1/users_controller_test.rb +[source,ruby] +---- +# ... +class Api::V1::UsersControllerTest < ActionDispatch::IntegrationTest + # ... + test "should update user" do + patch api_v1_user_url(@user), + params: { user: { email: @user.email } }, + headers: { Authorization: JsonWebToken.encode(user_id: @user.id) }, + as: :json + assert_response :success + end + + test "should forbid update user" do + patch api_v1_user_url(@user), params: { user: { email: @user.email } }, as: :json + assert_response :forbidden + end +end +---- + +Puedes ver ahora que tenemos que añadir una cabecera _Authorization_ para la acción de modidficar usuarios. De lo contrario queremos recibir una respuesta _forbidden_. + +Podemos pensar de forma similar para la prueba _should forbid destroy user_: + +.test/controllers/api/v1/users_controller_test.rb +[source,ruby] +---- +# ... +class Api::V1::UsersControllerTest < ActionDispatch::IntegrationTest + # ... + test "should destroy user" do + assert_difference('User.count', -1) do + delete api_v1_user_url(@user), headers: { Authorization: JsonWebToken.encode(user_id: @user.id) }, as: :json + end + assert_response :no_content + end + + test "should forbid destroy user" do + assert_no_difference('User.count') do + delete api_v1_user_url(@user), as: :json + end + assert_response :forbidden + end +end +---- + +Por el momento estas pruebas pueden fallar como ya lo podrías esperar: + +[source,bash] +---- +$ rails test test/controllers/api/v1/users_controller_test.rb +..F + +Failure: +Expected response to be a <2XX: success>, but was a <403: Forbidden> + +..F + +Failure: +"User.count" didn t change by -1. +Expected: 0 + Actual: 1 +---- + +La solución es muy simple. Vamos a añadir un `before_action` el cual llamará al método `check_owner` para las acciones `update` y `destroy`. De esta forma comprobamos que el usuario que corresponde al token JWT es el mismo que el usuario que necesita ser actualizado. + +Ésta es la implementación: + +[source,ruby] +.app/controllers/api/v1/users_controller.rb +---- +class Api::V1::UsersController < ApplicationController + before_action :set_user, only: %i[show update destroy] + before_action :check_owner, only: %i[update destroy] + # ... + + private + # ... + def check_owner + head :forbidden unless @user.id == current_user&.id + end +end +---- + +Ahí tienes! La implementación es realmente simple. Es por lo tanto tiempo de hacer un _commit_: + +[source,bash] +---- +$ git commit -am "Restrict actions for unauthorized users" +$ git checkout master +$ git merge chapter04 +---- + +== Conclusión + +Yeah! lo hiciste! tienes medio camino terminado! Manten este buen trabajo. Éste capítulo fue largo y dificil pero es un gran paso a seguir para implementar un mecanismo solido para manipular autenticación de usuarios. Incluso logramos tocar la superficie para implemenatar reglas simples de autenticación. + +En el proximo capítulo nos enfocaremos en la personalizacion de las salidas JSON para el usuario con la gema https://github.com/Netflix/fast_jsonapi[fast_jsonapi] y añadiremos un modelo `product` a la ecuación dando al usuario la habilidad para crear un producto y publicarlo para su venta. From cf809fdbcdebc83cece3ec0d3daa75763bb46523 Mon Sep 17 00:00:00 2001 From: Oscar Tellez Date: Mon, 16 Mar 2020 06:55:38 -0400 Subject: [PATCH 06/17] Chapter 5 to spanish --- rails6/es/chapter05-user-products.adoc | 762 +++++++++++++++++++++++++ 1 file changed, 762 insertions(+) create mode 100644 rails6/es/chapter05-user-products.adoc diff --git a/rails6/es/chapter05-user-products.adoc b/rails6/es/chapter05-user-products.adoc new file mode 100644 index 0000000..c4d92ad --- /dev/null +++ b/rails6/es/chapter05-user-products.adoc @@ -0,0 +1,762 @@ +[#chapter05-user-products] += Productos de usuario + +En el capítulo anterior, implementamos el mecanismo de autenticación que usaremos a travez de la aplicación. + +Por el momento tenemos una implementación del odelo `User` pero el momento de la verdad ha llgado. Vamos a personalizar la salida JSON añadir un segundo recurso: los productos del usuario. Estos son los elementos que el usuario va a comprar en la aplicación y por lo tanto enlazaremos directamente. + +Si estas familiriarizado con Rails, ya sabes de que estoy hablando. Pero para aquellos que no lo saben, vamos a asociar el modelo `User` con el modelo `Product` usando los metodos de _Active Record_ `has_many` y `belongs_to` + +En este capítulo vamos a: + +* contruir el modelo `Product` desde cero +* asociarlo con el usuario +* crear las entradas necesarias asi cualquier cliente puede acceder a la información. + +Puedes clonar el proyecto hasta este punto: + +[source,bash] +---- +$ git checkout tags/checkpoint_chapter05 +---- + +Antes que iniciemos y como es usual cuando iniciamos con nuevas caracteristicas necesitaremos crear una nueva rama: + +[source,bash] +---- +$ git checkout -b chapter05 +---- + +== El modelo producto + +Priermo crearemos un modelo `Product`. Entonces añadiremos validaciones y finalmente lo asociamos con el modelo `User. Como el modelo `User`, el modelo `Product` será completamente probado y sera automaticamente eliminado si el usuario es eliminado. + +=== Los funamentos del producto + +La plantilla `Product` necesitara varios campos: + +* un atributo `price` para el precio del producto +* un booleano `published` para saber si el producto ya está vendido o no +* un `title` para definir un título sexy al producto +* un `user_id` para asociar este producto particular a un usuario + + Como puedes adivinar lo generamos con el comando `rails generate`: + +[source,bash] +---- +$ rails generate model Product title:string price:decimal published:boolean user:belongs_to +Running via Spring preloader in process 1476 + invoke active_record + create db/migrate/20190608205942_create_products.rb + create app/models/product.rb + invoke test_unit + create test/models/product_test.rb + create test/fixtures/products.yml +---- + +NOTE: Usamos el el tipo `belongs_to` para el attributo `user`. Este es un atajo que creará una columna `user_id` de tipo `int` y entonces añade una llave foranea a el campo `users.id`. En adición, `user_id` tambien sera definiada como un `index` (índice). Esta es una buena práctica para la asociación de llaves porque esto optimiza las consultas de la base de datos. No es obligatorio, pero es altamente recomendado. + +El archivo de migracion deberia lucir asi: + +[source,ruby] +.db/migrate/20190608205942_create_products.rb +---- +class CreateProducts < ActiveRecord::Migration[6.0] + def change + create_table :products do |t| + t.string :title + t.decimal :price + t.boolean :published + t.belongs_to :user, null: false, foreign_key: true + + t.timestamps + end + end +end +---- + +Ahora solo tenemos que inciar la migracion: + +[source,bash] +---- +$ rake db:migrate +---- + +Una prueba debería de fallar hasta este punto: + +[source,bash] +---- +$ rake test +....E + +Error: +Api::V1::UsersControllerTest#test_should_destroy_user: +ActiveRecord::InvalidForeignKey: SQLite3::ConstraintException: FOREIGN KEY constraint failed + +rails test test/controllers/api/v1/users_controller_test.rb:43 +---- + +Seguramente dirás: + +> Que?! Pero no he tocado los usuarios!. + +Lo que he visto en el código de otros desarrolladores, cuando ellos trabajan con asociaciones, es que se olvidan de la destrucción de dependencias entre modelos. Lo que digo con esto es que si un usuario es eliminado, tambien lo deberian de ser los productos del usuario. + +Necesitamos un suario con uno de los productos para probar esta interacción entre modelos. Entones eliminaremos este usuario esperando que los productos desaparezcan con él. Rails ya tiene generado esto por nosotros. Echa un vistaso a el _fixture_ de los productos: + + +.test/fixtures/products.yml +[source,yaml] +---- +one: + title: MyString + price: 9.99 + published: false + user: one +# ... +---- + +Puedes ver que este _fixture_ no usa el atributo `user_id` pero si `user`. Esto significa que el producto `one` tendrá un atributo `user_id` correspondiente a el ID de usuario `one`. + +Es por lo tanto necesario especificar un borrado en cascada a fin de que sea eliminado el producto `onde` cuando el usuario `one` es eliminado. Vamos empezar con la prueba unitaria: + + +.test/models/user_test.rb +[source,ruby] +---- +# ... +class UserTest < ActiveSupport::TestCase + # ... + test 'destroy user should destroy linked product' do + assert_difference('Product.count', -1) do + users(:one).destroy + end + end +end +---- + +Justamente tienes que modificar el modelo `User` y especificar la relacion `has_many` con la opción `depend: :destroy`. Veremos mas tarde que hace este método con mas detalle. + +.app/models/user.rb +[source,ruby] +---- +# ... +class User < ApplicationRecord + # ... + has_many :products, dependent: :destroy +end +---- +<<< +Eso es todo. Ahor hacemos un _commit_: + +[source,bash] +---- +$ git add . && git commit -m "Generate product model" +---- + + + +=== Validaciones del producto + +Las validaciones son una parte importante cuando construimos cualquier típo de aplicación. Esto evitará que cualquier dato basura sea guardado en la base de datos. En el producto tenemos que asegurarnos que por ejemplo el precio es un `number` (número) y que no es negativo. + +Tambien una cosa importante sobre la validación es validar que cada producto tiene un usuario. En este caso necesitamos validar la precencia de el `user_id`. Puedes ver que estoy hablando en siguiente fragmento de código. + +[source,ruby] +.test/models/product_test.rb +---- +# ... +class ProductTest < ActiveSupport::TestCase + test "should have a positive price" do + product = products(:one) + product.price = -1 + assert_not product.valid? + end +end +---- + +Ahora necesitamos añadir la implementación para hacer que la prueba pase: + +[source,ruby] +.app/models/product.rb +---- +class Product < ApplicationRecord + validates :title, :user_id, presence: true + validates :price, numericality: { greater_than_or_equal_to: 0 }, presence: true + belongs_to :user +end +---- + +La prueba ahora esta en verde: + +[source,bash] +---- +$ rake test +................ +---- + +Tenemos un monton de código de buena calidad. Hagamos un commir y sigamos moviendonos: + +[source,bash] +---- +$ git commit -am "Adds some validations to products" +---- + + +== Endpoints de productos + +Ahora es tiempo de empezar a construir lo endpoints de los productos. Por ahora solo construiremos las cinco acciones REST. En el siguiente capítulo vamos a personalizar la salida JSON implementando la gema https://github.com/Netflix/fast_jsonapi[fast_jsonapi]. + +Primero necesitamos crear el controlador `products_controller`, y facilmente podemos lograrlo con el comando: + +[source,bash] +---- +$ rails generate controller api::v1::products + create app/controllers/api/v1/products_controller.rb + invoke test_unit + create test/controllers/api/v1/products_controller_test.rb +---- + +El comando anterior generará un monton de archivos que nos permitirán empezar a trabajar rápidamente. Lo que quiero decir con esto es ya generará el controlador y el archivo de prueba con un _scoped_ (alcanse) hacia la version 1 del API. + +Como calentamiento iniciaremos bien y facil contrullendo la acción `show` para el producto. + +=== Acción show para productos + +Como es usual iniciaremo por añador algunas especificaciones para la acción `show` dpara el producto en su controlador. La estrategia aqui es muy simple: justamente necesitamos crear un único producto y asegurar que la respuesta desde el server es la que esperamos. + +[source,ruby] +.test/controllers/api/v1/products_controller_test.rb +---- +# ... +class Api::V1::ProductsControllerTest < ActionDispatch::IntegrationTest + setup do + @product = products(:one) + end + + test "should show product" do + get api_v1_product_url(@product), as: :json + assert_response :success + + json_response = JSON.parse(self.response.body) + assert_equal @product.title, json_response['title'] + end +end +---- + +Entonces añadimos el codigo que hará pasar las pruebas: + +[source,ruby] +.app/controllers/api/v1/products_controller.rb +---- +class Api::V1::ProductsController < ApplicationController + def show + render json: Product.find(params[:id]) + end +end +---- + +Espera! Aun no corras las pruebas. Recuerda que necesitamos añadir el recuro al archivo `routes.rb`: + +[source,ruby] +.config/routes.rb +---- +Rails.application.routes.draw do + namespace :api, defaults: { format: :json } do + namespace :v1 do + resources :users, only: %i[show create update destroy] + resources :tokens, only: [:create] + resources :products, only: [:show] + end + end +end +---- + +Ahora nos aseguramos que las pruebas estan bien y en verde: + +[source,bash] +---- +$ rake test +................. +---- + +Como puedes notar ahora las especificaciones e implementación son muy sencillas. En realidad se comportan igual que el usuario. + +=== Listado de productos + +Ahora es tiempo de devolver una lista de productos (los cuales seran mostrados como catálogo de productos de la tieda). Este endpoint debe ser accesible sin credenciales. Significa que no requerimos que el usuario este logueado para acceder a la información. Como es usual empezaremos escribiendo algunas pruebas: + +[source,ruby] +.test/controllers/api/v1/products_controller_test.rb +---- +# ... +class Api::V1::ProductsControllerTest < ActionDispatch::IntegrationTest + setup do + @product = products(:one) + end + + test "should show products" do + get api_v1_products_url(), as: :json + assert_response :success + end + + test "should show product" do + get api_v1_product_url(@product), as: :json + assert_response :success + + json_response = JSON.parse(self.response.body) + assert_equal @product.title, json_response['title'] + end +end +---- + +Vamos a la implementación, la cual por ahora esta siendo un metodo `index` simple: + +[source,ruby] +.app/controllers/api/v1/products_controller.rb +---- +class Api::V1::ProductsController < ApplicationController + def index + render json: Product.all + end + #... +end +---- + +No olvides añadir la ruta correspondiente: + +[source,ruby] +.config/routes.rb +---- +Rails.application.routes.draw do + namespace :api, defaults: { format: :json } do + namespace :v1 do + # .... + resources :products, only: %i[show index] + end + end +end +---- + +Terminamos por ahora con el endopint al producto público. En la siguiente seccion nos enfocaremos en la contrucción de las acciones solicitando un usuario logueado para acceder a ellos. Dicho esto haremos commit de estos cambios y continuamos. + +[source,bash] +---- +$ git add . && git commit -m "Finishes modeling the product model along with user associations" +---- + +=== Creando productos + +Crear productos es un poco mas complejo porque necesitaremos una configuración adicional. La estratecia que seguiremos es asginar el producto creado al usuario que petenece al token JWT proporcionado en la cabecera HTTP `Authorization`. + +Asi que inciamos ocn el archivo `products_controller_test.rb`: + +[source,ruby] +.test/controllers/api/v1/products_controller_test.rb +---- +# ... +class Api::V1::ProductsControllerTest < ActionDispatch::IntegrationTest + # ... + + test 'should create product' do + assert_difference('Product.count') do + post api_v1_products_url, + params: { product: { title: @product.title, price: @product.price, published: @product.published } }, + headers: { Authorization: JsonWebToken.encode(user_id: @product.user_id) }, + as: :json + end + assert_response :created + end + + test 'should forbid create product' do + assert_no_difference('Product.count') do + post api_v1_products_url, + params: { product: { title: @product.title, price: @product.price, published: @product.published } }, + as: :json + end + assert_response :forbidden + end +end +---- + +Wow! Añadimos un montón de código. Si recuerdas la sección anterior, las pruebas son muy similares que las de la creacion de usuarios. Excepto por algunos cambios menores. + +De esta forma, podemos ver al usuario y la creación del producto asiciado con el. Pero espera! Hay algo mejor. + +Si adoptamos este enfoque, podemos incrementear el alcance de nuestro mecanismo de autenticación. Realmente construimos la lógica para obtener al usuario logueado desde la cabecera `Authorization` y asignarele un método. Es por lo tanto bastante facil de configurar simplemente añadiendo la cabecera de autorización a la solicitud y recuperando el usuario desde eso. Asi que vamos a hacerlo. +?????????????????????????????????????????????? +If we adopt this approach, we can increase the scope of our authorization mechanism. We actually built the logic to get logged user from the header `Authorization` and assigned him a method `current_user`. It is therefore quite easy to set up by simply adding the authorization header to the request and retrieving the user from it. So let's do it: + + +[source,ruby] +.app/controllers/api/v1/products_controller.rb +---- +class Api::V1::ProductsController < ApplicationController + before_action :check_login, only: %i[create] + # ... + + def create + product = current_user.products.build(product_params) + if product.save + render json: product, status: :created + else + render json: { errors: product.errors }, status: :unprocessable_entity + end + end + + private + + def product_params + params.require(:product).permit(:title, :price, :published) + end +end +---- + +Como puedes ver, protejemos la acción `create` con el método `check_login`. También creamos al producto por asociación con el usuario. Yo agregué este método tan sencillo a el _concern_ del archivo `authenticable.rb`: + +[source,ruby] +.app/controllers/concerns/authenticable.rb +---- +module Authenticable + # ... + protected + + def check_login + head :forbidden unless self.current_user + end +end +---- + +Una última cosa antes de hacer tus pruebas: la ruta necesaria: + +[source,ruby] +.config/routes.rb +---- +Rails.application.routes.draw do + namespace :api, defaults: { format: :json } do + namespace :v1 do + # ... + resources :products, only: %i[show index create] + end + end +end + +---- + +Ahora las pruebas deberian pasar: + +.... +$ rake test +.................... +.... + + +=== Actualizando los productos + +Espero que por ahora entiendas la lógica para construir la acciones que vienen. En esta sección nos enfocaremos en la acción `update` que funcionará a la acción `create`. Solamene necesitamos buscar el producto des la base de datos y actualizarlo. + +Añadiremos primer la acción a las rutas asi no nos olvidamos despues: + +[source,ruby] +.config/routes.rb +---- +Rails.application.routes.draw do + namespace :api, defaults: { format: :json } do + namespace :v1 do + # ... + resources :products, only: %i[show index create update] + end + end +end +---- + +Antes de iniciar borrando alguna prueba quiero aclarar que similarmente a la acción `create` vamos a dar alcance en el producto al con el método `current_user`. En este caso queremos asegurar que el producto que se esta actualizando pertenece al suauri actual. In this case we want to make sure the product we are updating is owned by the current user. Asi que buscaremos los productos de la asociación `user.products` proveída por Rails. + +Let's add some specs: + +[source,ruby] +.test/controllers/api/v1/products_controller_test.rb +---- +require 'test_helper' + +class Api::V1::ProductsControllerTest < ActionDispatch::IntegrationTest + # ... + + test 'should update product' do + patch api_v1_product_url(@product), + params: { product: { title: @product.title } }, + headers: { Authorization: JsonWebToken.encode(user_id: @product.user_id) }, + as: :json + assert_response :success + end + + test 'should forbid update product' do + patch api_v1_product_url(@product), + params: { product: { title: @product.title } }, + headers: { Authorization: JsonWebToken.encode(user_id: users(:two).id) }, + as: :json + assert_response :forbidden + end +end +---- + + +NOTE: Tengo añadido un _fixture_ correspondiente a un segundo usuario justo para verificar que el segundo usuario no puede modificar productos del primer usuario. + +Las pruebas parecen complejas pero echa un segundo vistazo. Son casi lo mismo que construimos para los usuarios. + +Ahora vamos a implementar el código para hacer pasar nuestras pruebas: + +[source,ruby] +.app/controllers/api/v1/products_controller.rb +---- +class Api::V1::ProductsController < ApplicationController + before_action :set_product, only: %i[show update] + before_action :check_login, only: %i[create] + before_action :check_owner, only: %i[update] + + # ... + + def create + product = current_user.products.build(product_params) + if product.save + render json: product, status: :created + else + render json: { errors: product.errors }, status: :unprocessable_entity + end + end + + def update + if @product.update(product_params) + render json: @product + else + render json: @product.errors, status: :unprocessable_entity + end + end + + private + # ... + + def check_owner + head :forbidden unless @product.user_id == current_user&.id + end + + def set_product + @product = Product.find(params[:id]) + end +end +---- + +La implementación es muy simple. Simplemente recuperaremos el productodesde el usuario conectad y simplemente lo actualizamos. Tenemos tambien agregadas esta acción a el `before_action` para prevenir cualquier usuario no autorizado desde la actualización de un producto. + +Ahora las pruebas deberían pasar: + +[source,bash] +---- +$ rake test +...................... +---- + + +=== Destruyendo productos + +Nuestra última parada para los endpoints de los productos sera la accion `destroy` (destruir). Podrias ahora imaginar como se vería esto. La estrategía aquí sera demasiado similar a las acciones `create` y `destroy`: obtenemos al usuario logueado con el token JWT y entonces buscamos el producto desde la asociación `user.products` y finalmente lo destruimos, regresamos un código `204`. + +Vamos a iniciar de nuevo añadiendo el nombre de la ruta al archivo de rutas: + +[source,ruby] +.config/routes.rb +---- +Rails.application.routes.draw do + namespace :api, defaults: { format: :json } do + namespace :v1 do + resources :users, only: %i[show create update destroy] + resources :tokens, only: [:create] + resources :products + end + end +end +---- + +Despues de esto, tenemos que añadir algunas pruebas como se muestra en este fragmento de código: + +[source,ruby] +.test/controllers/api/v1/products_controller_test.rb +---- +# ... +class Api::V1::ProductsControllerTest < ActionDispatch::IntegrationTest + # ... + + test "should destroy product" do + assert_difference('Product.count', -1) do + delete api_v1_product_url(@product), headers: { Authorization: JsonWebToken.encode(user_id: @product.user_id) }, as: :json + end + assert_response :no_content + end + + test "should forbid destroy product" do + assert_no_difference('Product.count') do + delete api_v1_user_url(@product), headers: { Authorization: JsonWebToken.encode(user_id: users(:two).id) }, as: :json + end + assert_response :forbidden + end +end +---- + + +Ahora simplemente añadimos el código necesario para hacer pasar las pruebas: + +[source,ruby] +.app/controllers/api/v1/products_controller.rb +---- +class Api::V1::ProductsController < ApplicationController + before_action :set_product, only: %i[show update destroy] + before_action :check_login, only: %i[create] + before_action :check_owner, only: %i[update destroy] + + # ... + + def destroy + @product.destroy + head 204 + end + + # ... +end +---- + +Como puedes ver las cuatro lineas implementadas hacen el trabajo. Podemos correr las pruebas para asegurar que todo esta bien y entonces haremos un commit de los cambios ya que hemos añadido un montón de código. Tambien asegurate que llamas a esta accion en el callback `before_action` al igual que en la acción `update`. + +[source,bash] +---- +$ rake test +........................ +---- + +Hagamos commit de los cambios: + +[source,bash] +---- +$ git commit -am "Adds the products create, update and destroy actions" +---- + + +== Llenado de la base de datos + +Vamos a llenar la base de datos con información falsa antes de continuar esribiendo mas código. Vamos a usar los _seeds_ para hacerlo. + +Con el archivo `db/seeds.rb`, Rails nos da una forma facil y rapida para asignar valores por defecto en una nueva instalación. Este es un simple archivo de Ruby que nos da completo acceso a clases y metodos de la aplicación. Asi que no necesitas meter todo manualmente con la consola de Rails sino que puedes simplemente usar el archivo `db/seeds.rb` con el comando `rake db:seed`. + +Asi que vamos a iniciar creando un usuario: + +.db/seeds.rb +[source,ruby] +---- +User.delete_all +user = User.create! email: 'toto@toto.fr', password: 'toto123' +puts "Created a new user: #{user.email}" +---- + +Y ahora puedes crear un usuario simplemente ejecutando el siguiente comando: + + +[source,bash] +---- +$ rake db:seed +Created a new user: toto@toto.fr +---- + +Funciona. No se tú, pero a mi me gusta tener datos ficticios para llenar correcamente mi base de datos de prueba. Solo que no siempre tengo la inspiración para dar sentido a my archivo _seed_ asi que uso la gema https://github.com/stympy/faker[`faker`]. Vamos a configurarla: + +[source,bash] +---- +$ bundle add faker +---- + +Ahora podemos usarla para crear cinco uuarios al mismo tiempo con diferentes emails. + +.db/seeds.rb +[source,ruby] +---- +User.delete_all + +5.times do + user = User.create! email: Faker::Internet.email, password: 'locadex1234' + puts "Created a new user: #{user.email}" +end +---- + +Y vamos a ver que pasa: + +[source,bash] +---- +$ rake db:seed +Created a new user: barbar@greenholt.io +Created a new user: westonpaucek@ortizbotsford.net +Created a new user: ricardo@schneider.com +Created a new user: scott@moenerdman.biz +Created a new user: chelsie@wiza.net +---- + +Ahí lo tienes. PEro podemos ir mas lejos creando productos asociados con estos usuarios: + + +.db/seeds.rb +[source,ruby] +---- +Product.delete_all +User.delete_all + +3.times do + user = User.create! email: Faker::Internet.email, password: 'locadex1234' + puts "Created a new user: #{user.email}" + + 2.times do + product = Product.create!( + title: Faker::Commerce.product_name, + price: rand(1.0..100.0), + published: true, + user_id: user.id + ) + puts "Created a brand new product: #{product.title}" + end +end +---- + +Ahi lo tienes. El resultado es asombroso. En una orden podemos crear tres usuarios y seis productos: + +[source,bash] +---- +$ rake db:seed +Created a new user: tova@beatty.org +Created a brand new product: Lightweight Steel Hat +Created a brand new product: Ergonomic Aluminum Lamp +Created a new user: tommyrunolfon@tremblay.biz +Created a brand new product: Durable Plastic Car +Created a brand new product: Ergonomic Leather Shirt +Created a new user: jordon@torp.io +Created a brand new product: Incredible Paper Hat +Created a brand new product: Sleek Concrete Pants +---- + +Hagamos un _commit_: + +[source,bash] +---- +$ git commit -am "Create a seed to populate database" +---- + +Y como llegamos al final de nuestro capítulo, est tiempo de aplicar todas las modificaciones a la rama master haciendo un _merge_: + +[source,bash] +---- +$ git checkout master +$ git merge chapter05 +---- + +== Conclusión + +Espero que hayas disfrutado este capítulo. Es el mas largo pero el código que hicimos juntos es una excelente base para el nucleo de nuestra applicación. + +En el siguiente capítulo, nos enfocaremos en perzonalizar la salido de los modelos usuarios y productos usando la gema https://github.com/Netflix/fast_jsonapi[fast_jsonapi]. Esto nos permitira filtrar facilmente los atributos para mostrar y manipular asociaciones como objetos embebidos por ejemplo. + From 812730b88336a3cb8f54b860c981c56198d4321a Mon Sep 17 00:00:00 2001 From: Oscar Tellez Date: Mon, 16 Mar 2020 15:14:54 -0400 Subject: [PATCH 07/17] Chapter 6 to spanish --- rails6/es/chapter06-improve-json.adoc | 985 ++++++++++++++++++++++++++ 1 file changed, 985 insertions(+) create mode 100644 rails6/es/chapter06-improve-json.adoc diff --git a/rails6/es/chapter06-improve-json.adoc b/rails6/es/chapter06-improve-json.adoc new file mode 100644 index 0000000..749d248 --- /dev/null +++ b/rails6/es/chapter06-improve-json.adoc @@ -0,0 +1,985 @@ +[#chapter06-improve-json] += Construyendo la repuesta JSON + +En el capítulo anterior agregamos productos a la aplicación y creamos las rutas necesarias. Tenemos tambien asociado un producto con un usario y restringidas algunas acciones del controlador `products_controller`. + +Ahora puedes estar satisfecho con todo este trabajo. Pero todavía tenemos un monton de trabajo por hacer. Actualmente tenemos unsa salida JSON que no es perfecta. La salida JSON luce así: + +[source,json] +---- +{ + "products": [ + { + "id": 1, + "title": "Tag Case", + "price": "98.7761933800815", + "published": false, + "user_id": 1, + "created_at": "2018-12-20T12:47:26.686Z", + "updated_at": "2018-12-20T12:47:26.686Z" + }, + ] +} +---- + +Como sea buscamso una salida que no contenga los campos `user_id`, `created_at` y `updated_at`. + +Una parte importante (y dificil) cuando estas creando tu API es decidir el formato de salida. Afortunadamente algunas organizaciones ya tienen encarado este tipo de problema y tienen establecidas algunas convenciones que descubrirás en este capítulo. + +Puedes clonar el proyecoto hasta este punto: + +[source,bash] +---- +$ git checkout tags/checkpoint_chapter06 +---- + +Iniciemos con una nueva rama para este capítulo: + +[source,bash] +---- +$ git checkout -b chapter06 +---- + +== Presentación de https://jsonapi.org/[JSON:API] + +Una parte importante y dificil de crear tu API es decidir el formato de salida. Afortunadamente algunas convenciones ya existen. Ciertamente las mas usada es https://jsonapi.org/[JSON:API]. + +La https://jsonapi.org/format/#document-structure[documentación de JSON:API] nos da algunas reglas a seguir respecto al formateado del documento JSON. + + +En consecuencia, nuestro documento *debería* contener estas llaves: + +* `data`: que contiene la información que develovemos +* `errors` que contienen un arreglo de errores ocurridos +* `meta` que contiene un https://jsonapi.org/format/#document-meta[meta object] + +El contenido de la llave `data` es demasiado estricto: + +* debe tener una llave de `type` correspondiente al tipo de modelo JSON (un article, un user, etc...) +* propiedades de los objetos deben ponerse en la llave `attributes` +* enlaces de objetos deben colocarse en una llave `relationships` + +En este capítulo vamos a personalizar la salida JSON usando la gema de Netflix: https://github.com/Netflix/fast_jsonapi[fast_jsonapi]. Afortunadamente ya implementa todas las especificaciones https://jsonapi.org/[JSON:API]. + +Asi que instalemos la gema `fast_jsonapi`: + +[source,bash] +---- +$ bundle add fast_jsonapi +---- + +Deberias estar listo para continuar este tutorial. + +== Serializar el usuario + +FastJSON API usa *serializers*. Los serializadores representan clases Ruby que serán responsables de convertur un modelo en un https://ruby-doc.org/core-2.6.3/Hash.html[`Hash`] o un JSON. + +Asi que nedesitamos añador un archivo `user_serializer.rb`. Podemos hacerlo manualmente pero la gema provee una inteface de linea de comandos para hacerlo: + +[source,bash] +---- +$ rails generate serializer User email + create app/serializers/user_serializer.rb +---- + +Esto habra creado un archivo llamado `user_serializer.rb` bajo la ruta `app/serializers`. El nuevo archivo deberia lucir como el siguiente archivo: + +[source,ruby] +.app/serializers/user_serializer.rb +---- +class UserSerializer + include FastJsonapi::ObjectSerializer + attributes :email +end +---- + +Este _serializer_ nos permitira convertur nusetro objeto `User` a JSON implementando todas las especificaciones JSON:API. Como especificamos `email` como `attributes` lo recibimos en un arreglo `data`. + +Vamos a intentar todo esto en la consola de rails con `rails console`: + +[source,ruby] +---- +2.6.3 :001 > UserSerializer.new( User.first ).serializable_hash +=> {:data=>{:id=>"25", :type=>:user, :attributes=>{:email=>"tova@beatty.org"}}} +---- + +Ahí tienes. Como puedes ver es realmente facil. Ahora podemos usar nuestro nuevo _serializer_ en nuestro _controller_: + + +.app/controllers/api/v1/users_controller.rb +[source,ruby] +---- +class Api::V1::UsersController < ApplicationController + # ... + def show + render json: UserSerializer.new(@user).serializable_hash + end + + def update + if @user.update(user_params) + render json: UserSerializer.new(@user).serializable_hash + else + # ... + end + end + + def create + # ... + if @user.save + render json: UserSerializer.new(@user).serializable_hash, status: :created + else + # ... + end + end + + # ... +end +---- + +No es demasiado facil? Como sea deberiamos tener una prueba que falla. Pruebalo por ti mismo: + +[source,bash] +---- +$ rake test + +Failure: +Expected: "one@one.org" + Actual: nil +---- + +Por alguna razón la respuesta no es lo que esperabamos. Esto es porque la gema modifica la respuesta que teníamos anteriormente definida. Así que para pasar esta prueba tenemos que modificarla: + +[source,ruby] +.test/controllers/api/v1/users_controller_test.rb +---- +# ... +class Api::V1::UsersControllerTest < ActionDispatch::IntegrationTest + # ... + test "should show user" do + # ... + assert_equal @user.email, json_response['data']['attributes']['email'] + end + # ... +end +---- + +Si lo chisite ahora la prueba pasa: + +[source,bash] +---- +$ rake test +........................ +---- + +Guardemos estos cambios y sigamos moviendonos: + +[source,bash] +---- +$ git add . && git commit -am "Adds user serializer for customizing the json output" +---- + + +== Serializado de productos + +Ahora que entendemos como trabaja la gema de serialización es tiempo de personalizar la salida del producto. El primer paso es el mismo que hicimos en el capitulo previo. Necesitamos un serializador de producto. Asi que hagamoslo: + +[source,bash] +---- +$ rails generate serializer Product title price published + create app/serializers/product_serializer.rb +---- + +Ahora vamos a añadir atributos para serializar el producto: + +[source,ruby] +.app/serializers/product_serializer.rb +---- +class ProductSerializer + include FastJsonapi::ObjectSerializer + attributes :title, :price, :published +end +---- + +Ahí está. No es tan complicado. Cambiemos nuestro controlador un poco. + +[source,ruby] +.app/controllers/api/v1/products_controller.rb +---- +class Api::V1::ProductsController < ApplicationController + # ... + def index + @products = Product.all + render json: ProductSerializer.new(@products).serializable_hash + end + + def show + render json: ProductSerializer.new(@product).serializable_hash + end + + def create + product = current_user.products.build(product_params) + if product.save + render json: ProductSerializer.new(product).serializable_hash, status: :created + else + # ... + end + end + + def update + if @product.update(product_params) + render json: ProductSerializer.new(@product).serializable_hash + else + # ... + end + end + # ... +end +---- + +I actualizamos nuestra prueba funcional: + +[source,ruby] +.test/controllers/api/v1/products_controller_test.rb +---- +# ... +class Api::V1::ProductsControllerTest < ActionDispatch::IntegrationTest + # ... + test 'should show product' do + # ... + assert_equal @product.title, json_response['data']['attributes']['title'] + end + # ... +end +---- + +Si quieres puedes revisar si la prueba pasa pero debería. Guardemos estos pequeños cambios: + +[source, bash] +---- +$ git add . +$ git commit -m "Adds product serializer for custom json output" +---- + +=== Serializar asociaciones + +Hemos trabajado con serializadores y has notado que es muy simple. En algunos casos la decicipon dificil es nombrar tus rutas o estructurar la salida JSON. Cuando se estra trabajando con asociaciones entre modelos en la API hay muchos enfoque que puees tomar. + +No debemos preocuparnos de este problem en nuestro caso: Las especificaciones JSON:API lo hicieron por nosotros! + +Para recapitular tenemos un tipo de asociacion `has_many` entre usuarios y productos. + +[source,ruby] +.app/models/user.rb +---- +class User < ApplicationRecord + has_many :products, dependent: :destroy + # ... +end +---- + +[source,ruby] +.app/models/product.rb +---- +class Product < ApplicationRecord + belongs_to :user + # ... +end +---- + +Es una buena idea integrar usuario en las salidas JSON de productos. Esto harala salida mas incomoda pero prevendra al cliente de la API ejecutar otras peticiones para recibir informacion del usuario relacionada a los productos. Este método realmente puede salvarte de un enorme cuello de botella. + +== Teoría de la inyeccion de relaciones + +Imagina un escenario donde pides a la API productos, pero en este caso tienes que mostrar alguna información del usuario. + +Una posible solución podria ser añadir el atributo `user_id` a el `product_serializer` asi podemos obtener el usuario correspondiente mas tarde. Esto puede sonar como una buena idea, pero si estar preocupado sobre el rendimiento, o si las transacciones de la base de datos no son suficientemente rápidas, deberias reconsiderar éste enfoque. Deberias entendes que de cada producto que recuperes, deberías de recuperar su usuario correspondiente. + +Enfrentando a este problema, tenemos varia alternativas. + +=== Integrar en un meta atributo + +La primera solicion (una buena en my opinión) es integrar identificadores de usuarios enlazados a los productos un un meta atributo. Asi obtenemos un JSON como abajo: + +[source,json] +---- +{ + "meta": { "user_ids": [1,2,3] }, + "data": [ + + ] +} +---- + +Asi que el cliente puede recuperar estos usuarios desde `user_ids`. + +=== Incorporando el objeto en el atributo + +Otra solución es incorporar el objeto `user` en el objeto `product`. Esto debería hacer a la primera petición lenta, pero de esta forma el cliente no ncesita hacer otra petición adicional. Un ejempo deel resultado esperado se presenta a continuación: + +[source,json] +---- +{ + "data": + [ + { + "id": 1, + "type": "product", + "attributes": { + "title": "First product", + "price": "25.02", + "published": false, + "user": { + "id": 2, + "attributes": { + "email": "stephany@lind.co.uk", + "created_at": "2014-07-29T03:52:07.432Z", + "updated_at": "2014-07-29T03:52:07.432Z", + "auth_token": "Xbnzbf3YkquUrF_1bNkZ" + } + } + } + } + ] +} +---- + +El problema con este enfoque es que tenemos duplicados del objeto `User' para cada producto que pertenece al mismo usuario: + +[source,json] +---- +{ + "data": + [ + { + "id": 1, + "type": "product", + "attributes": { + "title": "First product", + "price": "25.02", + "published": false, + "user": { + "id": 2, + "type": "user", + "attributes": { + "email": "stephany@lind.co.uk", + "created_at": "2014-07-29T03:52:07.432Z", + "updated_at": "2014-07-29T03:52:07.432Z", + "auth_token": "Xbnzbf3YkquUrF_1bNkZ" + } + } + } + }, + { + "id": 2, + "type": "product", + "attributes": { + "title": "Second product", + "price": "25.02", + "published": false, + "user": { + "id": 2, + "type": "user", + "attributes": { + "email": "stephany@lind.co.uk", + "created_at": "2014-07-29T03:52:07.432Z", + "updated_at": "2014-07-29T03:52:07.432Z", + "auth_token": "Xbnzbf3YkquUrF_1bNkZ" + } + } + } + } + ] +} +---- + + +=== Incorporar las relaciones incluidas en `include + +LA tercer solición (elegida por JSON:API) es una combinacion de las primeras dos. + +Incluiremos todoas las relaciones en una llave `include` que contendrá todoas las relaciones de los objetos previamente mencionados. Tambien, cada objeto inclurá una llave de ralación que define la relación y que deberia encontrar en cada llave `include`. + +Un JSON vale mas que mil palabras: + +[source,json] +---- +{ + "data": + [ + { + "id": 1, + "type": "product", + "attributes": { + "title": "First product", + "price": "25.02", + "published": false + }, + "relationships": { + "user": { + "id": 1, + "type": "user" + } + } + }, + { + "id": 2, + "type": "product", + "attributes": { + "title": "Second product", + "price": "25.02", + "published": false + }, + "relationships": { + "user": { + "id": 1, + "type": "user" + } + } + } + ], + "include": [ + { + "id": 2, + "type": "user", + "attributes": { + "email": "stephany@lind.co.uk", + "created_at": "2014-07-29T03:52:07.432Z", + "updated_at": "2014-07-29T03:52:07.432Z", + "auth_token": "Xbnzbf3YkquUrF_1bNkZ" + } + } + ] +} +---- + +¿Ves la diferencia? Esta solución reduce drasticamente el tamaño de el JSON y por lo tanto el ancho de banda utilizado. + +== Aplicación de la injección de relaciones + +Asi que incorporaremos el objeto user en el producto. Vamos a iniciar por añador algunas pruebas. + +Simplemente modificaremos la prueba `Products#show` para verificar que lo estamos recuperando: + + +[source,ruby] +.test/controllers/api/v1/products_controller_test.rb +---- +# ... +class Api::V1::ProductsControllerTest < ActionDispatch::IntegrationTest + # ... + test 'should show product' do + get api_v1_product_url(@product), as: :json + assert_response :success + + json_response = JSON.parse(response.body, symbolize_names: true) + assert_equal @product.title, json_response.dig(:data, :attributes, :title) + assert_equal @product.user.id.to_s, json_response.dig(:data, :relationships, :user, :data, :id) + assert_equal @product.user.email, json_response.dig(:included, 0, :attributes, :email) + end + # ... +end +---- + +Ahora revisaremos tres cosas que el JSON deberia retornar: + +. este contiene el título del producto +. este contiene el ID del usuario ligado al producto +. la información del usuario esta incluida en la llave `include` + +NOTE: Deberias haber notado que decidí suar el método https://ruby-doc.org/core-2.6.3/Hash.html#method-i-dig[`Hash#dig`]. Este es un metodo Ruby que permite recuperar elementos en un _Hash_ anidado evitando errores si un elemento no esta presente. + +PAra pasar esta prueba iniciaremos por incluir la relacion en el _serializer_: + +[source,ruby] +.app/serializers/product_serializer.rb +---- +class ProductSerializer + include FastJsonapi::ObjectSerializer + attributes :title, :price, :published + belongs_to :user +end +---- + +Esta adicion añadirá una llave `relationship` conteniendo el identificador del usuario: + +[source,json] +---- +{ + "data": { + "id": "1", + "type": "product", + "attributes": { + "title": "Durable Marble Lamp", + "price": "11.55", + "published": true + }, + "relationships": { + "user": { + "data": { + "id": "1", + "type": "user" + } + } + } + } +} +---- + +Esto nos permite corregir nuestras primeras dos afirmaciones. Ahora queremos incluir atrubutos de el usuario a quien pertenesca el producto. Para hacer esto siemplemente necesitamos pasar una opción `:include` al _serializer_ iinstanciado en el controlador _controller_. Entonces hagamoslo: + +[source,ruby] +.app/controllers/api/v1/products_controller.rb +---- +class Api::V1::ProductsController < ApplicationController + # ... + def show + options = { include: [:user] } + render json: ProductSerializer.new(@product, options).serializable_hash + end + # ... +end +---- + +Ahí tienes. Ahora asi es como deberia lucir el JSON: + +[source,json] +---- +{ + "data": { + ... + }, + "included": [ + { + "id": "1", + "type": "user", + "attributes": { + "email": "staceeschultz@hahn.info" + } + } + ] +} +---- + +Ahora las pruebas deberian pasar: + +[source,bash] +---- +$ rake test +........................ +---- + +Hagamos un _commit_ para celebrar: + +[source,bash] +---- +$ git commit -am "Add user relationship to product serializer" +---- + +<<< + +=== Recuperar productos del usuario + +¿Entiendes el principio? tenemos incluida informacion del usuario en el JSON de los productos. Podemos hacer lo mismo incluyendo información del producto relacionada a un usuario para la página `/api/v1/users/1`. + +Empecemos con la prueba: + +[source,ruby] +.test/controllers/api/v1/users_controller_test.rb +---- +# ... +class Api::V1::UsersControllerTest < ActionDispatch::IntegrationTest + # ... + test "should show user" do + get api_v1_user_url(@user), as: :json + assert_response :success + + json_response = JSON.parse(self.response.body, symbolize_names: true) + assert_equal @user.email, json_response.dig(:data, :attributes, :email) + assert_equal @user.products.first.id.to_s, json_response.dig(:data, :relationships, :products, :data, 0, :id) + assert_equal @user.products.first.title, json_response.dig(:included, 0, :attributes, :title) + end + # ... +end +---- + +_serializer_: + +[source,ruby] +.app/serializers/user_serializer.rb +---- +class UserSerializer + include FastJsonapi::ObjectSerializer + attributes :email + has_many :products +end +---- + +Y para finalizar el controlador: + +[source,ruby] +.app/controllers/api/v1/users_controller.rb +---- +class Api::V1::UsersController < ApplicationController + # ... + def show + options = { include: [:products] } + render json: UserSerializer.new(@user, options).serializable_hash + end + # ... +end +---- + +Ahí tienes. Obtenemos un JSON como el siguiente: + +[source,json] +---- +{ + "data": { + "id": "1", + "type": "user", + "attributes": { + "email": "staceeschultz@hahn.info" + }, + "relationships": { + "products": { + "data": [ + { "id": "1", "type": "product" }, + { "id": "2", "type": "product" } + ] + } + } + }, + "included": [ + { + "id": "1", + "type": "product", + "attributes": { + "title": "Durable Marble Lamp", + "price": "11.5537474980286", + "published": true + }, + "relationships": { + "user": { + "data": { + "id": "1", + "type": "user" + } + } + } + }, + { + ... + } + ] +} +---- + +Fue realmente facil. Hagamos un _commit_: + +[source,bash] +---- +$ git commit -am "Add products relationship to user#show" +---- + +== Buscando productos + +En esta ultima seccion continuaremos fortaleciendo la acción `Products#index` configurando un mecanismo de busqueda muy simple permitiendo a cualquier cliente filtrar los resultados. Esta sección es opcional asi que no tendrá impacto en los modulos de la aplicación. Pero si quiere practiar mas con las TDD (Test Driven Development) recomiendo que completes este último paso. + +Yo uso https://github.com/activerecord-hackery/ransack[Ransack] ó https://github.com/casecommons/pg_search[pg_search] para construir formas de busqueda extremamente rapido. Pero como el objetivo es aprender y buscar vamos a hacerlo muy sencillo. Creo que podemos contruir un motor de busqueda desde cero. Siplemento tenemos que considerar los tuterios por los cuales filtraremos los atributos. Quedate en tu asiento vamos a hacer este viaje juntos. + +Por lo tanto filtraremos los productos de acurdo a los siguientes criterios: + +* Por título +* Por precio +* Acomodar por fecha de creación + +Esto pude verse pequeño y fácil, pero creeme, esto te dará dolor de cabeza si no lo planeas. +It may seem short and easy, but believe me, it will give you a headache if you don't plan it. + +=== Por keyword + +Crearemos un _scope_ para encontrar los registros que coinciden con un patrón de caracteres en particular. Vamos allamarlo `filter_by_title`. + +Comenzaremos por añador algunos _fixtures_ con diferentes productos para probar: + +[source,yaml] +.test/fixtures/products.yml +---- +one: + title: TV Plosmo Philopps + price: 9999.99 + published: false + user: one + +two: + title: Azos Zeenbok + price: 499.99 + published: false + user: two + +another_tv: + title: Cheap TV + price: 99.99 + published: false + user: two +---- + +Y ahora podemos construir algunas pruebas: + +[source,ruby] +.test/models/product_test.rb +---- +# ... +class ProductTest < ActiveSupport::TestCase + # ... + test "should filter products by name" do + assert_equal 2, Product.filter_by_title('tv').count + end + + test 'should filter products by name and sort them' do + assert_equal [products(:another_tv), products(:one)], Product.filter_by_title('tv').sort + end +end +---- + +La siguiente prueba se asegura que el método `Product.filter_by_title` buscará correctamente los productos de acuerdo con su título. Usamos el término `tv` en minusculas para segurar que nuestra busqueda no sea sensitiva a mayusculas y minusculas. + +[source,ruby] +.app/models/product.rb +---- +class Product < ApplicationRecord + # ... + scope :filter_by_title, lambda { |keyword| + where('lower(title) LIKE ?', "%#{keyword.downcase}%") + } +end +---- + +NOTE: _scoping_ te permite especificar las consultas comunmente usadas que pueden ser referenciadas como llamada de metodo en los modelos. Con estos __scopes__ puedes enlazar metodos con Active Record methods como `where`, `joins` y `includes` porque un _scope_ siempre retorna un objeto https://api.rubyonrails.org/classes/ActiveRecord/Relation.html[`ActiveRecord::Relation`]. Te invito a que echos un vistaso en https://guides.rubyonrails.org/active_record_querying.html#scopes_record_querying.html#scopes[Rails documentation] + +Esta implementación es suficiente para que nuestras pruebas pasen: + +[source,bash] +---- +$ rake test +.......................... +---- + +=== Por precio + +Para filtrar por precio, las cosas pueden ser un poco mas delicadas. Separaremos la lógica del filtrado por precio en dos diferentes métodos: uno que bucará por productos con precio mayor al recivido y otro que busque aquellos que son menores que el precio. De esta forma, mantendremos algo de flexibilidad y podemos facilmente probar el _scope_. + +Vamos a iniciar por construir las pruebas de el _scope_ `above_or_equal_to_price`: + +[source,ruby] +.test/models/product_test.rb +---- +# ... +class ProductTest < ActiveSupport::TestCase + # ... + test 'should filter products by price and sort them' do + assert_equal [products(:two), products(:one)], Product.above_or_equal_to_price(200).sort + end +end +---- + +La implementación es muy, muy sencilla: + +[source,ruby] +.app/models/product.rb +---- +class Product < ApplicationRecord + # ... + scope :above_or_equal_to_price, lambda { |price| + where('price >= ?', price) + } +end +---- + +Esto es suficiente para convertir nuestra prueba en verde: + +[source,bash] +---- +$ rake test +........................... +---- + +Puedes imaginar el comportamiento del metodo opuesto. Aquí esta la prueba: + +[source,ruby] +.test/models/product_test.rb +---- +# ... +class ProductTest < ActiveSupport::TestCase + # ... + test 'should filter products by price lower and sort them' do + assert_equal [products(:another_tv)], Product.below_or_equal_to_price(200).sort + end +end +---- + +y la implementación. + +[source,ruby] +.app/models/product.rb +---- +class Product < ApplicationRecord + # ... + scope :below_or_equal_to_price, lambda { |price| + where('price <= ?', price) + } +end +---- + +Para nuestros motivos, vamos a hacer la prueba y revisar que todo esta hermosamente en verde: + +[source,bash] +---- +$ rake test +............................ +---- + +Como puedes ver, no tuvimos muchos problemas. Vamos a añadir otro _scope_ para acomodar los registros por la fecha de la última actualización. En el caso cuando el propuetrio de los productos decide actualizar alguna informacion seguramente bucara acomodar sus productos por la fecha de creación. + +=== Sort by creation date + +Este _scope_ es muy fácil. Vamos a añadir algunas pruebas primero: + +[source,ruby] +.test/models/product_test.rb +---- +# ... +class ProductTest < ActiveSupport::TestCase + # ... + test 'should sort product by most recent' do + # we will touch some products to update them + products(:two).touch + assert_equal [products(:another_tv), products(:one), products(:two)], Product.recent.to_a + end +end +---- + +Y la implementación: + +[source,ruby] +.app/models/product.rb +---- +class Product < ApplicationRecord + # ... + scope :recent, lambda { + order(:updated_at) + } +end +---- + +Todos nuestras pruebas deberían de pasar: + +[source,bash] +---- +$ rake test +............................. +---- + +Vamos a guardar nuestros cambios: + +[source,bash] +---- +$ git commit -am "Adds search scopes on the product model" +---- + + +==== Motor de busqueda + +Ahora que tenemos lo basico para el motor de busqueda que usaremos en nuestra aplicación, es tiempo para implementar un simple pero poderoso método de busqueda. Este getioanara toda la logica para recuperar los registros de los productos. + +El metodo consistirá en enlacar todos los `scope` que creamos anteriormente y retornar el resultado. Comencemos añadiendo algunas pruebas: + +[source,ruby] +.test/models/product_test.rb +---- +# ... +class ProductTest < ActiveSupport::TestCase + # ... + test 'search should not find "videogame" and "100" as min price' do + search_hash = { keyword: 'videogame', min_price: 100 } + assert Product.search(search_hash).empty? + end + + test 'search should find cheap TV' do + search_hash = { keyword: 'tv', min_price: 50, max_price: 150 } + assert_equal [products(:another_tv)], Product.search(search_hash) + end + + test 'should get all products when no parameters' do + assert_equal Product.all.to_a, Product.search({}) + end + + test 'search should filter by product ids' do + search_hash = { product_ids: [products(:one).id] } + assert_equal [products(:one)], Product.search(search_hash) + end +end +---- + +Añadimos un montón de código pero te aseguro que la implementacion es muy fácil. Tu puedes ir mas lejos y añadir pruebas adicionales pero, en mi caso, No lo encontré necesario. + +[source,ruby] +.app/models/product.rb +---- +class Product < ApplicationRecord + # ... + def self.search(params = {}) + products = params[:product_ids].present? ? Product.where(id: params[:product_ids]) : Product.all + + products = products.filter_by_title(params[:keyword]) if params[:keyword] + products = products.above_or_equal_to_price(params[:min_price].to_f) if params[:min_price] + products = products.below_or_equal_to_price(params[:max_price].to_f) if params[:max_price] + products = products.recent if params[:recent] + + products + end +end +---- + +Es importante notar que retornamos los productos como un objeto https://api.rubyonrails.org/classes/ActiveRecord/Relation.html[`ActiveRecord::Relation`] así que podemos concatenar otros métodos si es necesario o paginarlos como veremos en los últimos capítulos.Simplemente acutalizar la acción para recuperar los productos desde el método de busqueda: + +[source,ruby] +.app/controllers/api/v1/products_controller.rb +---- +class Api::V1::ProductsController < ApplicationController + # ... + def index + @products = Product.search(params) + render json: ProductSerializer.new(@products).serializable_hash + end + # ... +end +---- + +Podemos correr la suit completa de pruebas para asegurar que la aplicación esta en buen estado hasta aquí: + +[source,bash] +---- +$ rake test +................................. +33 runs, 49 assertions, 0 failures, 0 errors, 0 skips +---- + +Guardemos todos estos cambios: + +[source,bash] +---- +$ git commit -am "Adds search class method to filter products" +---- + +Y como estamos en el vinal de nuestro capítulo, es tiempo de aplicar todas nuestras modificaciones a la rama master haciendo un `merge`: + +[source,bash] +---- +$ git checkout master +$ git merge chapter06 +---- + +== Conclusión + +Hasta ahora fue facil gracias a la gema https://github.com/Netflix/fast_jsonapi_jsonapi[fast_jsonapi]. En el próximo capítulo vamos a iniciar con la construcción del modelo `Order` (orden) que implicará usuarios en los productos. From b4f731d9ebfcdd1cf8111db1dce7cf83c827b991 Mon Sep 17 00:00:00 2001 From: Oscar Tellez Date: Mon, 16 Mar 2020 16:37:20 -0400 Subject: [PATCH 08/17] Chapter 7 to spanish --- rails6/es/chapter07-placing-orders.adoc | 693 ++++++++++++++++++++++++ 1 file changed, 693 insertions(+) create mode 100644 rails6/es/chapter07-placing-orders.adoc diff --git a/rails6/es/chapter07-placing-orders.adoc b/rails6/es/chapter07-placing-orders.adoc new file mode 100644 index 0000000..87ee41d --- /dev/null +++ b/rails6/es/chapter07-placing-orders.adoc @@ -0,0 +1,693 @@ +[#chapter07-placing-orders] += Colocando órdenes + +En el capitulo previo manejamos asociaciones entre productos y usuarios y como serializarlos a fin de escalar rapido y fácil. Ahora es tiempo de empezar a color ordenes lo cual será una situacion algo mas compleja. Manejaremos asociaciones entre estos tres modelos. Debemos ser lo suficientemente inteligenes para manejar la salida JSON que estamos entregando. + +En este capítulo haremos algunas cosas que estan listadas a continuación: + +* Crear un modelo `Order` con sus correspondientes especificaciones +* Manipular la salida JSON con asociacion entre los modelos orden de usuario y producto +* Enviar un mail de confirmacion con el resumen de la orden + +Asi que ahora todo esta claro podemos ensuciarnos las manos. Puedes clonar el proyecto hasta este punto con: + +[source,bash] +---- +$ git checkout tags/checkpoint_chapter07 +---- + +Creemos una rama para empezar a trabajar: + +[source,bash] +---- +$ git checkout -b chapter07 +---- + +== Modelando la orden + +Si recuerdas asociaciones de modelos, el modelo `Order` esta asociado con usuarios y productos al mismo tiempo. Acutalmente esto es muy simple de lograr en Rails. La parte dificil es cuado vamos a serializar estos objetos. Hablare mas sobre esto en la siguiente sección. + +Vamos a empezar creando el modelo `order`con una forma especial: + +[source,bash] +---- +$ rails generate model order user:belongs_to total:decimal +---- + +El comando anterior generará el modelo order pero estoy tomando ventaja del método `references` para crear la llaver foranea correspondiente para que la orden pertenezca a el usuario. Esto también añade la directiva `belongs_to` dentro del modelo. Vamos a migrar la base de datos. + +[source,bash] +---- +$ rake db:migrate +---- + +Ahora es tiempo para escribir algunas pruebas dentro del archivo`order_test.rb`: + +[source,ruby] +.test/models/order_test.rb +---- +# ... +class OrderTest < ActiveSupport::TestCase + test 'Should have a positive total' do + order = orders(:one) + order.total = -1 + assert_not order.valid? + end +end +---- + +La implementacion es demasiado simple: + +[source,ruby] +.app/models/order.rb +---- +class Order < ApplicationRecord + belongs_to :user + validates :total, numericality: { greater_than_or_equal_to: 0 } + validates :total, presence: true +end +---- + +No olvides añadir la relación `orders` a nuestros usuarios espedivicando el borrado en cascada: + +[source,ruby] +.app/models/user.rb +---- +class User < ApplicationRecord + # ... + has_many :products, dependent: :destroy + has_many :orders, dependent: :destroy + # ... +end +---- + +Las pruebas deberían pasar: + +[source,bash] +---- +$ rake test +.................................. +---- + +Y hacemos _commit_ de todo esto: + +[source,bash] +---- +$ git add . && git commit -m "Generate orders" +---- + + +=== Ordenes y productos + +Necesitamos configurar la asociacion entre la `order` y el `product` y esto se hace con una asociación *has-many-to-many*. Como muchos productos puden ser puestos en muchas ordenes y las ordenes puede tener multiples productos. Así en este caso necesiamos un modelo intermedio el cual unirá estos tros dos objetos y mapeara las asociaciones apropiadas. + +Vamos a genera este modelo: + +[source,bash] +---- +$ rails generate model placement order:belongs_to product:belongs_to +---- + +Vamos a correr la migración en la base de datos: + +[source,bash] +---- +$ rake db:migrate +---- + +LA implementación es como: + +[source,ruby] +.app/models/product.rb +---- +class Product < ApplicationRecord + belongs_to :user + has_many :placements, dependent: :destroy + has_many :orders, through: :placements + # ... +end +---- + +[source,ruby] +.app/models/order.rb +---- +class Order < ApplicationRecord + has_many :placements, dependent: :destroy + has_many :products, through: :placements + # ... +end +---- + +Si has etado siguiendo el tutorial para la implementacion , esta ya esta lista debido a las `references` (referencias) que forman parte del comando generador del modelo. Podriamos añadir la opción `inverse_of` a el modelo `placement` para cada llamada `belongs_to`. Esto da un pequeño impulso cuando referenciamos al objeto padre. + +[source,ruby] +.app/models/placement.rb +---- +class Placement < ApplicationRecord + belongs_to :order + belongs_to :product, inverse_of: :placements +end +---- + +Vamos a correr las pruebas de los _modelos_ y asegurar que todo es verde: + +[source,bash] +---- +$ rake test +.................................. +---- + +Ahora que todo esta bieny en varde vamos a hacer commit de los cambios y continuar. + +[source,bash] +---- +$ git add . && git commit -m "Associates products and orders with a placements model" +---- + + +== Exponer el modelo usuario + +Es tiempo de poner en orden el controlador para epxopner las ordenes correctas. Si recuerdas el capítulo previo donde https://github.com/Netflix/fast_jsonapi_jsonapi[fast_jsonapi] fue usada, deberias reorder que fue realmente fácil. + +Vamos a definir prumero que acciones tomará: + +. Una acción de indexación para recuperar las ordenes de usuaro actuales +. Una accion show para recuperar un commando particular desde el usuario actual +. Una accion de creación para generar la orden + +Vamos a inciiar con la acción `index`. Primero tenemos el comando para crear el conrolador: + +[source,bash] +---- +$ rails generate controller api::v1::orders +---- + +Hasta este punto y antes de empezar a escribir algo de código tenemos que preguntarnos a nosotros mismos: + +> ¿Deberia dejar mis enpoints de ordenes anidado dentro de `UserController` o deberia aislarlas? + +La respuesta es realmente simple: esto depende de la carga o información que quieras exponer al desarrollador. + +En nuesro caso, no haremos esto porque recuperaremos los comandos del usuario desde la ruta `/orders`. Vamos a iniciar con algunas pruebas: + +[source,ruby] +.test/controllers/api/v1/orders_controller_test.rb +---- +# ... +class Api::V1::OrdersControllerTest < ActionDispatch::IntegrationTest + setup do + @order = orders(:one) + end + + test 'should forbid orders for unlogged' do + get api_v1_orders_url, as: :json + assert_response :forbidden + end + + test 'should show orders' do + get api_v1_orders_url, + headers: { Authorization: JsonWebToken.encode(user_id: @order.user_id) }, + as: :json + assert_response :success + + json_response = JSON.parse(response.body) + assert_equal @order.user.orders.count, json_response['data'].count + end +end +---- + +Si corremos la suit de pruebas ahora ambas pruebas deberian de fallar como ya esperabamos. Esto es porque estas no tienen establecidas las rutas o acciones correctas. Iniciemos añadiendo las rutas: + +[source,ruby] +.config/routes.rb +---- +Rails.application.routes.draw do + namespace :api, defaults: { format: :json } do + namespace :v1 do + resources :orders, only: [:index] + # ... + end + end +end +---- + +Ahora es tiempo para implementar la serialización de las ordenes: + + + +[source,bash] +---- +$ rails generate serializer Order +---- + +Y vamos a añadir relaciones: + +.app/serializers/order_serializer.rb +[source,ruby] +---- +class OrderSerializer + include FastJsonapi::ObjectSerializer + belongs_to :user + has_many :products +end +---- + +Ahora es tiempo de implementar el controlador: + +[source,ruby] +.app/controllers/api/v1/orders_controller.rb +---- +class Api::V1::OrdersController < ApplicationController + before_action :check_login, only: %i[index] + + def index + render json: OrderSerializer.new(current_user.orders).serializable_hash + end +end +---- + +Y ahora todos nuestras pruebas deberían de pasar: + +[source,bash] +---- +$ rake test +.................................... +36 runs, 53 assertions, 0 failures, 0 errors, 0 skips +---- + +Nos gustan que nuestros commits sean muy atomicos, asi que vamos a guardar estos cambios: + +[source,bash] +---- +$ git add . && git commit -m "Adds the index action for order" +---- + +=== Renderizar una sola orden + +Como ahora puedes imaginar esta ruta es muy facil. Unicamente hacemos algunas configuraciones (rutas, acción de controlador) y esta seccion estara terminada. Tabmien incluiremos productos relacionados a esta orden en la salida JSON. + +Vamos ainiciar añadiendo algunas pruebas: + +[source,ruby] +.test/controllers/api/v1/orders_controller_test.rb +---- +# ... +class Api::V1::OrdersControllerTest < ActionDispatch::IntegrationTest + # ... + test 'should show order' do + get api_v1_order_url(@order), + headers: { Authorization: JsonWebToken.encode(user_id: @order.user_id) }, + as: :json + assert_response :success + + json_response = JSON.parse(response.body) + include_product_attr = json_response['included'][0]['attributes'] + assert_equal @order.products.first.title, include_product_attr['title'] + end +end +---- + +Como puedes ver, la segunda parte de la prueba verifica que el producto esta incluido en el JSON. + +Vamos añadir la implementacion para correr nuestras pruebas. En el archivo `routes.rb` añadimos la acción `show` a las rutas de comando: + +[source,ruby] +.config/routes.rb +---- +# ... +Rails.application.routes.draw do + # ... + resources :orders, only: %i[index show] + # ... +end +---- + +Y la implementación deberia lucir como esto: + +[source,ruby] +.app/controllers/api/v1/orders_controller.rb +---- +class Api::V1::OrdersController < ApplicationController + before_action :check_login, only: %i[index show] + # ... + def show + order = current_user.orders.find(params[:id]) + + if order + options = { include: [:products] } + render json: OrderSerializer.new(order, options).serializable_hash + else + head 404 + end + end +end +---- + +Nuestras pruebas deberian estar todas verdes: + +[source,bash] +---- +$ rake test +..................................... +37 runs, 55 assertions, 0 failures, 0 errors, 0 skips +---- + +Vamos a hacer commit de los cambios y parar a crear la accion de crear orden: + +[source,bash] +---- +$ git commit -am "Adds the show action for order" +---- + +=== Colocando y ordenando + +Es tiempo ahora de dar la oportunidad de colocar algunas ordenes. ESto añadira complejidad a la aplicación, pero no te preocupes, vamos a hacer cada cosa en su tiempo. + +Antes de implementar esta caracteristica, tomare tiempo para pensar sobre la implicación de crear un comando en la applicación. No estoy hablando sobre configurar un servicio de transaccion como el de https://stripe.com/[Stripe] ó https://www.braintreepayments.com/[Braintree] pero algo como: + +* gestionamiento de productos out-of-stock (fuera de stock) +* reducir el inventario del producto +* añadir alguna validacion para el colocamiento de ordenes para asegurar que hay los sufcientes prodctos al momento de coloar la orden + +Parece que aún hay mucho por hacer pero creeme: estar mas cerca de lo que piensas y no es tan dificil como parece. Por ahora mandengamoslo simple y asumamos que aún tendremos suficientes productos para colocar cualquier numero de ordenes. Solo estamos preocupados sobre la respuesta del servidor por el momentos. + +Si tu recuerdas el modelo de orden, necesitamos tres cosas: + +* un total para la orden +* usuario que coloca la orden +* productos para la orden + +Basado en esta información podemos empezar añadiendo alguns pruebas: + +[source,ruby] +.test/controllers/api/v1/orders_controller_test.rb +---- +# ... +class Api::V1::OrdersControllerTest < ActionDispatch::IntegrationTest + setup do + # ... + @order_params = { order: { + product_ids: [products(:one).id, products(:two).id], + total: 50 + } } + end + + # ... + + test 'should forbid create order for unlogged' do + assert_no_difference('Order.count') do + post api_v1_orders_url, params: @order_params, as: :json + end + assert_response :forbidden + end + + test 'should create order with two products' do + assert_difference('Order.count', 1) do + post api_v1_orders_url, + params: @order_params, + headers: { Authorization: JsonWebToken.encode(user_id: @order.user_id) }, + as: :json + end + assert_response :created + end +end +---- + +Como puedes ver estamos crean una variable `order_params` con los datos de la orden. ¿Puedes ver el problema aquí? Si no, lo explicare mas tarde. Justamente añadimos el código necesario para hacer pasar laprueba. + +Primero necesitamos añadir la acción a los recuros en el archivo de rutas: + +[source,ruby] +.config/routes.rb +---- +# ... +Rails.application.routes.draw do + # ... + resources :orders, only: %i[index show create] + # ... +end +---- + +Entonces la implementacion es fácil: + +[source,ruby] +.app/controllers/api/v1/orders_controller.rb +---- +class Api::V1::OrdersController < ApplicationController + before_action :check_login, only: %i[index show create] + # ... + + def create + order = current_user.orders.build(order_params) + + if order.save + render json: order, status: 201 + else + render json: { errors: order.errors }, status: 422 + end + end + + private + + def order_params + params.require(:order).permit(:total, product_ids: []) + end +end +---- + +Y ahora nuestras pruebas deberian estar en verde: + +[source,bash] +---- +$ rake test +....................................... +39 runs, 59 assertions, 0 failures, 0 errors, 0 skips +---- + +Ok, entonces tenemos todo correcto y en verde. Ahora deberíamos movernos al sigueinte capitulo, ¿correcto? Dejame detenerte justo aquí. Tenemos algunos errores serios en la applicación, y estos no estan relacionados al código por si mismo pero si en la parte del negocio. + +No porque los las pruebas esten verdes, esto significa que la aplicación esta cibriendo la parte del negocio. Queria traer esto aqui porque en muchos casoses super facil solo recibir parametros y contruir objetos desde esos parametor pensando que siempre estamos recibiendo los datos correctos. En este caso particular no podemos confiar en eso, y la forma facil de ver esto, es que le estamos dando al cliente la oportunidad de poner el total, que locura! + +Tenemos que añadir algunas validaciones o un callbacl para calcular el total de la orden y colocarlo entre el modelo. De esta forma ya no recibiremos mas el atributo del total y asi tener el control total sobre este atributo. Vamos a hacer esto: + +Primer necesitamos algunas especificaciones a el modelo de la orden: + +[source,ruby] +.test/models/order_test.rb +---- +# ... +class OrderTest < ActiveSupport::TestCase + + setup do + @order = orders(:one) + @product1 = products(:one) + @product2 = products(:two) + end + + test 'Should set total' do + order = Order.new user_id: @order.user_id + order.products << products(:one) + order.products << products(:two) + order.save + + assert_equal (@product1.price + @product2.price), order.total + end +end +---- + +Ahora podemos añadir la implementacion: + +[source,ruby] +.app/models/order.rb +---- +class Order < ApplicationRecord + # ... + def set_total! + self.total = products.map(&:price).sum + end +end +---- + +Ahora podemos incluir el método `set_total!` a un callback `before_validation` para asegurar que tiene el total correcto antes de ser validado. + +[source,ruby] +.app/models/order.rb +---- +class Order < ApplicationRecord + before_validation :set_total! + # ... +end +---- + +Hasta este punto nos aseguramos que el total esta siempre presente y es mayor o igual a cero. Esto significa que podemos quitar esas validaciones y quitar las especificaciones. Esperaré. Nuestros test deberian pasar por ahora: + +[source,bash] +---- +$ rake test + +...........F + +Failure: +OrderTest#test_Should_have_a_positive_total [/home/arousseau/github/madeindjs/market_place_api/test/models/order_test.rb:14]: +Expected true to be nil or false + + +rails test test/models/order_test.rb:11 + +............................ + +Finished in 0.542600s, 73.7191 runs/s, 110.5786 assertions/s. +---- + + +Oops! Obtuvimos un _failure_ (falla) en nuestra anterior prueba _Should have a positive total_. Es lógico desde que el total de la orden es calculado dinamicamente. Asi que podemos simplemente quitar esta prueba que ha quedado obsoleta. + +Nustra prueba deberia pasar. Guardemos nuestros cambios: + +[source,bash] +---- +$ git commit -am "Adds the create method for the orders controller" +---- + + +== Enviar email de confirmacion de la orden + +La última seccion para este capítulo es para enviar el mail de confirmación al usuario que ordenó. Si quiere saltar esta parte e ir al siguiente capítulo hazlo. Esta seccion es mas como un calentamiento. + +Tal vez estas familiarizado con la manipulacion de emails con Rails asi que intentaremos hacer esto fácil y rápido. Primero creamos el `order_mailer` con un email llamado `send_confirmation`: + +[source,bash] +---- +$ rails generate mailer order_mailer send_confirmation +---- + +Ahora agregamos algunas pruebas para los correos de la orden que acabamos de crear: + +[source,ruby] +.test/mailers/order_mailer_test.rb +---- +# ... +class OrderMailerTest < ActionMailer::TestCase + + setup do + @order = orders(:one) + end + + test "should be set to be delivered to the user from the order passed in" do + mail = OrderMailer.send_confirmation(@order) + assert_equal "Order Confirmation", mail.subject + assert_equal [@order.user.email], mail.to + assert_equal ['no-reply@marketplace.com'], mail.from + assert_match "Order: ##{@order.id}", mail.body.encoded + assert_match "You ordered #{@order.products.count} products", mail.body.encoded + end + +end +---- + +Yo simplemente copie/pegue las pruebas desde la documentacion y las adapte a nuesras necesidades. Ahora nos aseguramos que estas pruebas pasan. + +Primero, añadimos el método `OrderMailer#send_confirmation`: + +[source,ruby] +.app/mailers/order_mailer.rb +---- +class OrderMailer < ApplicationMailer + default from: 'no-reply@marketplace.com' + def send_confirmation(order) + @order = order + @user = @order.user + mail to: @user.email, subject: 'Order Confirmation' + end +end +---- + +Despues de añadir este código añadimos las vistas correspondientes. Es una buena practica incluir un texto de la versión como extra a la version HTML. + + +[source,erb] +---- +<%# app/views/order_mailer/send_confirmation.text.erb %> +Order: #<%= @order.id %> +You ordered <%= @order.products.count %> products: +<% @order.products.each do |product| %> + <%= product.title %> - <%= number_to_currency product.price %> +<% end %> +---- + +[source,erb] +---- + +

Order: #<%= @order.id %>

+

You ordered <%= @order.products.count %> products:

+
    + <% @order.products.each do |product| %> +
  • <%= product.title %> - <%= number_to_currency product.price %>
  • + <% end %> +
+---- + +Ahora, nuestra prueba debería pasar: + +[source,bash] +---- +$ rake test +........................................ +40 runs, 66 assertions, 0 failures, 0 errors, 0 skips +---- + +Y ahora, solo llamamos al método `OrderMailer#send_confirmation` en la accion de crear en el controlador de la orden: + +[source,ruby] +.app/controllers/api/v1/orders_controller.rb +---- +class Api::V1::OrdersController < ApplicationController + # ... + def create + order = current_user.orders.build(order_params) + + if order.save + OrderMailer.send_confirmation(order).deliver + render json: order, status: 201 + else + render json: { errors: order.errors }, status: 422 + end + end + # ... +end +---- + +Para asegurar que no rompimos nada, vamos a correr todas las pruebas: + +[source,bash] +---- +$ rake test +........................................ +40 runs, 66 assertions, 0 failures, 0 errors, 0 skips +---- + +Hagamos commit a todo para ya que esta completa esta sección: + +[source,bash] +---- +$ git add . && git commit -m "Adds order confirmation mailer" +---- + +Y como hemos llegado al final de nuestro capítulo, es tiempo de aplicar todas nuestras modificaciones a la rama master haciendo un `merge': + +[source,bash] +---- +$ git checkout master +$ git merge chapter07 +---- + +== Conclusión + +Eso es! Lo hiciste! Puedes aplaudirte. Se que fue un largo tiemp pero creeme estas casi terminando. + +En siguientes capitulos continuaremos trabajando en la plantilla de la orden y añadir validaciones cuando se hace una orden. Algunos escenarios son: + +* Que pasa cuando los productos no estan disponibles? +* Reducir la cantidad de los productos en progreso cuadno se esta ordenando + +El siguiente capítulo sera corto, pero es muy importante para la salud de la aplicación. Asi que no te lo saltes. From 68a8a6fb241e928447fbd4d821f6314e0f1e36bb Mon Sep 17 00:00:00 2001 From: Oscar Tellez Date: Mon, 16 Mar 2020 17:39:30 -0400 Subject: [PATCH 09/17] Chapter 8 to spanish --- rails6/es/chapter08-improve-orders.adoc | 496 ++++++++++++++++++++++++ 1 file changed, 496 insertions(+) create mode 100644 rails6/es/chapter08-improve-orders.adoc diff --git a/rails6/es/chapter08-improve-orders.adoc b/rails6/es/chapter08-improve-orders.adoc new file mode 100644 index 0000000..ea5e255 --- /dev/null +++ b/rails6/es/chapter08-improve-orders.adoc @@ -0,0 +1,496 @@ +[#chapter08-improve_orders] += Mejorando las ordenes + +En el capítulo anterior extendimos nuestra API para ordenar y enviar email de confirmación al usuario (solo para mejorar la experiencia del usuario). Este capitulo cuida algunas validaciones en el modelo de la orden, solo para asegurarse que se puede ordenar, algo como: + +- Reducir la cantidad del producto actual cuando se genera una orden +- ¿Que pasa cuando no hay productos disponibles? + +Probablemente necesitaremos actualiza un poco la salida JSON para las ordenes pero no estropeemos las cosas. + +Asi que ahora que tenemos todo claro podemos ensuciarnos las manos. Puedes clonar el proyecto hasta este punto con: + +[source,ruby] +---- +$ git checkout tags/checkpoint_chapter08 +---- + +Vamos a crear una rama para empezar a trabajar: + +[source,ruby] +---- +$ git checkout -b chapter08 +---- + +== Decrementando la cantidad del producto + +En esta primera parada vamos a trabajar en la actualizacion de la cantidad de productopara asegurar que cada pedido entregue la orden real. +Actualmente el modelo `product` no tiene un atributo `quantity`. ASo que vamos a hacer eso: + +[source,bash] +---- +$ rails generate migration add_quantity_to_products quantity:integer +---- + +Espera, no corras las migraciones ahora. Le haremos unas pequeñas modificaciones. Como una buena practica me gusta añadir los valores por defecto a la base de datos solo para asegurarme que no me equivoco con valores `null`. ¡Este es un caso perfecto! + +Tu archivo de migración deberia lucir como esto: + +[source,ruby] +.db/migrate/20190621105101_add_quantity_to_products.rb +---- +class AddQuantityToProducts < ActiveRecord::Migration[6.0] + def change + add_column :products, :quantity, :integer, default: 0 + end +end +---- + +Ahora podemos migrar la base de datos: + +[source,bash] +---- +$ rake db:migrate +---- + +Y no olvidemos actualizar los _fixtures_ añadiendo el campo *quantity* (Yo elegi el valor `5` de manera aleatoria). + +[source,yml] +.test/fixtures/products.yml +---- +one: + # ... + quantity: 5 + +two: + # ... + quantity: 5 + +another_tv: + # ... + quantity: 5 +---- + + +Es tiempo ahora de reducir la cantidad de productos meintras una `Orden` esta siendo procesada. La primera cosa probablemente que vien a la mente es hacerlo en el modelo `Order`. Esto es un misterio común. + +Cuando trabajas con asociaciones _Many-to-Many_ (muchos a muchos), nos olvidamos completamente del modelo de union que en este caso es `Placement`. `Placement` es el mejor lugar para gestionar esto porque tiene accesos la orden y al producto. De esta forma, podemos facilmente reducir el stock de el producto. + +Antes de empezar a implementar código, necesitamos cambiar la forma que manipulamos la creación de ordenes porque ahora tenemos que aceptar la cantidad para cada producto. Si recuerdas estamos esperando por una tabla de indenfiricadores de producto. Intentaré mantener las cosas simples y enviar una tabla Hash con las llaves `product_id` y `quantity`. + +Un ejemplo rápido podria ser algo como esto: + +[source,ruby] +---- +product_ids_and_quantities = [ + { product_id: 1, quantity: 4 }, + { product_id: 3, quantity: 5 } +] +---- + +Esto se ponde dificl pero quedate conmigo. Vamos primero a construor algunas pruebas: + +[source,ruby] +.test/models/order_test.rb +---- +# ... +class OrderTest < ActiveSupport::TestCase + # ... + + test 'builds 2 placements for the order' do + @order.build_placements_with_product_ids_and_quantities [ + { product_id: @product1.id, quantity: 2 }, + { product_id: @product2.id, quantity: 3 }, + ] + + assert_difference('Placement.count', 2) do + @order.save + end + end +end +---- + + +Entonces en la implementación: + +[source,ruby] +.app/models/order.rb +---- +class Order < ApplicationRecord + # ... + + # @param product_ids_and_quantities [Array] something like this `[{product_id: 1, quantity: 2}]` + # @yield [Placement] placements build + def build_placements_with_product_ids_and_quantities(product_ids_and_quantities) + product_ids_and_quantities.each do |product_id_and_quantity| + placement = placements.build(product_id: product_id_and_quantity[:product_id]) + yield placement if block_given? + end + end +end +---- ++ +Y si coremos nuestras pruebas, deberian estar bien y en verde: + +[source,bash] +---- +$ rake test +........................................ +40 runs, 60 assertions, 0 failures, 0 errors, 0 skips +---- + +Lo que es `build_placements_with_product_ids_and_quantities` hará la colocacion de objetos y luego ejecutará el método `save` para la ordenar todo sera insertada en la base de datos. Un último paso antes de guardar esto es actualizar la prueba `orders_controller_test` junto con esta implementación. + +Primero actualizamos el archivo `orders_controller_test`: + +[source,ruby] +.test/controllers/api/v1/orders_controller_test.rb +---- +# ... +class Api::V1::OrdersControllerTest < ActionDispatch::IntegrationTest + setup do + @order = products(:one) + @order_params = { + order: { + product_ids_and_quantities: [ + { product_id: products(:one).id, quantity: 2 }, + { product_id: products(:two).id, quantity: 3 }, + ] + } + } + end + + # ... + + test 'should create order with two products and placements' do + assert_difference('Order.count', 1) do + assert_difference('Placement.count', 2) do + post api_v1_orders_url, params: @order_params, as: :json + headers: { Authorization: JsonWebToken.encode(user_id: @order.user_id) }, + end + end + assert_response :created + end +end +---- + +Entonces necesitamos actualizar `orders_controller`: + +[source,ruby] +.app/controllers/api/v1/orders_controller.rb +---- +class Api::V1::OrdersController < ApplicationController + # ... + + def create + order = Order.create! user: current_user + order.build_placements_with_product_ids_and_quantities(order_params[:product_ids_and_quantities]) + + if order.save + OrderMailer.send_confirmation(order).deliver + render json: order, status: :created + else + render json: { errors: order.errors }, status: :unprocessable_entity + end + end + + private + + def order_params + params.require(:order).permit(product_ids_and_quantities: [:product_id, :quantity]) + end +end +---- + + +Nota que tambien modifique el método `OrdersController#order_params`. + +Por último pero no menos importante, necesitamos actualizar el archivo que fabrica productos para asignar un valor alto de cantidad para tener algunos productos en stock. + +Hagamos commit de estos cambios y continuemos: + +[source,bash] +---- +$ git add . +$ git commit -m "Allows the order to be placed along with product quantity" +---- + +¿Notaste que no estamos guardando la cantidad por cada producto en ningun lado? Esta no es la forma de darle seguimiento. Esto puede ser reparado facilemente. Solo añadamos un atributo `quantity` a el modelo `Placement`. Asi de este modo para cada producto guardaremos su cantidad correspondiente. Vamos a iniciar creando la migración: + +[source,bash] +---- +$ rails generate migration add_quantity_to_placements quantity:integer +---- + +Como con el atrubuto para la cantidad del producto deberiamos añadir un valor por defecto igual a 0. Recuerda que esto es opcional pero me gusta este enfoque. El archivo de migración deberia lucir así: + +[source,ruby] +.db/migrate/20190621114614_add_quantity_to_placements.rb +---- +class AddQuantityToPlacements < ActiveRecord::Migration[6.0] + def change + add_column :placements, :quantity, :integer, default: 0 + end +end +---- + +Entonces corre las migraciones: + +[source,bash] +---- +$ rake db:migrate +---- + +Ahora agregamos el atributo `quantity` en los _fixtures_: + +[source,yml] +.test/fixtures/placements.yml +---- +one: + # ... + quantity: 5 + +two: + # ... + quantity: 5 +---- + +Ahora solo necesitamos actualizar la prueba `build_placements_with_product_ids_and_quantities` para añadir `quantity` para hacer los pedidos: + +[source,ruby] +.app/models/order.rb +---- +class Order < ApplicationRecord + # ... + + # @param product_ids_and_quantities [Array] something like this `[{product_id: 1, quantity: 2}]` + # @yield [Placement] placements build + def build_placements_with_product_ids_and_quantities(product_ids_and_quantities) + product_ids_and_quantities.each do |product_id_and_quantity| + placement = placements.build( + product_id: product_id_and_quantity[:product_id], + quantity: product_id_and_quantity[:quantity], + ) + yield placement if block_given? + end + end +end +---- + +Ahora nuestras pruebas deberian pasar: + +[source,bash] +---- +$ rake test +........................................ +40 runs, 61 assertions, 0 failures, 0 errors, 0 skips +---- + +Vamos a guardar los cambios: + +[source,bash] +---- +$ git add . && git commit -m "Adds quantity to placements" +---- + +=== Eltendiendo el modelo Placement + +Est tiempo de actualizar la cantidad del producto cada que la orden es guardada, o mas exacto cada que el placement (colocación) es creado. A fin de lograr esto vamos a añadir un metodo y entonces conectarlo con el callback `after_create`. + +[source,ruby] +.test/models/placement_test.rb +---- +# ... +class PlacementTest < ActiveSupport::TestCase + setup do + @placement = placements(:one) + end + + test 'decreases the product quantity by the placement quantity' do + product = @placement.product + + assert_difference('product.quantity', -@placement.quantity) do + @placement.decrement_product_quantity! + end + end +end +---- + +La implementación es bastante facil como se muestra a continación: + +[source,ruby] +.app/models/placement.rb +---- +class Placement < ApplicationRecord + # ... + after_create :decrement_product_quantity! + + def decrement_product_quantity! + product.decrement!(:quantity, quantity) + end +end +---- + + +Hagamos _commit_ a nuestros cambios: + +[source,bash] +---- +$ git commit -am "Decreases the product quantity by the placement quantity" +---- + +== Validar la canridad de productos + +Desde el comienzo del capítulo, tenemos añadido el atributo `quantity` a el modelo del producto. Es ahora tiempo para validar si la cantidad de producto es sificiente para conciliar la orden. A fin de que hagamos las cosas mas intersantes, vamos a hacer usando un validador personalizado. + +NOTE: puedes consultar https://guides.rubyonrails.org/active_record_validations.html#performing-custom-validations[la documentación]. + +Primero necesitamos añadir un directorio `validators` en el directorio `app` (Rails lo incluira por lo que no necesitamos preocuparnos de cargarlo). + +[source,bash] +---- +$ mkdir app/validators +$ touch app/validators/enough_products_validator.rb +---- + +Antes que borremos cualquier linea de código, necesitamos asegurarnos de añadir especificaciones a el modelo `Order` par revisar si la orden puede ser realizada. + +[source,ruby] +.test/models/order_test.rb +---- +# ... +class OrderTest < ActiveSupport::TestCase + # ... + + test "an order should command not too much product than available" do + @order.placements << Placement.new(product_id: @product1.id, quantity: (1 + @product1.quantity)) + + assert_not @order.valid? + end +end +---- + +Como puedes ver en la especificación, primero nos aseguramos que `placement_2` este tratando de pedir mas productos de los que stan disponibles, asi que en este caso suponemos que la `order` (orden) no es válida. + +La prueba por ahora deberia fallar, vamos a convertirla en verde añadiendo el código del validador: + +[source,ruby] +.app/validators/enough_products_validator.rb +---- +class EnoughProductsValidator < ActiveModel::Validator + def validate(record) + record.placements.each do |placement| + product = placement.product + if placement.quantity > product.quantity + record.errors[product.title.to_s] << "Is out of stock, just #{product.quantity} left" + end + end + end +end +---- + +Manipulo para añandir el mensaje a cada uno de los producto que estan fuera de stock, pero puede manejarlo diferente si quieres. Ahora solamente necesito añadir el validador al modelo `Order` de esta forma: + +[source,ruby] +.app/models/order.rb +---- +class Order < ApplicationRecord + include ActiveModel::Validations + # ... + validates_with EnoughProductsValidator + # ... +end +---- + +Gurademos los cambios: + +[source,bash] +---- +$ git add . && git commit -m "Adds validator for order with not enough products on stock" +---- + +== Actualizando el total + +Notaste que el `total` esta siendo calculado incorrectamente, porque actualmente este está añadiendo el precio para los productos en la orden independientemente de la cantidad solicitada. Dejame añadir el código para aclarar el problema: + +Actualmente en el modelo `order` tenemos este método para calcular el monto a pagar: + +[source,ruby] +.app/models/order.rb +---- +class Order < ApplicationRecord + # ... + def set_total! + self.total = products.map(&:price).sum + end + # ... +end +---- + +Ahora en lugar de calcular el `total` solo añadiendo el precio del producto necesitamos multiplicarlo por la cantidad. Asi que vamos a actualizar las especificaciones primero: + +[source,ruby] +.test/models/order_test.rb +---- +# ... +class OrderTest < ActiveSupport::TestCase + # ... + + test "Should set total" do + @order.placements = [ + Placement.new(product_id: @product1.id, quantity: 2), + Placement.new(product_id: @product2.id, quantity: 2) + ] + @order.set_total! + expected_total = (@product1.price * 2) + (@product2.price * 2) + + assert_equal expected_total, @order.total + end +end +---- + +Y la implementacion es muy sencilla: + +[source,ruby] +.app/models/order.rb +---- +class Order < ApplicationRecord + # ... + def set_total! + self.total = self.placements + .map{ |placement| placement.product.price * placement.quantity } + .sum + end + # ... +end +---- + +Y las especificaciones deberian ser verdes: + +[source,bash] +---- +$ rake test +.......................................... +42 runs, 63 assertions, 0 failures, 0 errors, 0 skips +---- + +Vamos a guardar los cambios: + +[source,bash] +---- +$ git commit -am "Updates the total calculation for order" +---- + +Y asi es como llegamos al final de nuesro capítulo, es tiempo de aplicar todas nuestras modificaciones a la rama master haciendo un _merge_: + +[source,bash] +---- +$ git checkout master +$ git merge chapter08 +---- + +== Conclusión + +Oh, ahi tienes! Dejame felicirater! Es un lagro camingo desde el pimer capítulo. Pero estas un paso mas cerca, De echo, el próximo capítulo sera el último. Así que trata de aprovecharlo al máximo. + +El último capítulo se enfocará en la forma de optimizar la API usando paginado, caché y tareas en segundo plano. Así que abrochate el cinturón, va a ser un viaje agitado. \ No newline at end of file From e69ac08328016435a28166d1e734e13c9b4ff2a4 Mon Sep 17 00:00:00 2001 From: Oscar Tellez Date: Tue, 17 Mar 2020 11:00:07 -0400 Subject: [PATCH 10/17] Chapter 9 to spanish --- rails6/es/chapter09-optimization.adoc | 739 ++++++++++++++++++++++++++ 1 file changed, 739 insertions(+) create mode 100644 rails6/es/chapter09-optimization.adoc diff --git a/rails6/es/chapter09-optimization.adoc b/rails6/es/chapter09-optimization.adoc new file mode 100644 index 0000000..f76753e --- /dev/null +++ b/rails6/es/chapter09-optimization.adoc @@ -0,0 +1,739 @@ +[#chapter09-optimization] += Optimizaciones + +Bienvenido a el ultimo capítulo de este libro. Ha sido un largo camino, pero estas solo a un paso de el final. En el capítulo anterior, completamos el modelado del modelo de la orden. Podriamos decir que el proyecto esta finalizado pero quiero cubrir algunos detalles importante sobre la optimización. Los temas que discutiremos seran: + +* paginacion +* caché +* optimización de las consultas SQL +* la activacion de CORS + +Trataré de ir tan lejos como pueda intentando cubrir algunos escenarios comunes. Espero que estos escenarios sean utiles para algunos de tus proyectos. + +Si tu empiezas leyendo hasta este punto, probanlemente quieras el código, puedes clonarlo con esto: + +[source,bash] +---- +$ git checkout tags/checkpoint_chapter09 +---- + +Ahora vamos a crear una rama para empezar a trabajar: + +[source,bash] +---- +$ git checkout -b chapter09 +---- + + +== Paginación + +Una estrategia muy común para optimizar un arreglo de registros desde la base de datos, es cargar solo algunos paginandolos y si tu estas familiarizado con esta técnica sabes que en Rails es realimente facil lograrlos sobretodo si estas usando https://github.com/mislav/will_paginate[will_paginate] ó https://github.com/amatsuda/kaminari[kaminari]. + +Entonces solo la parte dificil aqui es como soponemos manipular la salida JSON dando la suficiente información al cliente sobre como esta paginado el arreglo. Si recuerdas el primer capítulo compartí algunos recursos y practicas que iba a seguir aquí. Una de ellas fue http://jsonapi.org/ que es una pagina de mis favoritas. + +Si leemos la seccion de formato encontraremos una sub seccion llamada http://jsonapi.org/format/#document-structure-top-level[Top Level] y en algunas palabras se mencionan algunas cosas sobre paginación: + +> "meta": meta-información sobre un recurso, como la paginación. + +Esto no es muy descriptivo pero al menos tenemos una pista de que buscar despues sobre la implementación de la paginación, pero no te preocupues que es exactamente a donde estamos llendo ahora. + +Comencemos con la lista de `products`. + +=== Productos + +Estamos iniciano bien y facil paginando la lista de producto ya que no tenemos ningún tipo de restricción de acceso que nos lleve a pruebas más fáciles. + +Primero necesiatamos añadir la gema https://github.com/amatsuda/kaminari[kaminari] a nuestro `Gemfile`: + +[source,bash] +---- +$ bundle add kaminari +---- + +Ahora podemos ir a la acción `index` en el controlador `products_controller` y añadir los mpetodos de paginación como se señala en la documentación: + +[source,ruby] +.app/controllers/api/v1/products_controller.rb +---- +class Api::V1::ProductsController < ApplicationController + # ... + def index + @products = Product.page(params[:page]) + .per(params[:per_page]) + .search(params) + + render json: ProductSerializer.new(@products).serializable_hash + end + # ... +end +---- + +Hasta ahora la unica cosa que cambio es la consuta a la base de datos que justamente limita el resuldato a 25 por pagina que es el valor por defecto. Pero no tenemos añadida información extra a la salida JSON. + +Necesitamos proveer la información de paginación en el tag `meta` de la siguiente forma: + +[source,json] +---- +{ + "data": [ + ... + ], + "links": { + "first": "/api/v1/products?page=1", + "last": "/api/v1/products?page=30", + "prev": "/api/v1/products", + "next": "/api/v1/products?page=2" + } +} +---- + +Ahora tenemos la estructura final para el tag `meta` que necesitamos en la salida de la repuesta JSON. Vamos primer a añadir algnas especificaciones-: + +[source,ruby] +.test/controllers/api/v1/products_controller_test.rb +---- +# ... +class Api::V1::ProductsControllerTest < ActionDispatch::IntegrationTest + # ... + test 'should show products' do + get api_v1_products_url, as: :json + assert_response :success + + json_response = JSON.parse(response.body, symbolize_names: true) + assert_not_nil json_response.dig(:links, :first) + assert_not_nil json_response.dig(:links, :last) + assert_not_nil json_response.dig(:links, :prev) + assert_not_nil json_response.dig(:links, :next) + end + # ... +end +---- + +La prueba que acabamos de añador debería fallar: + +[source,bash] +---- +$ rake test +......................F + +Failure: +Api::V1::ProductsControllerTest#test_should_show_products [test/controllers/api/v1/products_controller_test.rb:13]: +Expected nil to not be nil. +---- + +Vamos a añador informaciónde paginación. Construiremos una parte de esto en _concerns_ para fragmentar mejor nuestro código: + +[source,ruby] +.app/controllers/concerns/paginable.rb +---- +# app/controllers/concerns/paginable.rb +module Paginable + protected + + def current_page + (params[:page] || 1).to_i + end + + def per_page + (params[:per_page] || 20).to_i + end +end +---- + +Y ahora podemos usarlo en el controlador. + +[source,ruby] +.app/controllers/api/v1/products_controller.rb +---- +class Api::V1::ProductsController < ApplicationController + include Paginable + # ... + + def index + @products = Product.page(current_page) + .per(per_page) + .search(params) + + options = { + links: { + first: api_v1_products_path(page: 1), + last: api_v1_products_path(page: @products.total_pages), + prev: api_v1_products_path(page: @products.prev_page), + next: api_v1_products_path(page: @products.next_page), + } + } + + render json: ProductSerializer.new(@products, options).serializable_hash + end +end +---- + +Ahora, si revisamos las especificaciones, estos deberían pasar todos: + +[source,bash] +---- +$ rake test +.......................................... +42 runs, 65 assertions, 0 failures, 0 errors, 0 skips +---- + +Ahora tenemos tenemos echa una super optimicación para la ruta de lista de productos, depende del cliente para recuperar el parametro de la `page` (página) para los registros. + +Vamos a hacer estos cambios y continuar con la lista de comandos. + +[source,bash] +---- +$ git add . +$ git commit -m "Adds pagination for the products index action to optimize response" +---- + + +=== Lista de ordenes + +Ahora es tiempo de hacer exactamente lo mismo para el enpoint de la lista de `orders` que deberia ser realmente fácil de implementar. Pero primero vamos a añadir algunas especificaciones al archivo `orders_controller_test.rb`: + +[source,ruby] +.test/controllers/api/v1/orders_controller_test.rb +---- +# ... +class Api::V1::OrdersControllerTest < ActionDispatch::IntegrationTest + # ... + test 'should show orders' do + get api_v1_orders_url, headers: { Authorization: JsonWebToken.encode(user_id: @order.user_id) }, as: :json + assert_response :success + + json_response = JSON.parse(response.body, symbolize_names: true) + assert_equal @order.user.orders.count, json_response[:data].count + assert_not_nil json_response.dig(:links, :first) + assert_not_nil json_response.dig(:links, :last) + assert_not_nil json_response.dig(:links, :prev) + assert_not_nil json_response.dig(:links, :next) + end + # ... +end +---- + +Como ya deberias saber, nuestras pruebas no estarán pasando: + +[source,bash] +---- +$ rake test +......................................F + +Failure: +Api::V1::OrdersControllerTest#test_should_show_orders [test/controllers/api/v1/orders_controller_test.rb:28]: +Expected nil to not be nil. +---- + +Cambienmos el rojo en verde: + + +[source,ruby] +.app/controllers/api/v1/orders_controller.rb +---- +class Api::V1::OrdersController < ApplicationController + include Paginable + # ... + + def index + @orders = current_user.orders + .page(current_page) + .per(per_page) + + options = { + links: { + first: api_v1_orders_path(page: 1), + last: api_v1_orders_path(page: @orders.total_pages), + prev: api_v1_orders_path(page: @orders.prev_page), + next: api_v1_orders_path(page: @orders.next_page), + } + } + + render json: OrderSerializer.new(@orders, options).serializable_hash + end + # ... +end +---- + +Ahora todas las pruebas deberian pasar bien y en verde: + +[source,bash] +---- +$ rake test +.......................................... +42 runs, 67 assertions, 0 failures, 0 errors, 0 skips +---- + + +Hafamos un commit, por que se viene una refactorización: + +[source,bash] +---- +$ git commit -am "Adds pagination for orders index action" +---- + + +=== Refactorizando la paginación + +Si tu has segido este tutorial o si tienes experiencia previa como desarrollador Rails, probeblemente te guste mantener las cosas SECAS. Es posible que hayas notado que el código que acabamos de escribir está ducplicado. Pienso que es una buen hábito hacer limpieza del código un poco cuando la funcionalidad esta implementada. + +Primero limpiaremos estas pruebas que duplicamos en los archivos `orders_controller_test.rb` y `products_controller_test.rb`: + +[source,ruby] +---- +assert_not_nil json_response.dig(:links, :first) +assert_not_nil json_response.dig(:links, :last) +assert_not_nil json_response.dig(:links, :next) +assert_not_nil json_response.dig(:links, :prev) +---- + +Para factorizarlo, vamos a mover estas afirmaciones a el archivo `test_helper.rb` en un metodo que usaremos: + +[source,ruby] +.test/test_helper.rb +---- +# ... +class ActiveSupport::TestCase + # ... + def assert_json_response_is_paginated json_response + assert_not_nil json_response.dig(:links, :first) + assert_not_nil json_response.dig(:links, :last) + assert_not_nil json_response.dig(:links, :next) + assert_not_nil json_response.dig(:links, :prev) + end +end +---- + +Este metodo puede ahora ser usado para remplazar las cuatro afirmaciones en los archivos `orders_controller_test.rb` y `products_controller_test.rb`: + +[source,ruby] +.test/controllers/api/v1/orders_controller_test.rb +---- +# ... +class Api::V1::OrdersControllerTest < ActionDispatch::IntegrationTest + # ... + test 'should show orders' do + # ... + assert_json_response_is_paginated json_response + end + # ... +end +---- + +[source,ruby] +.test/controllers/api/v1/products_controller_test.rb +---- +# ... +class Api::V1::ProductsControllerTest < ActionDispatch::IntegrationTest + # ... + test 'should show products' do + # ... + assert_json_response_is_paginated json_response + end + # ... +end +---- + +Y ambas especificaciones deberían pasar. + +[source,bash] +---- +$ rake test +.......................................... +42 runs, 71 assertions, 0 failures, 0 errors, 0 skips +---- + + +Ahora tenemos terminado esta simple refactorización para las pruebas, podemos movernos a la implementacion de la paginación para loc controladores y limpiar cosas. Si tu recuerdas la acción de indexación para ambos controladores producto y orden, ambos tienen el mismo formato de paginación. Asiq ue vamos a mover esta logica dentro de un método llamado `get_links_serializer_options` en el archivo `paginable.rb`, así podemos acceder a el desde cualqueir controlador que necesite paginación. + + +[source,ruby] +.app/controllers/concerns/paginable.rb +---- +module Paginable + protected + + def get_links_serializer_options links_paths, collection + { + links: { + first: send(links_paths, page: 1), + last: send(links_paths, page: collection.total_pages), + prev: send(links_paths, page: collection.prev_page), + next: send(links_paths, page: collection.next_page), + } + } + end + # ... +end +---- + +Y ahora podemos sustuir el has de paginacion en ambos controladores para el metodo. Justo así: + +[source,ruby] +.app/controllers/api/v1/orders_controller.rb +---- +class Api::V1::OrdersController < ApplicationController + include Paginable + # ... + + def index + @orders = current_user.orders + .page(current_page) + .per(per_page) + + options = get_links_serializer_options('api_v1_orders_path', @orders) + + render json: OrderSerializer.new(@orders, options).serializable_hash + end + # ... +end +---- + +[source,ruby] +.app/controllers/api/v1/products_controller.rb +---- +class Api::V1::ProductsController < ApplicationController + include Paginable + # ... + + def index + @products = Product.page(current_page) + .per(per_page) + .search(params) + + options = get_links_serializer_options('api_v1_products_path', @products) + + render json: ProductSerializer.new(@products, options).serializable_hash + end + # ... +end +---- + +Si corres las especificaciones para cada archivo deberian estar todas bien y verdes: + +[source,bash] +---- +$ rake test +.......................................... +42 runs, 71 assertions, 0 failures, 0 errors, 0 skips +---- + +Este deberia ser un buen momento para hacer un _commit_ a los cambios y movernos a la siguiente seccion sobre el caché: + +[source,bash] +---- +$ git commit -am "Factorize pagination" +---- + +== Almacenamiento en cache del API + +Actualmente esta es una implementación para almacenar en caché la gema `fast_jsonapi` que es realmente facil de manipular. A pesar de que en la ultima version de la gema, esta implementación puede cambiar, esta hace el trabajo. + +Si hacemos una petición a la lista de productos, notaremos que el tiempode respuesta toma cerca de 174 milisegundos usando cURL: + +[source,bash] +---- +$ curl -w 'Total: %{time_total}\n' -o /dev/null -s http://localhost:3000/api/v1/products +Total: 0,137088 +---- + +NOTE: La opción `-w` nos permite recuperar el tiepo de petición, `-o` redirecciona la respuesa a un archivo y `-s` esconde la pantalla de cURL + +Añadiendo solo una linea a la clase `ProductSerializer`, veremos un significante incremento en el tiempo de respuesta! + +[source,ruby] +.app/serializers/order_serializer.rb +---- +class OrderSerializer + # ... + cache_options enabled: true, cache_length: 12.hours +end +---- + +[source,ruby] +.app/serializers/product_serializer.rb +---- +class ProductSerializer + # ... + cache_options enabled: true, cache_length: 12.hours +end +---- + +[source,ruby] +.app/serializers/user_serializer.rb +---- +class UserSerializer + # ... + cache_options enabled: true, cache_length: 12.hours +end +---- + +Y esto es todo! Vamos a revisar la mejora: + +[source,bash] +---- +$ curl -w 'Total: %{time_total}\n' -o /dev/null -s http://localhost:3000/api/v1/products +Total: 0,054786 +$ curl -w 'Total: %{time_total}\n' -o /dev/null -s http://localhost:3000/api/v1/products +Total: 0,032341 +---- + +Asi que fuimos de 174 ms a 21 ms. La mejora por lo tanto es enorme! Vamos a guardar nuestros cambios una última vez: + +[source,ruby] +---- +$ git commit -am "Adds caching for the serializers" +---- + +== Consultas N+1 + +Consultas N+1* son una herida donde podemos tener un enrome impacto en el rendimiento de una aplicación. Este fenomeno a menudo ocurre cuando usamos **ORM** porque este genera **automaticamente** consultas SQL por nosotros. Esta herramienta tan practica es de doble filo porque puede genera un **largo numero** de consultas SQL. + +Algo que debemos saber sobre las consultas SQL es que es mejor limitar su numero. En otras palabras, una repuesta larga es a menudo mas eficiente que cientos de pequeñas. + +Aquí esta un ejemplo cuando queremos recuperar todos los usuarios que ya tiene un producto creado. Abre la consola de Rails con `rails console` y ejecuta el siguiente código Ruby: + +[source,ruby] +---- +Product.all.map { |product| product.user } +---- + +La consola interactiva de rails nos muestra consultas SQL que son generadas. Mira por ti mismo: + +Vemos aqui que un largo numero de peticiones son generadas: + +- `Product.all` = 1 petición para recuperar los productos +- `product.user` = 1 petición `SELECT "users".* FROM "users" WHERE "users". "id" =? LIMIT 1 [[[["id", 1]]]` por producto recuperado + +Por lo tanto el nombre "petición N+1" es ya que una solicitud se reailza a travéz de un enlace sencudario. + +Pordemos arreglar esto simplemente usando `includes`. `Includes` **pre-cargará** los objetos secundarios en una simple petición. Es muy facil de usar. Si repetimos el ejemplo anterior. Este es el resultado: + +[source,ruby] +---- +Product.includes(:user).all.map { |product| product.user } +---- + +La consola interactiva de Rails nos muestra las consultas SQL que son generadas. Mira por tí mismo: + +[source,sql] +---- +Product Load (0.3ms) SELECT "products".* FROM "products" +User Load (0.8ms) SELECT "users".* FROM "users" WHERE "users"."id" IN (?, ?, ?) [["id", 28], ["id", 29], ["id", 30]] +---- + +Rails crea una regunda petición que recuperará **todos** los usuarios a la vez. + +=== Prevencion de peticiones N + 1 + +Imageina que queremos añadir propietarios de los productos a la ruta `/products`. Ya hemos visto que con la librería `fast_jsonapi` es muy facil de hacer esto: + +[source,ruby] +.app/controllers/api/v1/products_controller.rb +---- +class Api::V1::ProductsController < ApplicationController + # ... + def index + # ... + options = get_links_serializer_options('api_v1_products_path', @products) + options[:include] = [:user] + + render json: ProductSerializer.new(@products, options).serializable_hash + end + # ... +end +---- + +Ahora vamos a hacer ua petición con cURL. Te recuerdo que nosotros debimos obetener un token de autenticación antes de acceder a la pagina. + +[source,bash] +---- +$ curl -X POST --data "user[email]=ockymarvin@jacobi.co" --data "user[password]=locadex1234" http://localhost:3000/api/v1/tokens +---- + +NOTE: "ockymarvin@jacobi.co" corresponde a un suaurio creado en mi aplicación con el _seed_. En tu caso, probablemente fue diferente del mio desde que usamos la librería Faker. + +Con la ayuda de el token obtenido, ahora podemos hacer una petición para acceder a los productos + +[source,bash] +---- +$ curl --header "Authorization=ey..." http://localhost:3000/api/v1/products +---- + +DLo mas probable es que veas varias respuestas en la consola Rails corriendo el servidor web. + +[source,sql] +---- +Started GET "/api/v1/products" for 127.0.0.1 at 2019-06-26 13:36:19 +0200 +Processing by Api::V1::ProductsController#index as JSON + (0.1ms) SELECT COUNT(*) FROM "products" + ↳ app/controllers/concerns/paginable.rb:9:in `get_links_serializer_options' + Product Load (0.2ms) SELECT "products".* FROM "products" LIMIT ? OFFSET ? [["LIMIT", 20], ["OFFSET", 0]] + ↳ app/controllers/api/v1/products_controller.rb:16:in `index' + User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 36], ["LIMIT", 1]] + ↳ app/controllers/api/v1/products_controller.rb:16:in `index' + (0.5ms) SELECT "products"."id" FROM "products" WHERE "products"."user_id" = ? [["user_id", 36]] + ↳ app/controllers/api/v1/products_controller.rb:16:in `index' + CACHE User Load (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 36], ["LIMIT", 1]] + ↳ app/controllers/api/v1/products_controller.rb:16:in `index' + CACHE User Load (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 36], ["LIMIT", 1]] + ↳ app/controllers/api/v1/products_controller.rb:16:in `index' + CACHE User Load (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 36], ["LIMIT", 1]] +---- + +Es por lo tanto desafortunadamente **muy fácil** para crear consultas N+1. Afortunadamentes, esta es una gema que nos permite **alertar** cuando este tipo de situación ocurre: https://github.com/flyerhzm/bullet[Bullet]. Bullet nos notificará (por correo, http://growl.info/[growl notification], https://slack.com[Slack], consola, etc...) cuando encuentra una peticion N+1. + +Para instalarla, vamos añadir la _gema_ al _GemFile_ + +[source,bash] +---- +$ bundle add bullet --group development +---- + + +Y eso es suficiente para actializar la configuracion de nuestra aplicacion para el entorno de desarrollo. En nuestro caso solo activaremos el modo `rails_logger` el cual sera mostrado: + +[source,ruby] +.config/environments/development.rb +---- +Rails.application.configure do + # ... + config.after_initialize do + Bullet.enable = true + Bullet.rails_logger = true + end +end +---- + +Reinicia el servidor web y reinicia la ultima peticion con cURL: + +[source,bash] +---- +$ curl --header "Authorization=ey..." http://localhost:3000/api/v1/products +---- + +Y mira en la consola de Rails. Bullet nos dice que tiene justmente una peticion N+1 detectada. + +---- +GET /api/v1/products +USE eager loading detected + Product => [:user] + Add to your finder: :includes => [:user] +---- + +Incluos nos dice como corregirla: + +> Add to your search engine:: includes => [: user] + +Asi que corregimos nuestro error en el controlador: + + +[source,ruby] +.app/controllers/api/v1/products_controller.rb +---- +class Api::V1::ProductsController < ApplicationController + # ... + def index + @products = Product.includes(:user) + .page(current_page) + .per(per_page) + .search(params) + + options = get_links_serializer_options('api_v1_products_path', @products) + options[:include] = [:user] + + render json: ProductSerializer.new(@products, options).serializable_hash + end + # ... +end +---- + +Ahí tienes! Es tiempo de hacer nuestro _commit_. + +[source,bash] +---- +$ git commit -am "Add bullet to avoid N+1 query" +---- + +== Activación de CORS + +En esta última sección, te hablaré sobre un ultimo problema que probablemente encontraste si tu has trabajado con tu propia API. + +Cuando haces una petición a un sitio externo (por ejemplo una peticion via AJAX), encontraras un error de este tipo: + + +> Failed to load https://example.com/ No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin "https://anfo.pl" is therefore not allowed access. If an opaque response serves your needs, set the request's mode to "no-cors" to fetch the resource with CORS disabled. + +"¿Pero que significa _Access-Control-Allow-Origin_?". El comportamiento que obervas es el efecto de la implementación CORS del navegador. Antes de la estandarización de CORS, no había forma de llamar a una terminal de API bajo otro dominio por razones de seguridad. Esto ha sido (y todavia es hasta cierto punto) bloqueado por la politica de el mismo origen. + +CORS es un mecanismo que tiene como objetivo permitir peticione echas en su nombre y al mismo tiempo bloque algunas peticion echa de modo desonesto por scripts y se activa cuando haces una peticion HTTP a: + +- un diferente campo +- un diferente sub-dominio +- un diferente puerto +- un diferente protocolo + +Vamos a habilitar manualmente esta caracteristica para que cualquier cliente puede hacer peticiones a nuestra API. + +Rails nos permite hacerlo esto facilmente. Mira el archivo `cors.rb` localizado en el directorio `initializers`. + + +[source,ruby] +.config/initializers/cors.rb +---- +# ... + +# Rails.application.config.middleware.insert_before 0, Rack::Cors do +# allow do +# origins 'example.com' +# +# resource '*', +# headers: :any, +# methods: [:get, :post, :put, :patch, :delete, :options, :head] +# end +# end +---- + +Ves. Es suficiente con descomentar el código y modificar un poco para limitar el acceso a algunos acciones o algunos vervos HTTP. En nuestro caso, esta configuración es muy conveniente para nosotros en este momento. + +[source,ruby] +.config/initializers/cors.rb +---- +# ... + +Rails.application.config.middleware.insert_before 0, Rack::Cors do + allow do + origins 'example.com' + resource '*', + headers: :any, + methods: [:get, :post, :put, :patch, :delete, :options, :head] + end +end +---- + +Debemos instalar la gema `rack-cors` que esta comentada en el `Gemfile`: + +[source,bash] +---- +$ bundle add rack-cors +---- + +Ahi tienes! Es tiempo de hacer nuestro último commit y fusionar nuestros cambios en la rama master. + + +[source,bash] +---- +$ git commit -am "Activate CORS" +$ git checkout master +$ git merge chapter09 +---- + +== Conclusión + +Si llegaste hasta este punto, eso significa que terminaste el libro. Buen trabajo! Te has convertido en un gran desarrollador API en Rails, tenlo por seguro. + +Asi que juntos hemos construido una API solida y completa. Esta tiene todas las cualidades para destronar a https://www.amazon.com/[Amazon], esta seguro. Te agradezco por ir atravez de esta gran aventura conmigo, Espero que disfrutaras el viaje tanto como yo lo hice. + +Me gutaría recordarte que el código fuente para este libro esta disponible en el formato https://asciidoctor.org[Asciidoctor] on https://github.com/asciidoctor/asciidoctor[GitHub]. Asi que no dudes en https://github.com/madeindjs/api_on_rails[forkear] el proyecto si quieres mejorarlo o corregir algún error que no ví. + +Si te gusta este libro, no vaciles en hacermelo saber por correo mailto:contact@rousseau-alexandre.fr[contact@rousseau-alexandre.fr]. Estoy abierto cualquier critica, buena o mala, junto a una buena cerveza :). From 5ce9fb177c742ff7c5fe202d6b59b108d16e299c Mon Sep 17 00:00:00 2001 From: Oscar Tellez Date: Tue, 17 Mar 2020 19:34:27 -0400 Subject: [PATCH 11/17] Spell check --- rails6/es/api_on_rails.adoc | 2 +- rails6/es/chapter00-before.adoc | 18 +-- rails6/es/chapter01-introduction.adoc | 80 +++++------ rails6/es/chapter02-api.adoc | 64 ++++----- rails6/es/chapter03-presenting-users.adoc | 164 +++++++++++----------- rails6/es/chapter04-athentification.adoc | 84 +++++------ rails6/es/chapter05-user-products.adoc | 118 ++++++++-------- rails6/es/chapter06-improve-json.adoc | 133 +++++++++--------- rails6/es/chapter07-placing-orders.adoc | 118 ++++++++-------- rails6/es/chapter08-improve-orders.adoc | 72 +++++----- rails6/es/chapter09-optimization.adoc | 130 ++++++++--------- 11 files changed, 488 insertions(+), 495 deletions(-) diff --git a/rails6/es/api_on_rails.adoc b/rails6/es/api_on_rails.adoc index a3b1320..cca7d1d 100644 --- a/rails6/es/api_on_rails.adoc +++ b/rails6/es/api_on_rails.adoc @@ -12,7 +12,7 @@ v6.0.5, 2020-01-09 :keywords: Rails, API, Ruby, Software :lang: es :author: Alexandre Rousseau -:description: Learn best practice to build an API using Ruby on Rails 5 +:description: Aprende las mejores prácticas para construir una API usando Ruby on Rails 6 :front-cover-image: image:cover.svg[] :revdate: 2020-01-09 diff --git a/rails6/es/chapter00-before.adoc b/rails6/es/chapter00-before.adoc index 58e9cb0..ae725f6 100644 --- a/rails6/es/chapter00-before.adoc +++ b/rails6/es/chapter00-before.adoc @@ -3,30 +3,30 @@ == Foreword -"API on Rails 6" esta basado en http://apionrails.icalialabs.com/book/["APIs on Rails: Building REST APIs with Rails"]. Fue públicado inicialmente en 2014 por https://twitter.com/kurenn[Abraham Kuri] bajo la licencia http://opensource.org/licenses/MIT[MIT] y http://people.freebsd.org/~phk/[Beerware]. +"API on Rails 6" está basado en http://apionrails.icalialabs.com/book/["APIs on Rails: Building REST APIs with Rails"]. Fue publicado inicialmente en 2014 por https://twitter.com/kurenn[Abraham Kuri] bajo la licencia http://opensource.org/licenses/MIT[MIT] y http://people.freebsd.org/~phk/[Beerware]. -La primera version no es mantenida y fue planeada para Ruby on Rails 4 la cual no https://guides.rubyonrails.org/maintenance_policy.html#security-issues[recive mas actualizaciaciones de seguridad]. He buscado actualizar este excelente libro, adaptandolo a nuevas versiones de Ruby on Rails. Este libro esta por lo tanto disponible para Ruby on Rails en sus versiones 5.2 y 6.0 (el cual te encuentras leyendo). +La primera versión no es mantenida y fue planeada para Ruby on Rails 4 la cual no https://guides.rubyonrails.org/maintenance_policy.html#security-issues[recibe más actualizaciones de seguridad]. He buscado actualizar este excelente libro, adaptándolo a nuevas versiones de Ruby on Rails. Este libro está por lo tanto disponible para Ruby on Rails en sus versiones 5.2 y 6.0 (el cual te encuentras leyendo). -NOTE: Este libro tambien esta disponible en el lenguaje Molière (Esto significa francés). +NOTE: Este libro también está disponible en el lenguaje Molière (Esto significa francés). == Acerca del autor -Mi nombre es http://rousseau-alexandre.fr[Alexandre Rousseau] y soy un desarrollador en Rails con mas de 4 años de experiencia (al momento de escribirlo). Actualmente soy socio en una compañia (https://isignif.fr[iSignif]) donde construyo y mantengo un producto SAAS usando Rails. Tambien contibuyo a lacomunidad Ruby produciendo y manteniendo algunas gemas que puedes consular en https://rubygems.org/profiles/madeindjs[my Rubygems.org profile]. La mayoria de mis proyectos estan en GitHub asi que no dudes en http://github.com/madeindjs/[seguirme]. +Mi nombre es http://rousseau-alexandre.fr[Alexandre Rousseau] y soy un desarrollador en Rails con más de 4 años de experiencia (al momento de escribirlo). Actualmente soy socio en una compañía (https://isignif.fr[iSignif]) donde construyo y mantengo un producto SAAS usando Rails. También contribuyo a la comunidad Ruby produciendo y manteniendo algunas gemas que puedes consular en https://rubygems.org/profiles/madeindjs[my Rubygems.org profile]. La mayoría de mis proyectos están en GitHub así que no dudes en http://github.com/madeindjs/[seguirme]. -Todo el codigo fuente de este libro en formato https://asciidoctor.org/[Asciidoctor] disponible en https://github.com/madeindjs/api_on_rails[GitHub]. Por lo tanto sientete libre de hacer https://github.com/madeindjs/api_on_rails/fork[fork] al proyecto si tu quieres mejorarlo o corregir errores que no noté. +Todo el código fuente de este libro está en formato https://asciidoctor.org/[Asciidoctor] disponible en https://github.com/madeindjs/api_on_rails[GitHub]. Por lo tanto, siéntete libre de hacer un https://github.com/madeindjs/api_on_rails/fork[fork] al proyecto si quieres mejorarlo o corregir errores que no noté. == Derechos de autor y licencia -Este libro está bajo la http://opensource.org/licenses/MIT[licencia MIT]. Todo el codigo fuente del libro esta en el formato https://fr.wikipedia.org/wiki/Markdown[Markdown] disponible en https://github.com/madeindjs/api_on_rails[GitHub] +Este libro está bajo la http://opensource.org/licenses/MIT[licencia MIT]. Todo el código fuente del libro está en el formato https://fr.wikipedia.org/wiki/Markdown[Markdown] disponible en https://github.com/madeindjs/api_on_rails[GitHub] -.MIT license +.Licencia MIT **** Copyright 2019 Alexandre Rousseau Por la presente se concede permiso, libre de cargos, a cualquier persona que obtenga una copia de este software y de los archivos de documentación asociados (el "Software"), a utilizar el Software sin restricción, incluyendo sin limitación los derechos a usar, copiar, modificar, fusionar, publicar, distribuir, sublicenciar, y/o vender copias del Software, y a permitir a las personas a las que se les proporcione el Software a hacer lo mismo, sujeto a las siguientes condiciones: El aviso de copyright anterior y este aviso de permiso se incluirán en todas las copias o partes sustanciales del Software. -EL SOFTWARE SE PROPORCIONA "COMO ESTÁ", SIN GARANTÍA DE NINGÚN TIPO, EXPRESA O IMPLÍCITA, INCLUYENDO PERO NO LIMITADO A GARANTÍAS DE COMERCIALIZACIÓN, IDONEIDAD PARA UN PROPÓSITO PARTICULAR E INCUMPLIMIENTO. EN NINGÚN CASO LOS AUTORES O PROPIETARIOS DE LOS DERECHOS DE AUTOR SERÁN RESPONSABLES DE NINGUNA RECLAMACIÓN, DAÑOS U OTRAS RESPONSABILIDADES, YA SEA EN UNA ACCIÓN DE CONTRATO, AGRAVIO O CUALQUIER OTRO MOTIVO, DERIVADAS DE, FUERA DE O EN CONEXIÓN CON EL SOFTWARE O SU USO U OTRO TIPO DE ACCIONES EN EL SOFTWARE. +EL SOFTWARE SE PROPORCIONA "COMO ESTÁ", SIN GARANTÍA DE NINGÚN TIPO, EXPRESA O IMPLÍCITA, INCLUYENDO, PERO NO LIMITADO A GARANTÍAS DE COMERCIALIZACIÓN, IDONEIDAD PARA UN PROPÓSITO PARTICULAR E INCUMPLIMIENTO. EN NINGÚN CASO LOS AUTORES O PROPIETARIOS DE LOS DERECHOS DE AUTOR SERÁN RESPONSABLES DE NINGUNA RECLAMACIÓN, DAÑOS U OTRAS RESPONSABILIDADES, YA SEA EN UNA ACCIÓN DE CONTRATO, AGRAVIO O CUALQUIER OTRO MOTIVO, DERIVADAS DE, FUERA DE O EN CONEXIÓN CON EL SOFTWARE O SU USO U OTRO TIPO DE ACCIONES EN EL SOFTWARE. **** "API on Rails 6" por https://github.com/madeindjs/api_on_rails[Alexandre Rousseau] es compartido de acuerdo a http://creativecommons.org/licenses/by-sa/4.0/[Creative Commons Attribution - Attribution-ShareAlike 4.0 International]. Construido sobre este libro http://apionrails.icalialabs.com/book/. @@ -35,7 +35,7 @@ La portada de este libro usa una hermosa foto tomada por https://unsplash.com/@s == Agradecimientos -Un gran "gracias" a todos los contribuidores de GitHub quienes mantienen este libro vivo. En orden alfabetico: +Un gran "gracias" a todos los contribuidores de GitHub quienes mantienen este libro vivo. En orden alfabético: * https://github.com/airdry[airdry] * https://github.com/Landris18[Landris18] diff --git a/rails6/es/chapter01-introduction.adoc b/rails6/es/chapter01-introduction.adoc index 901c52f..3e43236 100644 --- a/rails6/es/chapter01-introduction.adoc +++ b/rails6/es/chapter01-introduction.adoc @@ -1,34 +1,34 @@ [#chapter01-introduction] -= Introduction += Introducción -Bienvenido a API on Rails 6, un tutorial con esteroides para enseñarte el mejor camino para construir tú siguiente API con Rails. El proposito de este libro es proveer una metodología comprensiva para desarrollar una API RESTful siguiendo las mejores prácticas. +Bienvenido a API on Rails 6, un tutorial con esteroides para enseñarte el mejor camino para construir tú siguiente API con Rails. El propósito de este libro es proveer una metodología comprensiva para desarrollar una API RESTful siguiendo las mejores prácticas. -Al finalizar este libro, tu podrás crear tu propia API e integrarla con cualquier cliente como un navegador web o aplicación movil. El código generado esta codeado con Ruby on Rails 6.0 que es la versión actual. +Al finalizar este libro, tu podrás crear tu propia API e integrarla con cualquier cliente como un navegador web o aplicación móvil. El código generado esta codeado con Ruby on Rails 6.0 que es la versión actual. -El proposito de este libro no es solamente enseñarte como construir un API con Rails sino mas bien para enseñarte como construir una API *evolutiva* y *mantenible* con Rails. Esto es, mejorar tu conocimiento actual con Rails. En esta sección, tú aprenderás a: +El propósito de este libro no es solamente enseñarte como construir un API con Rails sino mucho mejor enseñarte como construir una API *evolutiva* y *mantenible* con Rails. Esto es, mejorar tu conocimiento actual con Rails. En esta sección, aprenderás a: - Usar Git para control de versiones - Construir respuestas JSON -- Probar tus end-points con tests unitarios y funcionales +- Probar tus end-points con pruebas unitarias y funcionales - Configurar autenticación con JSON Web Tokens (JWT) - Usar la especificación JSON:API - Optimizar y hacer cache de la API -Recomiendo energicamente que sigas todos los pasos en este libro. Intenta no saltarte capitulos porque doy algunos tips y trucos para improvisar tus habilidades a travez del libro. Puedes considerarte a ti mismo el personaje principal de un videojuego que gana un nivel en cada capitulo. +Recomiendo enérgicamente que sigas todos los pasos en este libro. Intenta no saltarte capítulos porque doy algunos tips y trucos para improvisar tus habilidades a través del libro. Puedes considerarte a ti mismo el personaje principal de un videojuego que gana un nivel en cada capítulo. -En este primer capítulo explicaré como configurar tu entorno de desarrollo (en caso que aún no lo sepas). Luego vamos a crear una aplicacion llamada `market_place_api`. Me aseguraré que te enseño las mejores practicas que he aprendido durante mi experiencia. Esto significa que vamos a iniciar usando *Git* justo despues de inicializar el proyecto. +En este primer capítulo explicaré como configurar tu entorno de desarrollo (en caso que aún no lo sepas). Luego vamos a crear una aplicación llamada `market_place_api`. Me aseguraré que te enseño las mejores practicas que he aprendido durante mi experiencia. Esto significa que vamos a iniciar usando *Git* justo después de inicializar el proyecto. -Vamos a crear la aplicación siguiendo un metodo simple de trabajo que usé a diario en los siguientes capitulos. Vamos a desarrollar una aplicación completa usando Test Driven Development(TDD). Tambien explicaré el interés de usar una API para tu siguiente proyecto y eligiendo un adecuado formato de respuesta como JSON o XML. Mas allá, vamos a tener nuestras manos sobre el código y completar lo basico de la aplicacion construyendo todos los caminos necesarios. Tambien vamos a implementar acceso seguro a la API implementando autenticacion por interambio de cabeceras HTTP. Finalmente, en el último capítulo, vamos a añadir tecnicas de optimización para mejorar la estructura y tiempos de respuesta del servidor. +Vamos a crear la aplicación siguiendo un método simple de trabajo que usé a diario en los siguientes capítulos. Vamos a desarrollar una aplicación completa usando Test Driven Development(TDD). También explicaré el interés de usar una API para tu siguiente proyecto y eligiendo un adecuado formato de respuesta como JSON o XML. Mas allá, vamos a tener nuestras manos sobre el código y completar lo básico de la aplicación construyendo todos los caminos necesarios. También vamos a implementar acceso seguro a la API implementando autenticación por intercambio de cabeceras HTTP. Finalmente, en el último capítulo, vamos a añadir técnicas de optimización para mejorar la estructura y tiempos de respuesta del servidor. -La aplicación final rozaŕa la superficie de iniciar una tienda donde los usuario pueden realizar ordenes, subir productos y mas. Hay muchas opciones allá afuera para echar a andar una tienda en linea, como http://shopify.com[Shopify], http://spreecommerce.com/[Spree] ó http://magento.com[Magento]. +La aplicación final rozará la superficie de iniciar una tienda donde los usuario pueden realizar ordenes, subir productos y más. Hay muchas opciones allá afuera para echar a andar una tienda en linea, como http://shopify.com[Shopify], http://spreecommerce.com/[Spree] o http://magento.com[Magento]. == Convenciones en este libro -Las convenciones en este libro estan basadas en este http://www.railstutorial.org/book/beginning#sec-conventions[Tutorial de Ruby on Rails]. En esta seccion vamos a mencionar algunas que tal vez no son muy claras. +Las convenciones en este libro están basadas en este http://www.railstutorial.org/book/beginning#sec-conventions[Tutorial de Ruby on Rails]. En esta sección vamos a mencionar algunas que tal vez no son muy claras. -Utilizaré muchos ejemplos usando la linea de comandos. No intentare con windows `cmd` (lo siento chic@s), asi que basare todos los ejemplos usando el estilo Unix, como a continuación se observa: +Utilizaré muchos ejemplos usando la línea de comandos. No intentare con windows `cmd` (lo siento chic@s), así que basare todos los ejemplos usando el estilo Unix, como a continuación se observa: [source,bash] ---- @@ -36,63 +36,63 @@ $ echo "A command-line command" A command-line command ---- -Estare usando algunas pautas relacionadas al lenguaje, y me refiero a lo siguiente: +Estaré usando algunas pautas relacionadas al lenguaje, y me refiero a lo siguiente: * *Evitar* significa que no debes hacerlo * *Preferir* indica que las 2 opciones, la primera es mejor -* *Usar* significa que eres bueno apra usar el recurso +* *Usar* significa que eres bueno para usar el recurso -Si por alguna razón encuentras errores cuando ejecutas un comando, en lugar de tratar de explicar cada resultado posible, te recomiendo 'googlearlo', lo cual no lo considero una mala practica. Pero si te gusta tomar una cerveza o tienes problemas con el tutorial siempre puedes mailto:contact@rousseau-alexandre.fr[escribirme]. +Si por alguna razón encuentras errores cuando ejecutas un comando, en lugar de tratar de explicar cada resultado posible, te recomiendo 'googlearlo', lo cual no lo considero una mala práctica. Pero si te gusta tomar una cerveza o tienes problemas con el tutorial siempre puedes mailto:contact@rousseau-alexandre.fr[escribirme]. == Entornos de desarrollo -Una de las partes mas dolorosas para casi todo desarrollador es configurar el entorno de desarrollo, pero mientras lo hagas, los siguientes pasos pueden ser una pieza del pastel y una buena recompenza. Asi que voy a guiarte para que te sientas motivado. +Una de las partes más dolorosas para casi todo desarrollador es configurar el entorno de desarrollo, pero mientras lo hagas, los siguientes pasos pueden ser una pieza del pastel y una buena recompensa. Así que voy a guiarte para que te sientas motivado. === Editores de texto y terminal -Hay muchos casos en que los entornos de desarrolo pueden diferir de computadora a computadora. Este no es el caso con los editores de texto o IDE's. Pienso que para el desarrolo en Rails un IDE es demasiado, pero alguien podria encontrarlo como la mejor forma de hacerlo, asi que si es tú caso te recomiendo que lo hagas con http://www.aptana.com/products/radrails[RadRails] ó http://www.jetbrains.com/ruby/index.html[RubyMine], ambos están bien soportados y vienen con muchas integraciones 'out of the box'. +Hay muchos casos en que los entornos de desarrollo pueden diferir de computadora a computadora. Este no es el caso con los editores de texto o IDE's. Pienso que para el desarrollo en Rails un IDE es demasiado, pero alguien podría encontrarlo como la mejor forma de hacerlo, así que si es tú caso te recomiendo que lo hagas con http://www.aptana.com/products/radrails[RadRails] o http://www.jetbrains.com/ruby/index.html[RubyMine], ambos están bien soportados y vienen con muchas integraciones 'out of the box'. -*Editor de texto*: En lo personal uso http://www.vim.org/[vim] como mi editor por defecto con https://github.com/carlhuda/janus[janus] el cual puede añadir y manejar muchos de los plugins que probablemente vas a utilizar. En caso que no sea un fande _vim_ como yo, hay muchas otras soluciones como http://www.sublimetext.com/[Sublime Text] que es multi plataforma, facil de aprender y customizable(este es probablemente ru mejor opción), esta altamente inspirado por http://macromates.com/[TextMate] (solo disponible para Mac OS). Una tercera opcion es usando un muy reciente editor de texto de los chicos de http://gitub.com[GitHub] llamado https://atom.io/[Atom], es un prometedor editor de texto echo con JavaScript, es facil de extender y personalizar para satisfacer tus necesidades, dale una oporrunidad. Cualquiera de los editores que te presento harán del trabajo, asi que te dejo elejir cual se ajusta a tu ojo. +*Editor de texto*: En lo personal uso http://www.vim.org/[vim] como mi editor por defecto con https://github.com/carlhuda/janus[janus] el cual puede añadir y manejar muchos de los plugins que probablemente vas a utilizar. En caso que no sea un fan de _vim_ como yo, hay muchas otras soluciones como http://www.sublimetext.com/[Sublime Text] que es multi plataforma, fácil de aprender y personalizable (este es probablemente tú mejor opción), esta altamente inspirado por http://macromates.com/[TextMate] (solo disponible para Mac OS). Una tercera opción es usando un muy reciente editor de texto de los chicos de http://gitub.com[GitHub] llamado https://atom.io/[Atom], es un prometedor editor de texto echo con JavaScript, es fácil de extender y personalizar para satisfacer tus necesidades, dale una oportunidad. Cualquiera de los editores que te presento harán del trabajo, así que te dejo elegir cual se ajusta a tu ojo. -*Terminal*: Si decides seguir con http://icalialabs.github.io/kaishi/[kaishi] para configurar el entorno, notarás que pone pro defecto el shell con `zsh`, lo cual recomiendo bastante. Para la terminal, no soy fan de applicaciones de _Terminal_ que traen imporvisaciones si estas en Mac OS, así que mira http://www.iterm2.com/#/section/home[iTerm2], Que es un remplazo de la terminal para Mac OS. Si estas en Linux probablemente ya tienes una linda terminal, pero la que viene por defecto puede funcionar bien. +*Terminal*: Si decides seguir con http://icalialabs.github.io/kaishi/[kaishi] para configurar el entorno, notarás que pone pro defecto el shell con `zsh`, lo cual recomiendo bastante. Para la terminal, no soy fan de aplicaciones de _Terminal_ que traen mejoras si estas en Mac OS, así que mira http://www.iterm2.com/#/section/home[iTerm2], Que es un remplazo de la terminal para Mac OS. Si estas en Linux probablemente ya tienes una linda terminal, pero la que viene por defecto puede funcionar bien. === Navegadores -Cuando se trata de navegadores diria http://www.mozilla.org/en-US/firefox/new/[Firefox] inmediatamente, pero algunos otros desarrolladores pueden decir https://www.google.com/intl/en/chrome/browser/[Chrome] o incluso https://www.apple.com/safari/[Safari]. Cualquiera de ellos ayudara a construir la aplicacion que buscas, ellos vienen con un buen inspector no justamente para el DOM pero para el analisis de red y muchas otras caracteristicas que ya conoces. +Cuando se trata de navegadores diría http://www.mozilla.org/en-US/firefox/new/[Firefox] inmediatamente, pero algunos otros desarrolladores pueden decir https://www.google.com/intl/en/chrome/browser/[Chrome] o incluso https://www.apple.com/safari/[Safari]. Cualquiera de ellos ayudara a construir la aplicación que buscas, ellos vienen con un buen inspector no justamente para el DOM pero para el análisis de red y muchas otras características que ya conoces. === Manejador de paquetes -* *Mac OS*: Hay muchas opciones para gestionasr como instalar tus paquetes en tu Mac, como el https://www.macports.org/[Mac Ports] ó http://brew.sh/[Homebrew], ambos son buenas opciones pero yo elegiría la ultima, he encontrado menos problemas cuando instalo software y lo administro. Para instalar `brew` solo ejecuta en la consola lo siguiente: +* *Mac OS*: Hay muchas opciones para gestionar o instalar tus paquetes en tu Mac, como el https://www.macports.org/[Mac Ports] ó http://brew.sh/[Homebrew], ambos son buenas opciones pero yo elegiría la última, he encontrado menos problemas cuando instalo software y lo administro. Para instalar `brew` solo ejecuta en la consola lo siguiente: [source,bash] ---- $ /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" ---- -* *Linux*: Estas listo!, realmente no es mucho problema si tu estas usando `apt`, `pacman`, `yum` siempre que te sientas comodo con ello sepas como instalar paquetes para poder seguir avanzando. +* *Linux*: Estas listo!, realmente no es mucho problema si tu estas usando `apt`, `pacman`, `yum` siempre que te sientas cómodo con ello sepas como instalar paquetes para poder seguir avanzando. === Git -Usaremos Git bastante, y puedes usarlo no solo para el proposito de este tutorial sino para cada proyecto independiente. +Usaremos Git bastante, y puedes usarlo no solo para el propósito de este tutorial sino para cada proyecto independiente. * en Mac OS: `$ brew install git` * en Linux: `$ sudo apt-get install git` === Ruby -Son muchos los caminos en que puedes instalar y gestionar ruby, y ahora tú puedes tener probablemente alguna version instalada si estas en Mac OS, para ver la version que tienes, solo ejecuta: +Son muchos los caminos en que puedes instalar y gestionar ruby, y ahora tú puedes tener probablemente alguna versión instalada si estas en Mac OS, para ver la versión que tienes, solo ejecuta: [source,bash] ---- $ ruby -v ---- -Rails 6.0 requiere la instalacion de la version 2.5 o mayor. +Rails 6.0 requiere la instalación de la versión 2.5 o mayor. -Yo recomiento usar http://rvm.io/[Ruby Version Manager (RVM)] ó http://rbenv.org/[rbenv] para instalarlo. Vamos a usar RVM en este tutorial pero no hay problema con cual de las 2 utilices. +Yo recomiendo usar http://rvm.io/[Ruby Version Manager (RVM)] ó http://rbenv.org/[rbenv] para instalarlo. Vamos a usar RVM en este tutorial, pero no hay problema con cuál de las 2 utilices. -El principio de esta herramienta es permitirte instalar varias versiones de Ruby en el mismo equipo, en un entorno hermetico con una posible versión instalada en tu sistema operativo y luego tener la habilidad de cambiar de una a otra version facilmente. +El principio de esta herramienta es permitirte instalar varias versiones de Ruby en el mismo equipo, en un entorno hermético con una posible versión instalada en tu sistema operativo y luego tener la habilidad de cambiar de una a otra versión fácilmente. Para instalar RVM, ve a https://rvm.io/ e instala la huella de la llave GPG: [La huella de la llave GPG te permite verificar la identidad del autor o del origen de la descarga.]. Para realizarlo ejecutamos: @@ -111,7 +111,7 @@ $ rvm install 2.6 Ahora es momento de instalar el resto de dependencias que vamos a usar. -==== Gemas, Rails & Librerias faltantes +==== Gemas, Rails y Librerías faltantes Primero actualizamos las gemas en el sistema: @@ -121,14 +121,14 @@ Primero actualizamos las gemas en el sistema: $ gem update --system ---- -En algunos casos si estas en Mac OS, necesitarás instalar algunas librerias extras: +En algunos casos si estas en Mac OS, necesitarás instalar algunas librerías extras: [source,bash] ---- $ brew install libtool libxslt libksba openssl ---- -Luego instalamos las gemas necesarias e ingoramos la documentación para cada una: +Luego instalamos las gemas necesarias e ignoramos la documentación para cada una: [source,bash] ---- @@ -136,7 +136,7 @@ $ gem install bundler $ gem install rails -v 6.0.0 ---- -Revisamos que todo funcina correctamente: +Revisamos que todo funciona correctamente: [source,bash] ---- @@ -146,7 +146,7 @@ Rails 6.0.0 ==== Base de datos -Recomiento altamante que instales http://www.postgresql.org/[Postgresql] para gestionar tus bases de datos. Pero aquí usaremos http://www.sqlite.org/[SQlite] por simplicidad. Si estas usando Mac OS estas listo para continuar, en caso que uses Linux, no te preocupes solo nos faltan unos pasos más: +Recomiendo mucho que instales http://www.postgresql.org/[Postgresql] para gestionar tus bases de datos. Pero aquí usaremos http://www.sqlite.org/[SQlite] por simplicidad. Si estas usando Mac OS estas listo para continuar, en caso que uses Linux, no te preocupes solo nos faltan unos pasos más: [source,bash] ---- @@ -162,7 +162,7 @@ $ sudo yum install libxslt-devel libxml2-devel libsqlite3-devel == Inicializando el proyecto -Inicializar una aplicación Rails puede ser muy sencillo para ti. Si no es el caso aqui tienes un tutorial super rápido. +Inicializar una aplicación Rails puede ser muy sencillo para ti. Si no es el caso aquí tienes un tutorial super rápido. Estos son los comandos: @@ -173,9 +173,9 @@ $ cd ~/workspace $ rails new market_place_api --api ---- -NOTE: La opción `--api` aparecio en la version 5 de Rails. Ésta te permite limitar las librerías y _Middleware_ incluido en la aplicación. Esto tambien evita generar vistas HTML cuando se usan los generadores de Rails. +NOTE: La opción `--api` apareció en la versión 5 de Rails. Ésta te permite limitar las librerías y _Middleware_ incluido en la aplicación. Esto también evita generar vistas HTML cuando se usan los generadores de Rails. -Como puedes adivinar, los anteriores comandos gerarán los huesos desnudos de tu aplicación Rails. +Como puedes adivinar, los anteriores comandos generaran los huesos desnudos de tu aplicación Rails. == Versionado @@ -183,7 +183,7 @@ Recuerda que Git te ayuda a dar seguimiento y mantener el historial de tu códig Ruby on Rails inicializa el directorio Git por tí cuando usas el comando `rails new`. Esto significa que no necesitas ejecutar el comando `git init`. -Sin embargo es necesario configurar la informacion del autor de los _commits_. Si aún no lo has echo, ve al directorio de proyecto y corre los siguientes comandos: +Sin embargo es necesario configurar la información del autor de los _commits_. Si aún no lo has echo, ve al directorio de proyecto y corre los siguientes comandos: [source,bash] ---- @@ -191,7 +191,7 @@ $ git config --global user.name "Aquí pon tu nombre" $ git config --global user.email "Aquí pon tu email" ---- -Rails tambien provee un archivo _.gitignore_ para ignorar algunos archivos a los que no queramos dar seguimiento. El archivo _.gitignore_ por defecto puede lucir como se ve a continuación: +Rails también provee un archivo _.gitignore_ para ignorar algunos archivos a los que no queramos dar seguimiento. El archivo _.gitignore_ por defecto puede lucir como se ve a continuación: ..gitignore ---- @@ -217,7 +217,7 @@ Rails tambien provee un archivo _.gitignore_ para ignorar algunos archivos a los /config/master.key ---- -Despues de modificar el archivo _.gitignore_ unicamente necesitamos añadir los archivos y hacer _commit_ de los cambios, para ello usamos los siguientes comandos: +Después de modificar el archivo _.gitignore_ únicamente necesitamos añadir los archivos y hacer _commit_ de los cambios, para ello usamos los siguientes comandos: [source,bash] ---- @@ -225,7 +225,7 @@ $ git add . $ git commit -m "Commit Inicial" ---- -TIP: He encontrado que el mensaje del commit deberia iniciar con un verbo en tiempo presente, describiendo lo que el commit hace y no lo que hizo, ayuda cuando estás explorando el historial del proyecto. Encontre esto mas natural para leer y entender. Seguiremos esta práctica hasta el final del tutorial. +TIP: He encontrado que el mensaje del commit debería iniciar con un verbo en tiempo presente, describiendo lo que el commit hace y no lo que hizo, ayuda cuando estás explorando el historial del proyecto. Encontré esto más natural para leer y entender. Seguiremos esta práctica hasta el final del tutorial. Por ultimo y como un paso opcional configuramos el proyecto en GitHub y hacemos _push_ de nuestro código al servidor remoto: Pero primero añadimos el _remoto_: @@ -241,8 +241,8 @@ Entonces hacemos _push_(empujamos) el código: $ git push -u origin master ---- -A medida que avanzamos con el tútorial, usaré las practicas que uso a diario, esto incluye trabajar con `branches`(ramas), `rebasing`, `squash` y algo mas. Por ahora no debes preocuparte si algunos terminos no te suenan familiares, te guiaré en ello con el tiempo. +A medida que avanzamos con el tútorial, usaré las practicas que uso a diario, esto incluye trabajar con `branches`(ramas), `rebasing`, `squash` y algo mas. Por ahora no debes preocuparte si algunos términos no te suenan familiares, te guiaré en ello con el tiempo. == Conclusión -Ha sido un largo camino a travéz de este capítulo, si has llegado hasta aquí déjame felicitarte y asegurarte que a partir de este punto las cosas mejorarán. Asi que vamos a ensuciarnos las manos y comenzar a escribir algo de código! +Ha sido un largo camino a través de este capítulo, si has llegado hasta aquí déjame felicitarte y asegurarte que a partir de este punto las cosas mejorarán. Asi que vamos a ensuciarnos las manos y comenzar a escribir algo de código! diff --git a/rails6/es/chapter02-api.adoc b/rails6/es/chapter02-api.adoc index 3374661..cf31868 100644 --- a/rails6/es/chapter02-api.adoc +++ b/rails6/es/chapter02-api.adoc @@ -1,7 +1,7 @@ [#chapter02-api] = La API -En esta sección resumiré la applicación. Hata aquí ya debiste leer el capítulo anterior. Si no lo has leído te recomiento que lo hagas. +En esta sección resumiré la aplicación. Hasta aquí ya debiste leer el capítulo anterior. Si no lo has leído te recomiendo que lo hagas. Puedes clonar el proyecto hasta este punto con: @@ -14,52 +14,52 @@ Resumiendo, simplemente generamos nuestra aplicación Rails e hicimos el primer == Planificando la aplicación -Como queremos que la aplicacion sea sencilla, esta consistirá de 5 modelos. No te preocupes si no entiendes completamente que estamos haciendo. Vamos a revisar y a construir cada uno de los recursos a medida que avancemos con el tutorial. +Como queremos que la aplicación sea sencilla, esta consistirá de 5 modelos. No te preocupes si no entiendes completamente que estamos haciendo. Vamos a revisar y a construir cada uno de los recursos a medida que avancemos con el tutorial. image:data_model.png[Esquema conexiones entre los modelos] -Resumiendo, el `user`(usuario) podrá ralizar muchas `orders`(ordenes/pedidos), subir multiples `products`(productos) los cuales pueden tener muchas `images`(imágenes) ó `comments`(comentarios) de otros usuarios de la applicación. +Resumiendo, el `user`(usuario) podrá realizar muchas `orders`(ordenes/pedidos), subir múltiples `products`(productos) los cuales pueden tener muchas `images`(imágenes) ó `comments`(comentarios) de otros usuarios de la aplicación. -No construiremos vistas para mostrar o interactuar con la API, asi que no hagas de esto un gran tutorial. Para ello hay muchas opciones allá afuera como los frameworks de javascript (https://angularjs.org/[Angular], https://vuejs.org/[Vue.js], https://reactjs.org/[React.js]). +No construiremos vistas para mostrar o interactuar con la API, así que no hagas de esto un gran tutorial. Para ello hay muchas opciones allá afuera como los frameworks de javascript (https://angularjs.org/[Angular], https://vuejs.org/[Vue.js], https://reactjs.org/[React.js]). Hasta este punto deberías preguntarte: -> ¿Esta bien, pero, yo necesito explorar ó visualizar como va la construccion del API? +> ¿Esta bien, pero, yo necesito explorar o visualizar cómo va la construcción del API? -Y eso es justo. Probablemente si googleas algo relacionado con explarar un api, aparecerá una aplicacion llamada https://www.getpostman.com/[Postman]. Este es un gran software pero no lo utilizaremos porque usaremos *cURL* que permite a cualquiera reproducir peticiones en cualquier computadora. +Y eso es justo. Probablemente si googleas algo relacionado con explorar un api, aparecerá una aplicación llamada https://www.getpostman.com/[Postman]. Este es un gran software pero no lo utilizaremos porque usaremos *cURL* que permite a cualquiera reproducir peticiones en cualquier computadora. == Configurar la API -Una API es definida por http://en.wikipedia.org/wiki/Application_programming_interface[wikipedia] como _La interfaz de programación de aplicaciones(API), es un conjunto de subrutinas, funciones y procedimientos que ofrece cierta biblioteca para ser utilizado por otro software como una capa de abstracción._ En otras palabras la forma en que el sistema interactua entre sí mediante una interfaz común, en nuestro caso un servicio web construido con JSON. Hay otros protocolos de comunicación como SOAP, pero no lo cubriremos aquí. +Una API es definida por http://en.wikipedia.org/wiki/Application_programming_interface[wikipedia] como _La interfaz de programación de aplicaciones (API), es un conjunto de subrutinas, funciones y procedimientos que ofrece cierta biblioteca para ser utilizado por otro software como una capa de abstracción. _ En otras palabras la forma en que el sistema interactúa entre sí mediante una interfaz común, en nuestro caso un servicio web construido con JSON. Hay otros protocolos de comunicación como SOAP, pero no lo cubriremos aquí. -JSON, como tipo estandar en Internet, es ampliamente aceptado, legible, extensible y facil de implementar. -Muchos de los frameworks actuales consumen APIs JSON por defecto (https://angularjs.org/[Angular] ó https://vuejs.org/[Vue.js] por ejemplo). Tambien hay grandes bibliotecas para Objetive-C como https://github.com/AFNetworking/AFNetworking[AFNetworking] ó http://restkit.org/[RESTKit]. Probablemente hay buenas soluciones para Android pero por mi falta de experiencia en esa plataforma, podría no ser la persona adecuada para recomendarte alguna. +JSON, como tipo estándar en Internet, es ampliamente aceptado, legible, extensible y fácil de implementar. +Muchos de los frameworks actuales consumen APIs JSON por defecto (https://angularjs.org/[Angular] ó https://vuejs.org/[Vue.js] por ejemplo). También hay grandes bibliotecas para Objetive-C como https://github.com/AFNetworking/AFNetworking[AFNetworking] ó http://restkit.org/[RESTKit]. Probablemente hay buenas soluciones para Android, pero por mi falta de experiencia en esa plataforma, podría no ser la persona adecuada para recomendarte alguna. -Muy bien. Asi que vamos a construir nuestra API con JSON. Hay muchos caminos para logarlo. Lo primero que me viene a la mente es justamente iniciar añadiendo rutas definiendo los _end points_. Pero puede ser mala idea porque no hay un http://www.w3.org/2005/Incubator/wcl/matching.html[patrón URI] suficientemente claro para saber que recurso esta expuesto. El protocolo o estructura del que estoy hablando es http://en.wikipedia.org/wiki/Representational_state_transfer[REST] que significa Transferencia de Estado Representacional(Representational state transfer) según la definición de Wikipedia. +Muy bien. Así que vamos a construir nuestra API con JSON. Hay muchos caminos para logarlo. Lo primero que me viene a la mente es justamente iniciar añadiendo rutas definiendo los _end points_. Pero puede ser mala idea porque no hay un http://www.w3.org/2005/Incubator/wcl/matching.html[patrón URI] suficientemente claro para saber que recurso está expuesto. El protocolo o estructura del que estoy hablando es http://en.wikipedia.org/wiki/Representational_state_transfer[REST] que significa Transferencia de Estado Representacional(Representational state transfer) según la definición de Wikipedia. [source,soap] ---- aService.getUser("1") ---- -Y en REST puedes llamar una URL con una peticion HTTP especifica, en este caso con una peticion GET: +Y en REST puedes llamar una URL con una petición HTTP específica, en este caso con una petición GET: La APIs RESTful debe seguir al menos tres simples pautas: * Una base http://en.wikipedia.org/wiki/Uniform_resource_identifier[URI], como es `http://example.com/resources/`. -* Un tipo multimedia de Internet para representar los datos, es comunmente JSON y es comunmente definido mediente el intercambio de cabeceras. -* Sigue el estandar http://en.wikipedia.org/wiki/HTTP_method#Request_methods[Metodos HTTP] como son GET, POST, PUT, DELETE. +* Un tipo multimedia de Internet para representar los datos, es comúnmente JSON y es comúnmente definido mediante el intercambio de cabeceras. +* Sigue el estándar http://en.wikipedia.org/wiki/HTTP_method#Request_methods[Metodos HTTP] como son GET, POST, PUT, DELETE. ** *GET*: Lee el recurso o recursos definidos por el patrón URI ** *POST*: Crea una nueva entrada en la colección de recursos -** *PUT*: Actualiza una colección o un miebro de los recursos +** *PUT*: Actualiza una colección o un miembro de los recursos ** *DELETE*: Destruye una colección o miembro de los recursos -Esto podría no ser suficientemente claro o podría parecer mucha información para digerir, pero como vamos avanzando en el tutorial, con suerte conseguiras entender con mayor fácilidad. +Esto podría no ser suficientemente claro o podría parecer mucha información para digerir, pero como vamos avanzando en el tutorial, con suerte conseguirás entender con mayor facilidad. === Restricciones de Rutas y Espacios de Nombres -Antes de comenzar a escribir código, preparamos el código con git. Vamos a estar usando una rama por capítulo, la subiremos a GitHub y entonces la fusionaremos con la rama master. Asi que vamos a a iniciar abriendo la terminal, `cd` hacia el directorio `market_place_api` y tecleamos lo siguiente: +Antes de comenzar a escribir código, preparamos el código con git. Vamos a estar usando una rama por capítulo, la subiremos a GitHub y entonces la fusionaremos con la rama master. Así que vamos a a iniciar abriendo la terminal, `cd` hacia el directorio `market_place_api` y tecleamos lo siguiente: [source,bash] ---- @@ -67,7 +67,7 @@ $ git checkout -b chapter02 Switched to a new branch 'chapter02' ---- -Unicamente vamos a estrar trabajando en `config/routes.rb`, ya que solo vamos a establecer las restricciones y el `formato` de respuesta predeterminado para cada respuesta. +Únicamente vamos a estar trabajando en `config/routes.rb`, ya que solo vamos a establecer las restricciones y el `formato` de respuesta predeterminado para cada respuesta. [source,ruby] .config/routes.rb @@ -105,11 +105,11 @@ Rails.application.routes.draw do end ---- -Por definicion un espacio de nombres en el archivo `routes.rb`. Rails automaticamente mapeara que espacio de nombres corresponde al folder de los _controlladores_, en nuestro caso el directorio `api/``. +Por definición un espacio de nombres en el archivo `routes.rb`. Rails automáticamente mapeara que espacio de nombres corresponde al folder de los _controlladores_, en nuestro caso el directorio `api/``. .Archivos multimedia soportados por Rails **** -Rails soporta 35 tipos diferentes de archivos multimedia, puedes listarlos accediendo a la clase SET del modulo Mime: +Rails soporta 35 tipos diferentes de archivos multimedia, puedes listarlos accediendo a la clase SET del módulo Mime: [source,bash] ---- @@ -132,7 +132,7 @@ Rails.application.routes.draw do end ---- -Hasta este punto no hemos hecho nada loco. Ahora lo que queremos es una _base_uri_ que incluye la version de la API. Pero hagamos commit antes de ir a la siguiente sección: +Hasta este punto no hemos hecho nada loco. Ahora lo que queremos es una _base_uri_ que incluye la versión de la API. Pero hagamos commit antes de ir a la siguiente sección: [source,bash] ---- @@ -142,7 +142,7 @@ $ git commit -m "Set the routes constraints for the api" == Versionado Api -Hasta este punto deberiamos tener un buen mapeado de rutas usando espacio de nombres. Tu archivo `routes.rb` debería lucir como esto: +Hasta este punto deberíamos tener un buen mapeado de rutas usando espacio de nombres. Tu archivo `routes.rb` debería lucir como esto: [source,ruby] .config/routes.rb @@ -155,16 +155,16 @@ Rails.application.routes.draw do end ---- -Ahora es tiempo de confugurar algunas otras restricciones para propositos de versionado. Deberias preocuparte por versionar tú aplicación desde el inicio pues le dara una mejor estrutura a tu api, y cuando hagas cambios, puedes dar a los desarrolladores que estan consumento tu api la oportunidad de adaptar las nuevas caraceristicas mientras las viejas quedan obsoletas. Este es un exelente http://railscasts.com/episodes/350-rest-api-versioning[railscast] explicando esto. +Ahora es tiempo de configurar algunas otras restricciones para propósitos de versionado. Deberías preocuparte por versionar tú aplicación desde el inicio pues le dará una mejor estructura a tu api, y cuando hagas cambios, puedes dar a los desarrolladores que están consumiendo tu api la oportunidad de adaptar las nuevas características mientras las viejas quedan obsoletas. Este es un excelente http://railscasts.com/episodes/350-rest-api-versioning[railscast] explicando esto. -Para establecer la version del API, primero necesitamos agregar otro directorio en el de `api` que antes creamos: +Para establecer la versión del API, primero necesitamos agregar otro directorio en el de `api` que antes creamos: [source,bash] ---- $ mkdir app/controllers/api/v1 ---- -De esta forma podemos definir espacio de nombres a nuesra api con diferentes versiones facilmente, ahora solo necesitamos añadir el codigo necesario al archivo `routes.rb`: +De esta forma podemos definir espacio de nombres a nuestra api con diferentes versiones fácilmente, ahora solo necesitamos añadir el código necesario al archivo `routes.rb`: [source,ruby] .config/routes.rb @@ -179,18 +179,18 @@ Rails.application.routes.draw do end ---- -Hasta este punto, el API puede ser alcanzada a travéz de la URL. Por ejemplo con esta configuracion un end-point para reuperar un producto podría ser algo como: . +Hasta este punto, el API puede ser alcanzada a través de la URL. Por ejemplo con esta configuración un end-point para recuperar un producto podría ser algo como: . .Patrones Comunes del API **** -Puedes encontrar muchas forma de configurar un _base_uri_ cuando construimos un api siguiendo diferentes patrones, asumiendo que estamos versionando nuestra api: +Puedes encontrar muchas formas de configurar un _base_uri_ cuando construimos un api siguiendo diferentes patrones, asumiendo que estamos versionando nuestra api: -* `api.example.com/`: En mi opinion este es el camino a seguir, te da una mejor interfaz y aislamiento, y a largo plazo puede ayudarte a http://www.makeuseof.com/tag/optimize-your-dns-for-faster-internet/[escalar rapidamente] +* `api.example.com/`: En mi opinión este es el camino a seguir, te da una mejor interfaz y aislamiento, y a largo plazo puede ayudarte a http://www.makeuseof.com/tag/optimize-your-dns-for-faster-internet/[escalar rápidamente] * `example.com/api/`: Este patrón es muy común, y es actualmente un buen camino a seguir cuando no quieres poner bajo espacio de nombres tu api en un subdominio -* `example.com/api/v1`: parece buena idea, poniendo la version del api mediante la URL, parece como un patrón descriptivo, pero esta forma te forza a incluir la URL en cada petición, asi que si en algún momento decides cambiar este patrón, se convierte en un problema de mantenimiento a largo plazo. +* `example.com/api/v1`: parece buena idea, poniendo la versión del api mediante la URL, parece como un patrón descriptivo, pero esta forma te forza a incluir la URL en cada petición, así que si en algún momento decides cambiar este patrón, se convierte en un problema de mantenimiento a largo plazo. -Estas son algunas practicas en la construccion de una API que recomiendan no versionar el API a travez de la URL. Es verdad. El desarrollador no debería conocer la version que esta usando. En terminos de simplicidad, he decidido dejar esta convención, que podremos aplicar en una segunda fase. +Estas son algunas prácticas en la construcción de una API que recomiendan no versionar el API a través de la URL. Es verdad. El desarrollador no debería conocer la versión que está usando. En términos de simplicidad, he decidido dejar esta convención, que podremos aplicar en una segunda fase. **** Es tiempo de hacer _commit_: @@ -200,7 +200,7 @@ Es tiempo de hacer _commit_: $ git commit -am "Set the versioning namespaces for API" ---- -Estamos en lo ultimo del capitulo. Por lo tanto es tiempo de aplicar nuestras modificaciones a la rama master haciendo un _merge_. Para hacerlo, nos cambiamos a la rama `master` y hacemos _merge_ de `chapter02`: +Estamos en lo último del capítulo. Por lo tanto, es tiempo de aplicar nuestras modificaciones a la rama master haciendo un _merge_. Para hacerlo, nos cambiamos a la rama `master` y hacemos _merge_ de `chapter02`: [source,bash] ---- @@ -210,9 +210,9 @@ $ git merge chapter02 == Conclusión -Ha sido un largo camino, lo se, pero lo hiciste, no te rindas esto solo es un pequeño escalón para cualquier cosa grande, asi que sigue. Mientras tanto y si te sientes curioso hay algunas gemas que pueden manejar este tipo de confuguración: +Ha sido un largo camino, lo sé, pero lo hiciste, no te rindas esto solo es un pequeño escalón para cualquier cosa grande, así que sigue. Mientras tanto y si te sientes curioso hay algunas gemas que pueden manejar este tipo de configuración: * https://github.com/Sutto/rocket_pants[RocketPants] * https://github.com/bploetz/versionist[Versionist] -No cubriré eso en este libro, ya que estamos intentando aprender a implementar este tipo de funcionalidades, pero es bueno saberlo. Por cierto el código hasta este punto está https://github.com/madeindjs/market_place_api_6/releases/tag/checkpoint_chapter03[aquí]. +No cubriré eso en este libro, ya que estamos intentando aprender a implementar este tipo de funcionalidades, pero es bueno saberlo. Por cierto, el código hasta este punto está https://github.com/madeindjs/market_place_api_6/releases/tag/checkpoint_chapter03[aquí]. diff --git a/rails6/es/chapter03-presenting-users.adoc b/rails6/es/chapter03-presenting-users.adoc index 04474b0..6a325cc 100644 --- a/rails6/es/chapter03-presenting-users.adoc +++ b/rails6/es/chapter03-presenting-users.adoc @@ -1,9 +1,9 @@ [#chapter03-presenting-users] = Presentando a los usuarios -En el último capítulo configuramos el esqueleto para la configuracion de los enpoints en nuestra aplicación. +En el último capítulo configuramos el esqueleto para la configuración de los enpoints en nuestra aplicación. -En un próximo capítulo manejaremos autenticación de usuarios mediante autenticación con tokens configurando permisos para poner límites de acceso preguntando que usuario esta autenticado. En capitulos venideros vamos a relacionar `products` (productos) a usuarios y dar la habilidad de generar órdenes. +En un próximo capítulo manejaremos autenticación de usuarios mediante autenticación con tokens configurando permisos para poner límites de acceso preguntando que usuario esta autenticado. En capítulos venideros vamos a relacionar `products` (productos) a usuarios y dar la habilidad de generar órdenes. Puedes clonar el proyecto hasta este punto con: @@ -14,10 +14,10 @@ $ git checkout tags/checkpoint_chapter03 Como ya estarás imaginando hay muchas soluciones de autenticación para Rails, https://github.com/binarylogic/authlogic[AuthLogic], https://github.com/thoughtbot/clearance[Clearance] y https://github.com/plataformatec/devise[Devise]. -Estas libreriras son soluciones como llave en mano, por ejemplo ellas te permiten gestionar un montón de cosas como autenticación, olvido de contraseña, validación, etc.. Sin embargo vamos a usar la gema https://github.com/codahale/bcrypt-ruby[bcrypt] para generar un hash para la contraseña del usuario. +Estas librerías son soluciones como llave en mano, por ejemplo ellas te permiten gestionar un montón de cosas como autenticación, olvido de contraseña, validación, etc.. Sin embargo, vamos a usar la gema https://github.com/codahale/bcrypt-ruby[bcrypt] para generar un hash para la contraseña del usuario. -Este capítulo estará completo. Puede ser largo pero intentare cubrir el mayor numero de temas posibles. -Sientete libre de tomar un café y vamos. Al final de este capítulo tendrás construida la logica del usuario asi como la validación y manejo de errores. +Este capítulo estará completo. Puede ser largo pero intentare cubrir el mayor número de temas posibles. +Siéntete libre de tomar un café y vamos. Al final de este capítulo tendrás construida la lógica del usuario así como la validación y manejo de errores. Es un buen momento para crear una nueva rama: @@ -26,18 +26,18 @@ Es un buen momento para crear una nueva rama: $ git checkout -b chapter03 ---- -NOTE: Asegurate que estas en la rama `master` antes de hacer _checkout_. +NOTE: Asegúrate que estas en la rama `master` antes de hacer _checkout_. == Modelo usuario -=== Generación de el modelo `User` +=== Generación del modelo `User` -Comenzaremos por generar nuestro modelo `User`. Este modelo será realmente basico y trendrá solo dos campos: +Comenzaremos por generar nuestro modelo `User`. Este modelo será realmente básico y tendrá solo dos campos: -- `email` el cual sera único y permitira conectar con la aplicación -- `password_digest` el cual contiene la version *hasheada* de la contraseña (los discutiremos mas tarde en este capítulo) +- `email` el cual será único y permitirá conectar con la aplicación +- `password_digest` el cual contiene la versión *hasheada* de la contraseña (los discutiremos mas tarde en este capítulo) -Genereamos nuestro modelo `User` usando el comando _generate model_ provisto por Ruby on Rails. Es muy facil de usar: +Generamos nuestro modelo `User` usando el comando _generate model_ provisto por Ruby on Rails. Es muy fácil de usar: [source,bash] ---- @@ -50,11 +50,11 @@ invoke active_record create test/fixtures/users.yml ---- -NOTE: El _modelo_ es el elemento que contiene la información o datos asi como la logica relacionada a esa información: validacion, lectura y guardado. +NOTE: El _modelo_ es el elemento que contiene la información o datos así como la lógica relacionada a esa información: validación, lectura y guardado. -Este comando genera un monton de archivos! No te preocupes revisaremos uno por uno. +¡Este comando genera un montón de archivos! No te preocupes revisaremos uno por uno. -El archivo de migración contenido en el forder `db/migrate` conteiene la *migración* que describe los cambios que realizará en la base de datos. Este archivo puede lucir así: +El archivo de migración contenido en el forder `db/migrate` contiene la *migración* que describe los cambios que realizará en la base de datos. Este archivo puede lucir así: .db/migrate/20190603195146_create_users.rb [source,ruby] @@ -73,12 +73,12 @@ end NOTE: La fecha insertada al inicio del nombre del archivo de migración debiera ser diferente para ti ya que corresponde a la fecha de creación de la migración. -Haremos un pequeño cambio a la migracion a fin de añadir algunas validaciones a la base de datos. Con rails es una práctica común hacer validaciones directamente en el modelo Ruby. Es buena práctica hacer algo en el esquema de la base de datos. +Haremos un pequeño cambio a la migración a fin de añadir algunas validaciones a la base de datos. Con rails es una práctica común hacer validaciones directamente en el modelo Ruby. Es buena práctica hacer algo en el esquema de la base de datos. Por lo tanto haremos dos restricciones adicionales: - email es forzoso: usaremos la propiedad `null: false`. -- email debe ser único: añadiremos un indice para la columna email con la propiedad `unique: true`. +- email debe ser único: añadiremos un índice para la columna email con la propiedad `unique: true`. - password es forzoso: usamos la propiedad `null: false`. La migración quedaría así: @@ -95,7 +95,7 @@ create_table :users do |t| end ---- -Una vez completa la migración, podemos correr los camibos con el siguiente comando: +Una vez completa la migración, podemos correr los cambios con el siguiente comando: .db/migrate/20190603195146_create_users.rb [source,ruby] @@ -107,11 +107,11 @@ $ rake db:migrate == 20190603195146 CreateUsers: migrated (0.0028s) ============================= ---- -NOTE: Este comando convertirá nuestra migracion en una consulta SQL que actualizara la base de datos SQLite3 almacenada en el folder _db_. +NOTE: Este comando convertirá nuestra migración en una consulta SQL que actualizara la base de datos SQLite3 almacenada en el folder _db_. ==== Modelo -Asi definimos nuestro esquema de la base de datos. El siguiente paso es actualizar nuestro modelo para definir *reglas de validación*. Estas reglas estan definidas en el modelo localizado en el folder`app/models`. +Así definimos nuestro esquema de la base de datos. El siguiente paso es actualizar nuestro modelo para definir *reglas de validación*. Estas reglas están definidas en el modelo localizado en el folder`app/models`. Ruby on Rails provee un mecanismo completo que puedes encontrar en https://guides.rubyonrails.org/active_record_validations.html[su documentación oficial]. En nuestro caso buscamos validar solo 3 cosas: @@ -131,7 +131,7 @@ class User < ApplicationRecord end ---- -Ahi tienes. Rails una sintaxis simple y el código es muy legible. +Ahí tienes. Rails una sintaxis simple y el código es muy legible. .Validación del Email **** @@ -139,23 +139,21 @@ Habrás notado que la validación del email es muy simplista solo validando la p Es normal. -Hay infinidad de excepciones en la dirección de un correo electrónico -There are infinite exceptions to the email address so well https://davidcel.is/posts/stop-validating-email-addresses-with-regex/[que incluso `Mira todos estos espacios!@example.com` es una dirección de correo valida]. Por lo tanto es mejor para favorecer un enfoque sencillo y confirmar la direccion de correo enviando un email. +Hay infinidad de excepciones en la dirección de un correo electrónico https://davidcel.is/posts/stop-validating-email-addresses-with-regex/[que incluso `Mira todos estos espacios!@example.com` es una dirección de correo valida]. Por lo tanto, es mejor para favorecer un enfoque sencillo y confirmar la dirección de correo enviando un email. **** ==== Pruebas unitarias -Finalizamos con las pruebas unitarias. Aqui usaremos Minitest un framework de -pruebas que es proporcionado por defecto con Rails. +Finalizamos con las pruebas unitarias. Aquí usaremos Minitest un framework de pruebas que es proporcionado por defecto con Rails. -Minitest esta basado en _Fixtures_ que te permiten llenar tu base de datos con datos *predefinidos*. Los _Fixtures_ estan definidos en un archivo YAML en el directorio `tests/fixtures`. Hay un archivo por plantilla. +Minitest está basado en _Fixtures_ que te permiten llenar tu base de datos con datos predefinidos*. Los _Fixtures_ están definidos en un archivo YAML en el directorio `tests/fixtures`. Hay un archivo por plantilla. Debemos por lo tanto iniciar actualizando nuestros `tests/fixtures`. -NOTE: _fixtures_ no estan diseñados para crear todas los datos que tus pruebas necesitan. Solo te permiten definir los datos basicos que tu aplicación necesita. +NOTE: _fixtures_ no están diseñados para crear todas los datos que tus pruebas necesitan. Solo te permiten definir los datos básicos que tu aplicación necesita. -Asi que comenzamos por crear un _fixture_ definiendo un usuario: +Así que comenzamos por crear un _fixture_ definiendo un usuario: .test/fixtures/users.yml [source,yaml] @@ -167,7 +165,7 @@ one: Ahora podemos crear tres pruebas: -- 1. Verifica que un usuario con datos correctos es váido: +- 1. Verifica que un usuario con datos correctos es válido: .test/models/user_test.rb [source,ruby] @@ -179,7 +177,7 @@ test 'user with a valid email should be valid' do end ---- -- 2. Verifica que un usuario con un email erroneo no es válido: +- 2. Verifica que un usuario con un email erróneo no es válido: .test/models/user_test.rb [source,ruby] @@ -191,7 +189,7 @@ test 'user with invalid email should be invalid' do end ---- -- 3. Verifica que un nuevo usuario con email no es válido. Asi que usamos el mismo email que creamos en el _fixture_. +- 3. Verifica que un nuevo usuario con email no es válido. Así que usamos el mismo email que creamos en el _fixture_. .test/models/user_test.rb [source,ruby] @@ -204,7 +202,7 @@ test 'user with taken email should be invalid' do end ---- -Ahi lo tienes. Podemos validar que nuestra implementación es correcta simplemente corriendo las pruebas unitarias que creamos: +Ahí lo tienes. Podemos validar que nuestra implementación es correcta simplemente corriendo las pruebas unitarias que creamos: [source,bash] ---- @@ -222,18 +220,18 @@ $ git add . && git commit -m "Create user model" === Hash de la contraseña -Previamente implementamos el almacenamiento de los datos del usuario. Pero seguimos teniendo un problema por resolver: *el almacenamiento de la contraseña esta en texto plano*. +Previamente implementamos el almacenamiento de los datos del usuario. Pero seguimos teniendo un problema por resolver: *el almacenamiento de la contraseña está en texto plano*. -> Si almacenas la contraseña de los usuarios en texto plano, entonces un atacante que roba una copia de tu base de datos tiene una lista gigante de emails y contraseñas. Alguno de tus usuarios podria tener únicamente una contraseña -- para su cuenta de email, para sus cuentas de banco, para su aplicación. Un simple hackeo puede escalar en un robo masivo de identidad. - https://github.com/codahale/bcrypt-ruby#why-you-should-use-bcrypt[fuente - Porque deberias usar bcrypt(en inglés)] +> Si almacenas la contraseña de los usuarios en texto plano, entonces un atacante que roba una copia de tu base de datos tiene una lista gigante de emails y contraseñas. Alguno de tus usuarios podría tener únicamente una contraseña -- para su cuenta de email, para sus cuentas de banco, para su aplicación. Un simple hackeo puede escalar en un robo masivo de identidad. - https://github.com/codahale/bcrypt-ruby#why-you-should-use-bcrypt[fuente - Porque deberías usar bcrypt(en inglés)] -Asi que vamos a usar la gema bcrypt para *hashear* la contraseña. +Así que vamos a usar la gema bcrypt para *hashear* la contraseña. -NOTE: Hashear es el proceso de transformar un arreglo de caracteres en un _Hash_. Este _Hash_ no te permite encontrar el arreglo de caracteres original. Pero como sea, podemos facilmente usarlo para encontra si un arreglo de caracteres dado coincide con el _hash_ que almacenamos. +NOTE: Hashear es el proceso de transformar un arreglo de caracteres en un _Hash_. Este _Hash_ no te permite encontrar el arreglo de caracteres original. Pero como sea, podemos fácilmente usarlo para encontrar si un arreglo de caracteres dado coincide con el _hash_ que almacenamos. Primero debemos agregar la gema Bcrypt al _Gemfile_. Podemos usar el comando `bundle add`. Que hará: 1. añadir la gema al Gemfile recuperando la versión más reciente -2. ejecutar el comando `bundle install` el cual instalará la gema y actualizara el archivo _Gemfile.lock_ "bloqueando" la versión actual de la gema +2. ejecutar el comando `bundle install` el cual instalará la gema y actualizará el archivo _Gemfile.lock_ "bloqueando" la versión actual de la gema Por lo tanto, ejecutamos el siguiente comando: @@ -242,7 +240,7 @@ Por lo tanto, ejecutamos el siguiente comando: $ bundle add bcrypt ---- -Una vez que el comando es ejecutado, la siguiente linea es añadidad al final del _Gemfile_: +Una vez que el comando es ejecutado, la siguiente línea es añadida al final del _Gemfile_: [source,ruby] .Gemfile @@ -252,7 +250,7 @@ gem "bcrypt", "~> 3.1" NOTE: La versión 3.1 de bcrypt es la versión actual al momento de escribir. Esto podría por lo tanto variar en tú caso. -Active Record nos ofrece un metodo https://github.com/rails/rails/blob/6-0-stable/activemodel/lib/active_model/secure_password.rb#L61[`ActiveModel::SecurePassword::has_secure_password`] que hara interfaz con Bcrypt y nos ayudará con la contraseña lo que lo hace mas fácil. +Active Record nos ofrece un método https://github.com/rails/rails/blob/6-0-stable/activemodel/lib/active_model/secure_password.rb#L61[`ActiveModel::SecurePassword::has_secure_password`] que hará interfaz con Bcrypt y nos ayudará con la contraseña lo que lo hace más fácil. [source,ruby] .app/models/user.rb @@ -263,13 +261,13 @@ class User < ApplicationRecord end ---- -`has_secure_password` agrega las siguintes validaciones: +`has_secure_password` agrega las siguientes validaciones: * La contraseña debe estar presente en la creación. * La longitud de la contraseña debe ser menor o igual a 72 bytes. -* La confirmación de la contraseña usa el traributo `password_confirmation` (si es enviado) +* La confirmación de la contraseña usa el atributo `password_confirmation` (si es enviado) -En adición, este metodo añadirá un atributo `User#password` que sera automaticamente hasheado y guardado en el atributo `User#password_digest`. +En adición, este método añadirá un atributo `User#password` que será automáticamente hasheado y guardado en el atributo `User#password_digest`. Vamos a intentarlo ahora mismo en la consola de Rails. Abre una consola con `rails console`: @@ -279,7 +277,7 @@ Vamos a intentarlo ahora mismo en la consola de Rails. Abre una consola con `rai =># ---- -Puedes ver que cuando llamas al metodo `User#create!` , el atributo `password` es hasheado y guardado en `password_digest`. Vamos a enviar tambien un atributo `password_confirmation` que ActiveRecord comparará con `password`: +Puedes ver que cuando llamas al método `User#create!` , el atributo `password` es hasheado y guardado en `password_digest`. Vamos a enviar también un atributo `password_confirmation` que ActiveRecord comparará con `password`: [source,ruby] ---- @@ -287,16 +285,16 @@ Puedes ver que cuando llamas al metodo `User#create!` , el atributo `password` e ActiveRecord::RecordInvalid (Validation failed: Password confirmation doesn t match Password) ---- -Todo esta trabajando como lo planeamos! Vamos a hacer un _commit_ para mantener la historia concisa: +¡Todo está trabajando como lo planeamos! Vamos a hacer un _commit_ para mantener la historia concisa: [source,bash] ---- $ git commit -am "Setup Bcrypt" ---- -== Build users +== Creando usuarios -Es tiempo de hacer nuestro primer "entry point". Iniciaremos por construir la acción `show` que responderá concon información de un usuario único en formato JSON. Los pasos son: +Es tiempo de hacer nuestro primer "entry point". Iniciaremos por construir la acción `show` que responderá con información de un usuario único en formato JSON. Los pasos son: 1. generar el controlador `users_controller`. 2. añadir las pruebas correspondientes @@ -311,27 +309,27 @@ En orden para respetar la vista de nuestra API, vamos a cortar nuestra aplicaci $ rails generate controller api::v1::users ---- -Este comando creará el archivo `users_controller_test.rb`. Antes de ir mas lejos hay dos cosas que queremos probar en nuestra API: +Este comando creará el archivo `users_controller_test.rb`. Antes de ir más lejos hay dos cosas que queremos probar en nuestra API: * La estructura JSON que devuelve el servidor -* El codigo de la respuesta HTTP que devuelve el servidor +* El código de la respuesta HTTP que devuelve el servidor .Códigos HTTP más comunes **** -El primer dígito de el codigo de estado especifica una de las 5 clases de respuesta. El minimo indispensable para un cliente HTTP es que este una de estas 5 clases. Esta es una lista de los códigos HTTP comunemten usados: +El primer dígito de el código de estado especifica una de las 5 clases de respuesta. El mínimo indispensable para un cliente HTTP es que este una de estas 5 clases. Esta es una lista de los códigos HTTP comúnmente usados: -* `200`: Respuesta estandar para una solicitud HTTP exitosa. Usualmente en solicitudes `GET` -* `201`: La petición fue recibida y resulta en la creacion de nu nuevo recurso. Despues de una solicitud `POST` -* `204`: El servidor tiene una petición procesada con éxito, pero no se regresó ningun contenido. Esto es usual en una solicitud `DELETE` exitosa. -* `400`: La petición no se peude ejecutadar debido a una sintaxis incorrecta. Puede suceder para cualquier tipo de solicitud. +* `200`: Respuesta estándar para una solicitud HTTP exitosa. Usualmente en solicitudes `GET` +* `201`: La petición fue recibida y resulta en la creación del nuevo recurso. Después de una solicitud `POST` +* `204`: El servidor tiene una petición procesada con éxito, pero no se regresó ningún contenido. Esto es usual en una solicitud `DELETE` exitosa. +* `400`: La petición no se puede ejecutar debido a una sintaxis incorrecta. Puede suceder para cualquier tipo de solicitud. * 401: Similar al 403, pero especialmente usada al solicitar autenticación y ha fallado o aún no se ha proporcionado. Puede suceder en cualquier tipo de solicitud. -* `404`: El recurso solicitado no fue encontrado pero podría estar disponible en el futuro. Usualmente concierne a la petición `GET`. -* 500: Un mensaje de error generico, dado cuando una condición inesperada ha sido encontrada y ningun otro mensaje especifico es apropiado. +* `404`: El recurso solicitado no fue encontrado, pero podría estar disponible en el futuro. Usualmente concierne a la petición `GET`. +* 500: Un mensaje de error genérico, dado cuando una condición inesperada ha sido encontrada y ningún otro mensaje especifico es apropiado. -Para una liesta completa de codigos HTTP, mira este https://en.wikipedia.org/wiki/List_of_HTTP_status_codes[articulo de Wikipedia (en inglés)]. +Para una lista completa de códigos HTTP, mira este https://en.wikipedia.org/wiki/List_of_HTTP_status_codes[articulo de Wikipedia (en inglés)]. **** -Por lo tanto vamos a implementar la prueba funcional que verifica el acceso a el metodo `Users#show`. +Por lo tanto, vamos a implementar la prueba funcional que verifica el acceso al método `Users#show`. [source,ruby] @@ -354,7 +352,7 @@ end ---- -Entonces simplemente agrega la accion a tu controlador. Es extremadamente simple: +Entonces simplemente agrega la acción a tu controlador. Es extremadamente simple: [source,ruby] .app/controllers/api/v1/users_controller.rb @@ -381,7 +379,7 @@ DRb::DRbRemoteError: undefined method \`api_v1_user_url' for #' ---- -Este tipo de error es muy común cuando generaste tus recursos manualmente! En efecto, nos hemos olvidad por completo de *la ruta*. Asi que vamos a añadirla: +¡Este tipo de error es muy común cuando generaste tus recursos manualmente! En efecto, nos hemos olvidado por completo de *la ruta*. Así que vamos a añadirla: [source,ruby] .config/routes.rb @@ -403,7 +401,7 @@ $ rails test 4 runs, 5 assertions, 0 failures, 0 errors, 0 skips ---- -Como siempre, despues de añadir una caracteristica que nos satisface, vams a hacer un _commit_: +Como siempre, después de añadir una característica que nos satisface, vamos a hacer un _commit_: [source,bash] ---- @@ -412,9 +410,9 @@ $ git add . && git commit -m "Adds show action to the users controller" === Prueba tu recurso con cURL -Asi que finalmente tenemos un recurso para probar. Tenemos muchas soluciones para probarlo. La primera que se me viene a la mente es hacer uso de cURL, el cual esta integrado en la mayoria de distribuciones Linux. Asi que vamos a probarlo: +Así que finalmente tenemos un recurso para probar. Tenemos muchas soluciones para probarlo. La primera que se me viene a la mente es hacer uso de cURL, el cual está integrado en la mayoría de distribuciones Linux. Así que vamos a probarlo: -Pirmero inicializamos el servidor de rails en una nueva terminal. +Primero inicializamos el servidor de Rails en una nueva terminal. [source,bash] ---- $ rails s @@ -428,15 +426,15 @@ $ curl http://localhost:3000/api/v1/users/1 {"id":1,"email":"toto@toto.org", ... ---- -Encontramos el usuario que creamos con la consola de Rails en la seccion previa. Ahora tienes una entrada en el API para registro de usuarios. +Encontramos el usuario que creamos con la consola de Rails en la sección previa. Ahora tienes una entrada en el API para registro de usuarios. === Crear usuarios -Ahora que tenemos mejor entendimiento de como contruir "entry points" (puntos de entrada), es tiempo de extender nuestra API. Una de las caracteristicas mas importantes es darles a los usuarios que puedan crear un perfil en nuestra aplicación. Como siempre, vamos a escribir nuestras pruebas antes de implementar nuestro código para extener nuestro banco de pruebas. +Ahora que tenemos mejor entendimiento de como construir "entry points" (puntos de entrada), es tiempo de extender nuestra API. Una de las características más importantes es darles a los usuarios que puedan crear un perfil en nuestra aplicación. Como siempre, vamos a escribir nuestras pruebas antes de implementar nuestro código para extender nuestro banco de pruebas. -Asegura que tu directoruo de Git esta limpio y que no tienes algun archivo en _staging_. Si es asi hasles _commit_ que vamos a empezar de nuevo. +Asegura que tu directorio de Git está limpio y que no tienes algún archivo en _staging_. Si es así hazles _commit_ que vamos a empezar de nuevo. -Asiq eu vamos a iniciar por escribir nuestra prueba añadiendo una entrada para crear un usuario en el archivo `users_controller_test.rb`: +Así que vamos a iniciar por escribir nuestra prueba añadiendo una entrada para crear un usuario en el archivo `users_controller_test.rb`: [source,ruby] .test/controllers/users_controller_test.rb @@ -460,12 +458,12 @@ class Api::V1::UsersControllerTest < ActionDispatch::IntegrationTest end ---- -Es un monton de codigo. No te preocupes explicarŕe todo: +Es un montón de código. No te preocupes explicare todo: -* En el primer test revisamos la creacion de un usuario enviando una peticion POST valida. Entonces, revisamos que un usuario adiconal ahora existe en la base de datos y que el codigo HTTP de respuesta es `created` (código de estado 201) -* En el segundo test revisamos que el usuario no es creado usando una direccion de correo que ya está en uso. Entonces, revisamos que el codigo HTTP de respuesta es `unprocessable_entity` (código de estado 422) +* En el primer test revisamos la creación de un usuario enviando una petición POST valida. Entonces, revisamos que un usuario adicional ahora existe en la base de datos y que el código HTTP de respuesta es `created` (código de estado 201) +* En el segundo test revisamos que el usuario no es creado usando una dirección de correo que ya está en uso. Entonces, revisamos que el código HTTP de respuesta es `unprocessable_entity` (código de estado 422) -Hasta este punto, la prueba deberia de fallar (como esperabamos): +Hasta este punto, la prueba debería de fallar (como esperábamos): [source,bash] ---- @@ -501,7 +499,7 @@ class Api::V1::UsersController < ApplicationController end ---- -Recuerda que cada vez que agregamos una entrada en nuestra API debemos agregar esta acción en nuestra archivo `routes.rb`. +Recuerda que cada vez que agregamos una entrada en nuestra API debemos agregar esta acción en nuestro archivo `routes.rb`. [source,ruby] .config/routes.rb @@ -515,7 +513,7 @@ Rails.application.routes.draw do end ---- -Como puesdes ver, la implementacion es bastante simple. Tambien hemos añadido el metodo privado `user_params` para proteger de la asignacion masiva de atributos. Ahora nuestra prueba debería de pasar: +Como puedes ver, la implementación es bastante simple. También hemos añadido el método privado `user_params` para proteger de la asignación masiva de atributos. Ahora nuestra prueba debería de pasar: [source,bash] ---- @@ -536,7 +534,7 @@ $ git commit -am "Adds the user create endpoint" El esquema para actualizar usuarios es muy similar a la de creación. Si eres un desarrollador Rails experimentado, ya sabes las diferencias entre estas dos acciones: * La accion update (actualizar) responde a una petición PUT/PATCH. -* Unicamente un usuario conectado deberia ser capáz de actualizar su información. Esto significa que tendremos que forzar a un usuario a autenticarse. Discutiremos esto en el capítulo 5. +* Únicamente un usuario conectado debería ser capaz de actualizar su información. Esto significa que tendremos que forzar a un usuario a autenticarse. Discutiremos esto en el capítulo 5. Como siempre, empezamos escribiendo nuestra prueba: @@ -558,7 +556,7 @@ class Api::V1::UsersControllerTest < ActionDispatch::IntegrationTest end ---- -Para que la prueba se exitosa, debemos construir la accion update en el archivo `users_controller.rb` y agrgar la ruta al archivo `routes.rb`. Como puedes ver, tenemos mucho código duplicado, vamos a rediseñar nuestra prueba en el capítulo 4. Primero añadimos la accion al archivo `routes.rb`: +Para que la prueba se exitosa, debemos construir la acción update en el archivo `users_controller.rb` y agregar la ruta al archivo `routes.rb`. Como puedes ver, tenemos mucho código duplicado, vamos a rediseñar nuestra prueba en el capítulo 4. Primero añadimos la acción al archivo `routes.rb`: [source,ruby] .config/routes.rb @@ -604,7 +602,7 @@ end ---- -Todas nuestras pruebas deberian pasar: +Todas nuestras pruebas deberían pasar: [source,bash] ---- @@ -620,9 +618,9 @@ Hacemos un _commit_ ya que todo funciona: $ git commit -am "Adds update action the users controller" ---- -=== Delete the user +=== Eliminar al usuario -Hasta aquí, hemos echo un monton de acciones en el controlador del usuario con sus propias pruebas pero no hemos terminado. Solo necesitamos una cosa mas, que es la acción de destruir. Asi que vamos a crear la prueba: +Hasta aquí, hemos hecho un montón de acciones en el controlador del usuario con sus propias pruebas, pero no hemos terminado. Solo necesitamos una cosa más, que es la acción de destruir. Así que vamos a crear la prueba: [source,ruby] .test/controllers/users_controller_test.rb @@ -640,9 +638,9 @@ class Api::V1::UsersControllerTest < ActionDispatch::IntegrationTest end ---- -Como puedes ver, la prueba es muy simple. Unicamente respondemos con estado *204* que significa `No Content` (Sin contenido). Tambien podriamos devolver un código de estado *200*, pero encuentro mas natural la respuesta `No Content` (Sin contenido) en este caso porque eliminamos un recurso y una respuesta exitosa podria ser bastante. +Como puedes ver, la prueba es muy simple. Únicamente respondemos con estado *204* que significa `No Content` (Sin contenido). También podríamos devolver un código de estado *200*, pero encuentro más natural la respuesta `No Content` (Sin contenido) en este caso porque eliminamos un recurso y una respuesta exitosa podría ser bastante. -La implementación de la acción de destruccion es muy simple: +La implementación de la acción de destrucción es muy simple: [source,ruby] .app/controllers/api/v1/users_controller.rb @@ -661,7 +659,7 @@ class Api::V1::UsersController < ApplicationController end ---- -No olvides añadir la accion `destroy` en el archivo `routes.rb`: +No olvides añadir la acción `destroy` en el archivo `routes.rb`: [source,ruby] .config/routes.rb @@ -673,7 +671,7 @@ Rails.application.routes.draw do end ---- -Las pruebas deberian de pasar si todo es correcto: +Las pruebas deberían de pasar si todo es correcto: [source,bash] ---- @@ -682,14 +680,14 @@ $ rails test 9 runs, 13 assertions, 0 failures, 0 errors, 0 skips ---- -Recuerda que despues de hacer algunos cambios en nuestro código, es buena practica hacerles _commit_ asi podremos tener un historial segmentado correctamente. +Recuerda que después de hacer algunos cambios en nuestro código, es buena práctica hacerles _commit_ así podremos tener un historial segmentado correctamente. [source,bash] ---- $ git commit -am "Adds destroy action to the users controller" ---- -Y a medida que lleamos al final de nuestro capítulo, es tiempo de aplicar nuestra modificaciones a la rama master haciendo un _merge_: +Y a medida que llegamos al final de nuestro capítulo, es tiempo de aplicar nuestra modificaciones a la rama master haciendo un _merge_: [source,bash] ---- @@ -699,4 +697,4 @@ $ git merge chapter03 == Conclusión -Oh, ahi tienes! Bien echo! Se que probablemente fue un largo tiempo, pero no te rindas! Asegurate de entender cada pieza del código, las cosas mejorarán, en el siguiente capítulo, vamos a rediseñar nuestras pruebas para hace nusetro código mas legible y mantenible. Entonces quedate conmigo! +¡Oh, ahí tienes!, ¡Bien echo! ¡Se que probablemente fue un largo tiempo, pero no te rindas! Asegúrate de entender cada pieza del código, las cosas mejorarán, en el siguiente capítulo, vamos a rediseñar nuestras pruebas para hace nuestro código más legible y mantenible. ¡Entonces quédate conmigo! diff --git a/rails6/es/chapter04-athentification.adoc b/rails6/es/chapter04-athentification.adoc index 2b66b82..fa71d72 100644 --- a/rails6/es/chapter04-athentification.adoc +++ b/rails6/es/chapter04-athentification.adoc @@ -3,7 +3,7 @@ Ha sido un largo tiempo desde que iniciamos. Espero que te guste este viaje tanto como a mí. -En el capítulo anterior configuramos las entradas de recursos para los usuarios. Si te saltaste este capítulo ó si no entendiste todo, te recomiendo encarecidamente que lo mires. Éste cubre las primeras bases de las pruebas y es una introducción a respuestas JSON. +En el capítulo anterior configuramos las entradas de recursos para los usuarios. Si te saltaste este capítulo o si no entendiste todo, te recomiendo encarecidamente que lo mires. Éste cubre las primeras bases de las pruebas y es una introducción a respuestas JSON. Puedes clonar el proyecto hasta este punto: @@ -12,7 +12,7 @@ Puedes clonar el proyecto hasta este punto: $ git checkout tags/checkpoint_chapter04 ---- -En este capítulo las cosas se pondrán muy intersantes porque vamos a configurar el mecanismo de autenticación. En mi opinión es uno de los capítulos mas interesantes. Introduciremos un montón de terminos nuevos y terminarás con un simple pero poderoso sistema de autenticación. No sientas panico vamos por ello. +En este capítulo las cosas se pondrán muy interesantes porque vamos a configurar el mecanismo de autenticación. En mi opinión es uno de los capítulos más interesantes. Introduciremos un montón de términos nuevos y terminarás con un simple pero poderoso sistema de autenticación. No sientas pánico vamos por ello. La primera cosa es que primero (y como es usual cuando iniciamos un nuevo capítulo) vamos a crear una nueva rama: @@ -23,7 +23,7 @@ $ git checkout -b chapter04 == Sesion sin estado -Antes de que hagamos algo, algo debe estar claro: *una API no maneja sesiones*. Si no tienes experiencia construyendo este tipo de aplicaciones puede sonar un poco loco pero quedate conmigo. Un API puede ser sin estado lo cual significa por definición _es una que provee una respuesa despues de tú peticion, y luego no requiere más atención_. Lo cual significa que un estado previo o un estado futuro no es requerido para que esl sistema trabaje. +Antes de que hagamos algo, algo debe estar claro: *una API no maneja sesiones*. Si no tienes experiencia construyendo este tipo de aplicaciones puede sonar un poco loco pero quédate conmigo. Un API puede ser sin estado lo cual significa por definición _es una que provee una respuesta después de tú petición, y luego no requiere más atención_. Lo cual significa que un estado previo o un estado futuro no es requerido para que el sistema trabaje. El flujo para autenticar al usuario mediante una API es muy simple: @@ -31,22 +31,22 @@ El flujo para autenticar al usuario mediante una API es muy simple: . El server regresa el recurso `user` junto con su correspondiente token de autenticación . Para cada página que requiere autenticación el cliente tiene que enviar el `token de autenticación` -Por supuesto estos no son los únicos 3 pasos a seguir, y en el paso 2 debería pensar, bien yo realmente necesito responder con la información del usuario o solo el `token de autenticación`? Yo podría decir que eso realmente depende de tí, pero a mí me gusta regresar el usuario completo, de esta forma puedo mapearlo de inmediato en mi cliente y guardar otra posible solicitud que haya sido echa. +Por supuesto estos no son los únicos 3 pasos a seguir, y en el paso 2 debería pensar, bien yo realmente ¿necesito responder con la información del usuario o solo el `token de autenticación`? Yo podría decir que eso realmente depende de tí, pero a mí me gusta regresar el usuario completo, de esta forma puedo mapearlo de inmediato en mi cliente y guardar otra posible solicitud que haya sido echa. -En esta sección y la siguiente vamos a enfocarnos en construir un controllador de sesiones junto a sus acciones correspondientes. Vamos entonces a completar el flujo de solicitudes agregando los accesos de autorización necesarios. +En esta sección y la siguiente vamos a enfocarnos en construir un controlador de sesiones junto a sus acciones correspondientes. Vamos entonces a completar el flujo de solicitudes agregando los accesos de autorización necesarios. === Presentación de JWT -Cuando nos acercamos a los tokens de autenticación, tenemos un estandar: el JSON Web Token (JWT). +Cuando nos acercamos a los tokens de autenticación, tenemos un estándar: el JSON Web Token (JWT). -> JWT es un estandar abierto definido en RFC 75191. Este permite el intercambio seguro de tokens entre varias partes. - https://wikipedia.org/wiki/JSON_Web_Token_Web_Token[Wikipedia] +> JWT es un estándar abierto definido en RFC 75191. Este permite el intercambio seguro de tokens entre varias partes. - https://wikipedia.org/wiki/JSON_Web_Token_Web_Token[Wikipedia] En general un token JWT se compone de tres partes: - un *header* estructurado en JSON contiene por ejemplo la fecha de validación del token. - un *payload* estructurado en JSON puede contener *cualquier dato*. En nuestro caso, contiene el indetificador del usuario "conectado". -- un *signature* que nos permite verificar que el token fue encriptado por nuestra aplicación y es por lo danto válido. +- un *signature* que nos permite verificar que el token fue encriptado por nuestra aplicación y es por lo danto válido. Estas tres partes son cada una codificadas en base64 y entonces concatenadas usando puntos (`.`). Lo cual nos da algo como: @@ -57,7 +57,7 @@ eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4 Una ves decodificado, este token nos da la siguiente información: -.La cabecera del tojen JWT +.La cabecera del token JWT [source,json] ---- { "alg": "HS256", "typ": "JWT" } @@ -69,29 +69,29 @@ Una ves decodificado, este token nos da la siguiente información: { "sub": "1234567890", "name": "John Doe", "iat": 1516239022 } ---- -NOTE: Para mas información sobre tokens JWT te invito a visiar https://jwt.io[jwt.io] +NOTE: Para más información sobre tokens JWT te invito a visitar https://jwt.io[jwt.io] -Esto tien muchas ventajas justo como enviar información en payload de tokens. Por ejemplo, podemos elegir integrar información del usuario en el _payload_. +Esto tiene muchas ventajas justo como enviar información en payload de tokens. Por ejemplo, podemos elegir integrar información del usuario en el _payload_. === Configurando el token de autenticación -El estandar JWT tiene muchas implementaciones en varios lenguajes y librerias. Por supuesto, hay una gema de Ruby en este tema: https://github.com/jwt/ruby-jwt[ruby-jwt]. +El estándar JWT tiene muchas implementaciones en varios lenguajes y librerías. Por supuesto, hay una gema de Ruby en este tema: https://github.com/jwt/ruby-jwt[ruby-jwt]. -Asi que vamos a comenzar instalandola: +Asi que vamos a comenzar instalándola: [source,bash] ---- $ bundle add jwt ---- -Una vez completada la siguiente linea es añadida a tu _Gemfile_: +Una vez completada la siguiente línea es añadida a tu _Gemfile_: [source,ruby] ---- gem "jwt", "~> 2.2" ---- -La librería es muy simple. Hay dos metodos: `JWT.encode` y `JWT.decode`. Vamos a abrir una terminal con `console rails` y a correr algunas pruebas: +La librería es muy simple. Hay dos métodos: `JWT.encode` y `JWT.decode`. Vamos a abrir una terminal con `console rails` y a correr algunas pruebas: [source,ruby] ---- @@ -100,9 +100,9 @@ La librería es muy simple. Hay dos metodos: `JWT.encode` y `JWT.decode`. Vamos => [{"message"=>"Hello World"}, {"alg"=>"HS256"}] ---- -En la primera linea codificamos un _payload_ con la llave secreta `my_secret_key`. Asi obtenemos un token que podemos decodificar de manera simple. La segunda linea decodifica el token y vemos que podemos encontrar sin dilema nuestro _payload_. +En la primera línea codificamos un _payload_ con la llave secreta `my_secret_key`. así obtenemos un token que podemos decodificar de manera simple. La segunda línea decodifica el token y vemos que podemos encontrar sin dilema nuestro _payload_. -Vamos a incluir toda la logica en una clase `JsonWebToken` en un nuevo archivo localizado en `lib/`. Esto nos permite evitar el código duplicado. Esta clase justamente codificará y decodificará los tokens JWT. Así que aqui esta la implementación. +Vamos a incluir toda la lógica en una clase `JsonWebToken` en un nuevo archivo localizado en `lib/`. Esto nos permite evitar el código duplicado. Esta clase justamente codificará y decodificará los tokens JWT. Así que aquí está la implementación. .lib/json_web_token.rb [source,ruby] @@ -122,9 +122,9 @@ class JsonWebToken end ---- -Yo se que es un monton de código pero lo revisaremos juntos. +Yo se que es un montón de código pero lo revisaremos juntos. -- el método `JsonWebToken.encode` se encarga de codificar el _payload_ añadiendo una fecha de expiración de 24 horas por defecto. Ademas usamos la misma llave de encriptación que viene configurada con Rails. +- el método `JsonWebToken.encode` se encarga de codificar el _payload_ añadiendo una fecha de expiración de 24 horas por defecto. Además usamos la misma llave de encriptación que viene configurada con Rails. - el método `JsonWebToken.decode` decodifica el token JWT y obtiene el _payload_. Entonces usamos la clase https://api.rubyonrails.org/classes/ActiveSupport/HashWithIndifferentAccess.html[`HashWithIndifferentAccess`] proveída por Rails la cual nos permite recuperar un valor de un `Hash` con un `Symbol` ó `String`. Ahí tienes. Para cargar el archivo en tú aplicación, necesitas especificar el directorio `lib` en la lista de _autoload de Ruby on rails. Para hacerlo, agrega la siguiente configuración al archivo `application.rb`: @@ -150,7 +150,7 @@ $ git add . && git commit -m "Setup JWT gem" === Controlador de Token -Tenemos sin embargo que configurar el sistema para generar un token JWT. Es ahora tiempo de crear una ruta que generará este token. Las acciones que implementaremos seran administradas como servicios _RESTful_: la conexión sera gestionada por una peticion POST a la acción `create`. +Tenemos sin embargo que configurar el sistema para generar un token JWT. Es ahora tiempo de crear una ruta que generará este token. Las acciones que implementaremos serán administradas como servicios _RESTful_: la conexión será gestionada por una petición POST a la acción `create`. Para empezar, iniciaremos creando el controlador y el método `create` en el _namespace_ `/api/v1`. Con Rails, una orden es suficiente: @@ -176,12 +176,12 @@ end ---- -Vamos a construir pruebas funcionales antes de ir mas lejos. El comportamiento deseado es el siguiente: +Vamos a construir pruebas funcionales antes de ir más lejos. El comportamiento deseado es el siguiente: -- Yo recibo un token si envio un email valido junto con el password +- Yo recibo un token si envío un email valido junto con el password - de otro modo el server responde un `forbidden` -Las pruebas por lo tanto se materilizan de la siguiente forma: +Las pruebas por lo tanto se materializan de la siguiente forma: .test/controllers/api/v1/tokens_controller_test.rb [source,ruby] @@ -208,7 +208,7 @@ class Api::V1::TokensControllerTest < ActionDispatch::IntegrationTest end ---- -Te estarás preguntando: "pero como puedes saber la contraseña del usuario?". Simplemente usa el método `BCrypt::Password.create` en los _fixtures_ de `users`: +Te estarás preguntando: "¿pero como puedes saber la contraseña del usuario?". Simplemente usa el método `BCrypt::Password.create` en los _fixtures_ de `users`: .test/fixtures/users.yml [source,yaml] @@ -235,7 +235,7 @@ Failure: Expected response to be a <401: unauthorized>, but was a <204: No Content> ---- -Es normal. Ahora es tiempo de implementar la logica para crear el token JWT. Es muy sencillo. +Es normal. Ahora es tiempo de implementar la lógica para crear el token JWT. Es muy sencillo. .app/controllers/api/v1/tokens_controller.rb [source,ruby] @@ -262,14 +262,14 @@ class Api::V1::TokensController < ApplicationController end ---- -Es un montón de codigo pero es muy simple: +Es un montón de código pero es muy simple: -. Siempre filtramos los parametros con el metodo `user_params`. -. Recuperamos el usuario con el método `User.find_by_email` (que es un metodo "mágico" de _Active Record_ mientras el campo `email` este presente en la base de datos) y recuperamos el usuario -. Usamos el método `User#authenticate` (el cual existe gracias a la gema `bcrypt`) con la contraseña como un parameto. Bcrypt hará un _hash_ de la contraseña y verifica si coincide con el atributo `password_digest`. La funcion regresa `true` si todo salio bien, `false` si no. +. Siempre filtramos los parámetros con el método `user_params`. +. Recuperamos el usuario con el método `User.find_by_email` (que es un método "mágico" de _Active Record_ mientras el campo `email` esté presente en la base de datos) y recuperamos el usuario +. Usamos el método `User#authenticate` (el cual existe gracias a la gema `bcrypt`) con la contraseña como un parámetro. Bcrypt hará un _hash_ de la contraseña y verifica si coincide con el atributo `password_digest`. La función regresa `true` si todo salió bien, `false` si no. . Si la contraseña corresponde al _hash_, un JSON conteniendo el _token_ generado con la clase `JsonWebToken` es devuelto. De otro modo, una respuesta vacía es devuelta con una cabecera `unauthorized` -Estas hasta aquí? No te preocupes, esta terminado! Ahora tus pruebas deberían pasar. +¿Estas hasta aquí? ¡No te preocupes, esta terminado! Ahora tus pruebas deberían pasar. [source,bash] ---- @@ -281,7 +281,7 @@ Finished in 0.226196s, 48.6304 runs/s, 70.7351 assertions/s. 11 runs, 16 assertions, 0 failures, 0 errors, 0 skips ---- -Muy bien! Es tiempo de hacer un commit que contendra todos nuestros cambios: +¡Muy bien! Es tiempo de hacer un commit que contendrá todos nuestros cambios: [source,bash] ---- @@ -295,11 +295,11 @@ Entonces ya implementamos la siguiente lógica: la API retorna el token de auten Pero ahora implementaremos la siguiente lógica: encontraremos el usuario correspondiente del token de autenticación proporcionado en la cabecera HTTP. Necesitamos hacerlo cada vez que este cliente solicite un `entry point` que requiera permisos. -Usaremos la cabecera HTTP `Authorization` que a menudo es usada para este proposito. Tambien podemos usar un parametro GET llamado `apiKey` pero prefiero usar una cabecera HTTP porque da contexto a la petición sin contaminar la URL con parametros adicionales. +Usaremos la cabecera HTTP `Authorization` que a menudo es usada para este propósito. También podemos usar un parámetro GET llamado `apiKey` pero prefiero usar una cabecera HTTP porque da contexto a la petición sin contaminar la URL con parámetros adicionales. -Por lo tanto crearemos un método `current_user` para satisfacer nuestras necesidades. Este encontrará el usuario gracias a su token de autenticación que es enviado en cada petición. +Por lo tanto, crearemos un método `current_user` para satisfacer nuestras necesidades. Este encontrará el usuario gracias a su token de autenticación que es enviado en cada petición. -Cuando se trata de autenticación, me gusta añadir todos los metodos asociados en un archivo separado. Entonces simplemente incluimos el archivo `ApplicationController`. De este modo, es muy facil para probar de forma aislada. Vamos a crear el archivo en el directorio `controllers/concerns` con un metodo `current_user` que implementaremos despues: +Cuando se trata de autenticación, me gusta añadir todos los métodos asociados en un archivo separado. Entonces simplemente incluimos el archivo `ApplicationController`. De este modo, es muy fácil para probar de forma aislada. Vamos a crear el archivo en el directorio `controllers/concerns` con un método `current_user` que implementaremos después: [source,ruby] .app/controllers/concerns/authenticable.rb @@ -344,7 +344,7 @@ class AuthenticableTest < ActionDispatch::IntegrationTest end ---- -Te estaras preguntando, "De donde viene el controlador `MockController`?", De echo, éste es un _Mock_, por ejemplo una clase que imita el comportamiento de otra para probar un comportamiento +Te estarás preguntando, "¿De donde viene el controlador `MockController`?", De hecho, éste es un _Mock_, por ejemplo una clase que imita el comportamiento de otra para probar un comportamiento Podemos definir la clase `MockController` justo sobre nuestra prueba: @@ -391,7 +391,7 @@ class AuthenticableTest < ActionDispatch::IntegrationTest end ---- -Nuestra prueba debería fallar. Asi que vamos a implementar el código para que ésta pase: +Nuestra prueba debería fallar. Así que vamos a implementar el código para que ésta pase: [source,ruby] .app/controllers/concerns/authenticable.rb @@ -443,11 +443,11 @@ $ git add . && git commit -m "Adds authenticable module for managing authenticat La autorización juega un papel importante en la construcción de aplicaciones porque nos ayuda a definir que usuario tiene permisos para continuar. -Tenemos una ruta para actualizar el usuario pero es un problema: cualquiera puede actualizar cualquier usuario. En esta seccion, vamos a implementar un metodo que requerirá al usuario estar logueado para prevenir accesos no autorizados. +Tenemos una ruta para actualizar el usuario, pero hay un problema: cualquiera puede actualizar cualquier usuario. En esta sección, vamos a implementar un método que requerirá al usuario estar logueado para prevenir accesos no autorizados. === Acciones de autorización -Es tiempo ahora de actualizar nuestro archivo `users_controller.rb` para negar el acceso a ciertas acciones. Vamos tambien a implementar el método `current_user` en las acciones `update` y `destroy` para asegurarnos que el usuario que esta logueado solo podrá actualizar sus datos y puede unicamente borrar (y solo) su cuenta. +Es tiempo ahora de actualizar nuestro archivo `users_controller.rb` para negar el acceso a ciertas acciones. Vamos también a implementar el método `current_user` en las acciones `update` y `destroy` para asegurarnos que el usuario que esta logueado solo podrá actualizar sus datos y puede únicamente borrar (y solo) su cuenta. Por lo tanto dividimos nuestra prueba en dos pruebas _should update user_ y _should destroy user_. @@ -474,7 +474,7 @@ class Api::V1::UsersControllerTest < ActionDispatch::IntegrationTest end ---- -Puedes ver ahora que tenemos que añadir una cabecera _Authorization_ para la acción de modidficar usuarios. De lo contrario queremos recibir una respuesta _forbidden_. +Puedes ver ahora que tenemos que añadir una cabecera _Authorization_ para la acción de modificar usuarios. De lo contrario queremos recibir una respuesta _forbidden_. Podemos pensar de forma similar para la prueba _should forbid destroy user_: @@ -538,7 +538,7 @@ class Api::V1::UsersController < ApplicationController end ---- -Ahí tienes! La implementación es realmente simple. Es por lo tanto tiempo de hacer un _commit_: +¡Ahí tienes! La implementación es realmente simple. Es por lo tanto tiempo de hacer un _commit_: [source,bash] ---- @@ -549,6 +549,6 @@ $ git merge chapter04 == Conclusión -Yeah! lo hiciste! tienes medio camino terminado! Manten este buen trabajo. Éste capítulo fue largo y dificil pero es un gran paso a seguir para implementar un mecanismo solido para manipular autenticación de usuarios. Incluso logramos tocar la superficie para implemenatar reglas simples de autenticación. +¡Yeah!, ¡lo hiciste! tienes medio camino terminado! Mantén este buen trabajo. Este capítulo fue largo y difícil pero es un gran paso a seguir para implementar un mecanismo sólido para manipular autenticación de usuarios. Incluso logramos tocar la superficie para implementar reglas simples de autenticación. -En el proximo capítulo nos enfocaremos en la personalizacion de las salidas JSON para el usuario con la gema https://github.com/Netflix/fast_jsonapi[fast_jsonapi] y añadiremos un modelo `product` a la ecuación dando al usuario la habilidad para crear un producto y publicarlo para su venta. +En el próximo capítulo nos enfocaremos en la personalización de las salidas JSON para el usuario con la gema https://github.com/Netflix/fast_jsonapi[fast_jsonapi] y añadiremos un modelo `product` a la ecuación dando al usuario la habilidad para crear un producto y publicarlo para su venta. diff --git a/rails6/es/chapter05-user-products.adoc b/rails6/es/chapter05-user-products.adoc index c4d92ad..9191f30 100644 --- a/rails6/es/chapter05-user-products.adoc +++ b/rails6/es/chapter05-user-products.adoc @@ -1,15 +1,15 @@ [#chapter05-user-products] = Productos de usuario -En el capítulo anterior, implementamos el mecanismo de autenticación que usaremos a travez de la aplicación. +En el capítulo anterior, implementamos el mecanismo de autenticación que usaremos a través de la aplicación. -Por el momento tenemos una implementación del odelo `User` pero el momento de la verdad ha llgado. Vamos a personalizar la salida JSON añadir un segundo recurso: los productos del usuario. Estos son los elementos que el usuario va a comprar en la aplicación y por lo tanto enlazaremos directamente. +Por el momento tenemos una implementación del modelo `User` pero el momento de la verdad ha llegado. Vamos a personalizar la salida JSON añadir un segundo recurso: los productos del usuario. Estos son los elementos que el usuario va a comprar en la aplicación y por lo tanto enlazaremos directamente. -Si estas familiriarizado con Rails, ya sabes de que estoy hablando. Pero para aquellos que no lo saben, vamos a asociar el modelo `User` con el modelo `Product` usando los metodos de _Active Record_ `has_many` y `belongs_to` +Si estas familiarizado con Rails, ya sabes de que estoy hablando. Pero para aquellos que no lo saben, vamos a asociar el modelo `User` con el modelo `Product` usando los metodos de _Active Record_ `has_many` y `belongs_to` En este capítulo vamos a: -* contruir el modelo `Product` desde cero +* construir el modelo `Product` desde cero * asociarlo con el usuario * crear las entradas necesarias asi cualquier cliente puede acceder a la información. @@ -20,7 +20,7 @@ Puedes clonar el proyecto hasta este punto: $ git checkout tags/checkpoint_chapter05 ---- -Antes que iniciemos y como es usual cuando iniciamos con nuevas caracteristicas necesitaremos crear una nueva rama: +Antes que iniciemos y como es usual cuando iniciamos con nuevas características necesitaremos crear una nueva rama: [source,bash] ---- @@ -29,9 +29,9 @@ $ git checkout -b chapter05 == El modelo producto -Priermo crearemos un modelo `Product`. Entonces añadiremos validaciones y finalmente lo asociamos con el modelo `User. Como el modelo `User`, el modelo `Product` será completamente probado y sera automaticamente eliminado si el usuario es eliminado. +Primero crearemos un modelo `Product`. Entonces añadiremos validaciones y finalmente lo asociamos con el modelo `User. Como el modelo `User`, el modelo `Product` será completamente probado y será automáticamente eliminado si el usuario es eliminado. -=== Los funamentos del producto +=== Los fundamentos del producto La plantilla `Product` necesitara varios campos: @@ -54,9 +54,9 @@ Running via Spring preloader in process 1476 create test/fixtures/products.yml ---- -NOTE: Usamos el el tipo `belongs_to` para el attributo `user`. Este es un atajo que creará una columna `user_id` de tipo `int` y entonces añade una llave foranea a el campo `users.id`. En adición, `user_id` tambien sera definiada como un `index` (índice). Esta es una buena práctica para la asociación de llaves porque esto optimiza las consultas de la base de datos. No es obligatorio, pero es altamente recomendado. +NOTE: Usamos el tipo `belongs_to` para el atributo `user`. Este es un atajo que creará una columna `user_id` de tipo `int` y entonces añade una llave foránea a el campo `users.id`. En adición, `user_id` también será definido como un `index` (índice). Esta es una buena práctica para la asociación de llaves porque esto optimiza las consultas de la base de datos. No es obligatorio, pero es altamente recomendado. -El archivo de migracion deberia lucir asi: +El archivo de migración debería lucir así: [source,ruby] .db/migrate/20190608205942_create_products.rb @@ -75,7 +75,7 @@ class CreateProducts < ActiveRecord::Migration[6.0] end ---- -Ahora solo tenemos que inciar la migracion: +Ahora solo tenemos que iniciar la migración: [source,bash] ---- @@ -98,11 +98,11 @@ rails test test/controllers/api/v1/users_controller_test.rb:43 Seguramente dirás: -> Que?! Pero no he tocado los usuarios!. +> ¿Que?, ¡Pero no he tocado los usuarios! -Lo que he visto en el código de otros desarrolladores, cuando ellos trabajan con asociaciones, es que se olvidan de la destrucción de dependencias entre modelos. Lo que digo con esto es que si un usuario es eliminado, tambien lo deberian de ser los productos del usuario. +Lo que he visto en el código de otros desarrolladores, cuando ellos trabajan con asociaciones, es que se olvidan de la destrucción de dependencias entre modelos. Lo que digo con esto es que si un usuario es eliminado, también lo deberían de ser los productos del usuario. -Necesitamos un suario con uno de los productos para probar esta interacción entre modelos. Entones eliminaremos este usuario esperando que los productos desaparezcan con él. Rails ya tiene generado esto por nosotros. Echa un vistaso a el _fixture_ de los productos: +Necesitamos un usuario con uno de los productos para probar esta interacción entre modelos. Entones eliminaremos este usuario esperando que los productos desaparezcan con él. Rails ya tiene generado esto por nosotros. Echa un vistazo a el _fixture_ de los productos: .test/fixtures/products.yml @@ -116,9 +116,9 @@ one: # ... ---- -Puedes ver que este _fixture_ no usa el atributo `user_id` pero si `user`. Esto significa que el producto `one` tendrá un atributo `user_id` correspondiente a el ID de usuario `one`. +Puedes ver que este _fixture_ no usa el atributo `user_id` pero si `user`. Esto significa que el producto `one` tendrá un atributo `user_id` correspondiente al ID de usuario `one`. -Es por lo tanto necesario especificar un borrado en cascada a fin de que sea eliminado el producto `onde` cuando el usuario `one` es eliminado. Vamos empezar con la prueba unitaria: +Es por lo tanto necesario especificar un borrado en cascada a fin de que sea eliminado el producto `one` cuando el usuario `one` es eliminado. Vamos empezar con la prueba unitaria: .test/models/user_test.rb @@ -135,7 +135,7 @@ class UserTest < ActiveSupport::TestCase end ---- -Justamente tienes que modificar el modelo `User` y especificar la relacion `has_many` con la opción `depend: :destroy`. Veremos mas tarde que hace este método con mas detalle. +Justamente tienes que modificar el modelo `User` y especificar la relación `has_many` con la opción `depend: :destroy`. Veremos más tarde que hace este método con mas detalle. .app/models/user.rb [source,ruby] @@ -158,9 +158,9 @@ $ git add . && git commit -m "Generate product model" === Validaciones del producto -Las validaciones son una parte importante cuando construimos cualquier típo de aplicación. Esto evitará que cualquier dato basura sea guardado en la base de datos. En el producto tenemos que asegurarnos que por ejemplo el precio es un `number` (número) y que no es negativo. +Las validaciones son una parte importante cuando construimos cualquier tipo de aplicación. Esto evitará que cualquier dato basura sea guardado en la base de datos. En el producto tenemos que asegurarnos que por ejemplo el precio es un `number` (número) y que no es negativo. -Tambien una cosa importante sobre la validación es validar que cada producto tiene un usuario. En este caso necesitamos validar la precencia de el `user_id`. Puedes ver que estoy hablando en siguiente fragmento de código. +También una cosa importante sobre la validación es validar que cada producto tiene un usuario. En este caso necesitamos validar la presencia del `user_id`. Puedes ver que estoy hablando en siguiente fragmento de código. [source,ruby] .test/models/product_test.rb @@ -187,7 +187,7 @@ class Product < ApplicationRecord end ---- -La prueba ahora esta en verde: +La prueba ahora está en verde: [source,bash] ---- @@ -195,7 +195,7 @@ $ rake test ................ ---- -Tenemos un monton de código de buena calidad. Hagamos un commir y sigamos moviendonos: +Tenemos un montón de código de buena calidad. Hagamos un commit y sigamos moviéndonos: [source,bash] ---- @@ -205,9 +205,9 @@ $ git commit -am "Adds some validations to products" == Endpoints de productos -Ahora es tiempo de empezar a construir lo endpoints de los productos. Por ahora solo construiremos las cinco acciones REST. En el siguiente capítulo vamos a personalizar la salida JSON implementando la gema https://github.com/Netflix/fast_jsonapi[fast_jsonapi]. +Ahora es tiempo de empezar a construir los endpoints de los productos. Por ahora solo construiremos las cinco acciones REST. En el siguiente capítulo vamos a personalizar la salida JSON implementando la gema https://github.com/Netflix/fast_jsonapi[fast_jsonapi]. -Primero necesitamos crear el controlador `products_controller`, y facilmente podemos lograrlo con el comando: +Primero necesitamos crear el controlador `products_controller`, y fácilmente podemos lograrlo con el comando: [source,bash] ---- @@ -217,13 +217,13 @@ $ rails generate controller api::v1::products create test/controllers/api/v1/products_controller_test.rb ---- -El comando anterior generará un monton de archivos que nos permitirán empezar a trabajar rápidamente. Lo que quiero decir con esto es ya generará el controlador y el archivo de prueba con un _scoped_ (alcanse) hacia la version 1 del API. +El comando anterior generará un montón de archivos que nos permitirán empezar a trabajar rápidamente. Lo que quiero decir con esto es ya generará el controlador y el archivo de prueba con un _scoped_ (alcanse) hacia la versión 1 del API. -Como calentamiento iniciaremos bien y facil contrullendo la acción `show` para el producto. +Como calentamiento iniciaremos bien y fácil construyendo la acción `show` para el producto. === Acción show para productos -Como es usual iniciaremo por añador algunas especificaciones para la acción `show` dpara el producto en su controlador. La estrategia aqui es muy simple: justamente necesitamos crear un único producto y asegurar que la respuesta desde el server es la que esperamos. +Como es usual iniciaremos por añadir algunas especificaciones para la acción `show` para el producto en su controlador. La estrategia aquí es muy simple: justamente necesitamos crear un único producto y asegurar que la respuesta desde el server es la que esperamos. [source,ruby] .test/controllers/api/v1/products_controller_test.rb @@ -244,7 +244,7 @@ class Api::V1::ProductsControllerTest < ActionDispatch::IntegrationTest end ---- -Entonces añadimos el codigo que hará pasar las pruebas: +Entonces añadimos el código que hará pasar las pruebas: [source,ruby] .app/controllers/api/v1/products_controller.rb @@ -256,7 +256,7 @@ class Api::V1::ProductsController < ApplicationController end ---- -Espera! Aun no corras las pruebas. Recuerda que necesitamos añadir el recuro al archivo `routes.rb`: +¡Espera! Aun no corras las pruebas. Recuerda que necesitamos añadir el recuro al archivo `routes.rb`: [source,ruby] .config/routes.rb @@ -272,7 +272,7 @@ Rails.application.routes.draw do end ---- -Ahora nos aseguramos que las pruebas estan bien y en verde: +Ahora nos aseguramos que las pruebas están bien y en verde: [source,bash] ---- @@ -280,11 +280,11 @@ $ rake test ................. ---- -Como puedes notar ahora las especificaciones e implementación son muy sencillas. En realidad se comportan igual que el usuario. +Como puedes notar ahora las especificaciones e implementación son muy sencillas. En realidad, se comportan igual que el usuario. === Listado de productos -Ahora es tiempo de devolver una lista de productos (los cuales seran mostrados como catálogo de productos de la tieda). Este endpoint debe ser accesible sin credenciales. Significa que no requerimos que el usuario este logueado para acceder a la información. Como es usual empezaremos escribiendo algunas pruebas: +Ahora es tiempo de devolver una lista de productos (los cuales serán mostrados como catálogo de productos de la tienda). Este endpoint debe ser accesible sin credenciales. Significa que no requerimos que el usuario este logueado para acceder a la información. Como es usual empezaremos escribiendo algunas pruebas: [source,ruby] .test/controllers/api/v1/products_controller_test.rb @@ -310,7 +310,7 @@ class Api::V1::ProductsControllerTest < ActionDispatch::IntegrationTest end ---- -Vamos a la implementación, la cual por ahora esta siendo un metodo `index` simple: +Vamos a la implementación, la cual por ahora está siendo un método `index` simple: [source,ruby] .app/controllers/api/v1/products_controller.rb @@ -338,7 +338,7 @@ Rails.application.routes.draw do end ---- -Terminamos por ahora con el endopint al producto público. En la siguiente seccion nos enfocaremos en la contrucción de las acciones solicitando un usuario logueado para acceder a ellos. Dicho esto haremos commit de estos cambios y continuamos. +Terminamos por ahora con el endopint al producto público. En la siguiente sección nos enfocaremos en la construcción de las acciones solicitando un usuario logueado para acceder a ellos. Dicho esto, haremos commit de estos cambios y continuamos. [source,bash] ---- @@ -347,9 +347,9 @@ $ git add . && git commit -m "Finishes modeling the product model along with use === Creando productos -Crear productos es un poco mas complejo porque necesitaremos una configuración adicional. La estratecia que seguiremos es asginar el producto creado al usuario que petenece al token JWT proporcionado en la cabecera HTTP `Authorization`. +Crear productos es un poco más complejo porque necesitaremos una configuración adicional. La estrategia que seguiremos es asignar el producto creado al usuario que pertenece al token JWT proporcionado en la cabecera HTTP `Authorization`. -Asi que inciamos ocn el archivo `products_controller_test.rb`: +Así que iniciamos con el archivo `products_controller_test.rb`: [source,ruby] .test/controllers/api/v1/products_controller_test.rb @@ -379,14 +379,11 @@ class Api::V1::ProductsControllerTest < ActionDispatch::IntegrationTest end ---- -Wow! Añadimos un montón de código. Si recuerdas la sección anterior, las pruebas son muy similares que las de la creacion de usuarios. Excepto por algunos cambios menores. +¡Wow! Añadimos un montón de código. Si recuerdas la sección anterior, las pruebas son muy similares que las de la creación de usuarios. Excepto por algunos cambios menores. -De esta forma, podemos ver al usuario y la creación del producto asiciado con el. Pero espera! Hay algo mejor. - -Si adoptamos este enfoque, podemos incrementear el alcance de nuestro mecanismo de autenticación. Realmente construimos la lógica para obtener al usuario logueado desde la cabecera `Authorization` y asignarele un método. Es por lo tanto bastante facil de configurar simplemente añadiendo la cabecera de autorización a la solicitud y recuperando el usuario desde eso. Asi que vamos a hacerlo. -?????????????????????????????????????????????? -If we adopt this approach, we can increase the scope of our authorization mechanism. We actually built the logic to get logged user from the header `Authorization` and assigned him a method `current_user`. It is therefore quite easy to set up by simply adding the authorization header to the request and retrieving the user from it. So let's do it: +De esta forma, podemos ver al usuario y la creación del producto asociado con el. Pero espera! Hay algo mejor. +Si adoptamos este enfoque, podemos incrementar el alcance de nuestro mecanismo de autenticación. Realmente construimos la lógica para obtener al usuario logueado desde la cabecera `Authorization` y asignarle un método `current_user`. Es por lo tanto bastante fácil de configurar simplemente añadiendo la cabecera de autorización a la solicitud y recuperando el usuario desde ahí. Entonces hagamoslo. [source,ruby] .app/controllers/api/v1/products_controller.rb @@ -412,7 +409,7 @@ class Api::V1::ProductsController < ApplicationController end ---- -Como puedes ver, protejemos la acción `create` con el método `check_login`. También creamos al producto por asociación con el usuario. Yo agregué este método tan sencillo a el _concern_ del archivo `authenticable.rb`: +Como puedes ver, protegemos la acción `create` con el método `check_login`. También creamos al producto por asociación con el usuario. Yo agregué este método tan sencillo al _concern_ del archivo `authenticable.rb`: [source,ruby] .app/controllers/concerns/authenticable.rb @@ -443,7 +440,7 @@ end ---- -Ahora las pruebas deberian pasar: +Ahora las pruebas deberían pasar: .... $ rake test @@ -453,9 +450,9 @@ $ rake test === Actualizando los productos -Espero que por ahora entiendas la lógica para construir la acciones que vienen. En esta sección nos enfocaremos en la acción `update` que funcionará a la acción `create`. Solamene necesitamos buscar el producto des la base de datos y actualizarlo. +Espero que por ahora entiendas la lógica para construir la acciones que vienen. En esta sección nos enfocaremos en la acción `update` que funcionará a la acción `create`. Solamente necesitamos buscar el producto desde la base de datos y actualizarlo. -Añadiremos primer la acción a las rutas asi no nos olvidamos despues: +Añadiremos primer la acción a las rutas así no nos olvidamos después: [source,ruby] .config/routes.rb @@ -470,9 +467,9 @@ Rails.application.routes.draw do end ---- -Antes de iniciar borrando alguna prueba quiero aclarar que similarmente a la acción `create` vamos a dar alcance en el producto al con el método `current_user`. En este caso queremos asegurar que el producto que se esta actualizando pertenece al suauri actual. In this case we want to make sure the product we are updating is owned by the current user. Asi que buscaremos los productos de la asociación `user.products` proveída por Rails. +Antes de iniciar borrando alguna prueba quiero aclarar que similarmente a la acción `create` vamos a dar alcance en el producto al con el método `current_user`. En este caso queremos asegurar que el producto que se está actualizando pertenece al usuario actual. Así que buscaremos los productos de la asociación `user.products` proveída por Rails. -Let's add some specs: +Agreguemos algunas especificaciones: [source,ruby] .test/controllers/api/v1/products_controller_test.rb @@ -503,7 +500,7 @@ end NOTE: Tengo añadido un _fixture_ correspondiente a un segundo usuario justo para verificar que el segundo usuario no puede modificar productos del primer usuario. -Las pruebas parecen complejas pero echa un segundo vistazo. Son casi lo mismo que construimos para los usuarios. +Las pruebas parecen complejas, pero echa un segundo vistazo. Son casi lo mismo que construimos para los usuarios. Ahora vamos a implementar el código para hacer pasar nuestras pruebas: @@ -547,7 +544,7 @@ class Api::V1::ProductsController < ApplicationController end ---- -La implementación es muy simple. Simplemente recuperaremos el productodesde el usuario conectad y simplemente lo actualizamos. Tenemos tambien agregadas esta acción a el `before_action` para prevenir cualquier usuario no autorizado desde la actualización de un producto. +La implementación es muy simple. Simplemente recuperaremos el producto desde el usuario conectad y simplemente lo actualizamos. Tenemos también agregadas esta acción a el `before_action` para prevenir cualquier usuario no autorizado desde la actualización de un producto. Ahora las pruebas deberían pasar: @@ -560,7 +557,7 @@ $ rake test === Destruyendo productos -Nuestra última parada para los endpoints de los productos sera la accion `destroy` (destruir). Podrias ahora imaginar como se vería esto. La estrategía aquí sera demasiado similar a las acciones `create` y `destroy`: obtenemos al usuario logueado con el token JWT y entonces buscamos el producto desde la asociación `user.products` y finalmente lo destruimos, regresamos un código `204`. +Nuestra última parada para los endpoints de los productos será la acción `destroy` (destruir). Podrías ahora imaginar cómo se vería esto. La estrategia aquí será demasiado similar a las acciones `create` y `destroy`: obtenemos al usuario logueado con el token JWT y entonces buscamos el producto desde la asociación `user.products` y finalmente lo destruimos, regresamos un código `204`. Vamos a iniciar de nuevo añadiendo el nombre de la ruta al archivo de rutas: @@ -578,7 +575,7 @@ Rails.application.routes.draw do end ---- -Despues de esto, tenemos que añadir algunas pruebas como se muestra en este fragmento de código: +Después de esto, tenemos que añadir algunas pruebas como se muestra en este fragmento de código: [source,ruby] .test/controllers/api/v1/products_controller_test.rb @@ -625,7 +622,7 @@ class Api::V1::ProductsController < ApplicationController end ---- -Como puedes ver las cuatro lineas implementadas hacen el trabajo. Podemos correr las pruebas para asegurar que todo esta bien y entonces haremos un commit de los cambios ya que hemos añadido un montón de código. Tambien asegurate que llamas a esta accion en el callback `before_action` al igual que en la acción `update`. +Como puedes ver las cuatro líneas implementadas hacen el trabajo. Podemos correr las pruebas para asegurar que todo está bien y entonces haremos un commit de los cambios ya que hemos añadido un montón de código. También asegúrate que llamas a esta acción en el callback `before_action` al igual que en la acción `update`. [source,bash] ---- @@ -643,9 +640,9 @@ $ git commit -am "Adds the products create, update and destroy actions" == Llenado de la base de datos -Vamos a llenar la base de datos con información falsa antes de continuar esribiendo mas código. Vamos a usar los _seeds_ para hacerlo. +Vamos a llenar la base de datos con información falsa antes de continuar escribiendo más código. Vamos a usar los _seeds_ para hacerlo. -Con el archivo `db/seeds.rb`, Rails nos da una forma facil y rapida para asignar valores por defecto en una nueva instalación. Este es un simple archivo de Ruby que nos da completo acceso a clases y metodos de la aplicación. Asi que no necesitas meter todo manualmente con la consola de Rails sino que puedes simplemente usar el archivo `db/seeds.rb` con el comando `rake db:seed`. +Con el archivo `db/seeds.rb`, Rails nos da una forma fácil y rápida para asignar valores por defecto en una nueva instalación. Este es un simple archivo de Ruby que nos da completo acceso a clases y métodos de la aplicación. Así que no necesitas meter todo manualmente con la consola de Rails sino que puedes simplemente usar el archivo `db/seeds.rb` con el comando `rake db:seed`. Asi que vamos a iniciar creando un usuario: @@ -666,14 +663,14 @@ $ rake db:seed Created a new user: toto@toto.fr ---- -Funciona. No se tú, pero a mi me gusta tener datos ficticios para llenar correcamente mi base de datos de prueba. Solo que no siempre tengo la inspiración para dar sentido a my archivo _seed_ asi que uso la gema https://github.com/stympy/faker[`faker`]. Vamos a configurarla: +Funciona. No sé tú, pero a mí me gusta tener datos ficticios para llenar correctamente mi base de datos de prueba. Solo que no siempre tengo la inspiración para dar sentido a mi archivo _seed_ así que uso la gema https://github.com/stympy/faker[`faker`]. Vamos a configurarla: [source,bash] ---- $ bundle add faker ---- -Ahora podemos usarla para crear cinco uuarios al mismo tiempo con diferentes emails. +Ahora podemos usarla para crear cinco usuarios al mismo tiempo con diferentes emails. .db/seeds.rb [source,ruby] @@ -698,7 +695,7 @@ Created a new user: scott@moenerdman.biz Created a new user: chelsie@wiza.net ---- -Ahí lo tienes. PEro podemos ir mas lejos creando productos asociados con estos usuarios: +Ahí lo tienes. Pero podemos ir más lejos creando productos asociados con estos usuarios: .db/seeds.rb @@ -723,7 +720,7 @@ User.delete_all end ---- -Ahi lo tienes. El resultado es asombroso. En una orden podemos crear tres usuarios y seis productos: +Ahí lo tienes. El resultado es asombroso. En una orden podemos crear tres usuarios y seis productos: [source,bash] ---- @@ -746,7 +743,7 @@ Hagamos un _commit_: $ git commit -am "Create a seed to populate database" ---- -Y como llegamos al final de nuestro capítulo, est tiempo de aplicar todas las modificaciones a la rama master haciendo un _merge_: +Y como llegamos al final de nuestro capítulo, es tiempo de aplicar todas las modificaciones a la rama master haciendo un _merge_: [source,bash] ---- @@ -756,7 +753,6 @@ $ git merge chapter05 == Conclusión -Espero que hayas disfrutado este capítulo. Es el mas largo pero el código que hicimos juntos es una excelente base para el nucleo de nuestra applicación. - -En el siguiente capítulo, nos enfocaremos en perzonalizar la salido de los modelos usuarios y productos usando la gema https://github.com/Netflix/fast_jsonapi[fast_jsonapi]. Esto nos permitira filtrar facilmente los atributos para mostrar y manipular asociaciones como objetos embebidos por ejemplo. +Espero que hayas disfrutado este capítulo. Es el más largo pero el código que hicimos juntos es una excelente base para el núcleo de nuestra aplicación. +En el siguiente capítulo, nos enfocaremos en personalizar la salido de los modelos usuarios y productos usando la gema https://github.com/Netflix/fast_jsonapi[fast_jsonapi]. Esto nos permitirá filtrar fácilmente los atributos para mostrar y manipular asociaciones como objetos embebidos, por ejemplo. diff --git a/rails6/es/chapter06-improve-json.adoc b/rails6/es/chapter06-improve-json.adoc index 749d248..af2c941 100644 --- a/rails6/es/chapter06-improve-json.adoc +++ b/rails6/es/chapter06-improve-json.adoc @@ -1,9 +1,9 @@ [#chapter06-improve-json] = Construyendo la repuesta JSON -En el capítulo anterior agregamos productos a la aplicación y creamos las rutas necesarias. Tenemos tambien asociado un producto con un usario y restringidas algunas acciones del controlador `products_controller`. +En el capítulo anterior agregamos productos a la aplicación y creamos las rutas necesarias. Tenemos también asociado un producto con un usuario y restringidas algunas acciones del controlador `products_controller`. -Ahora puedes estar satisfecho con todo este trabajo. Pero todavía tenemos un monton de trabajo por hacer. Actualmente tenemos unsa salida JSON que no es perfecta. La salida JSON luce así: +Ahora puedes estar satisfecho con todo este trabajo. Pero todavía tenemos un montón de trabajo por hacer. Actualmente tenemos una salida JSON que no es perfecta. La salida JSON luce así: [source,json] ---- @@ -24,7 +24,7 @@ Ahora puedes estar satisfecho con todo este trabajo. Pero todavía tenemos un mo Como sea buscamso una salida que no contenga los campos `user_id`, `created_at` y `updated_at`. -Una parte importante (y dificil) cuando estas creando tu API es decidir el formato de salida. Afortunadamente algunas organizaciones ya tienen encarado este tipo de problema y tienen establecidas algunas convenciones que descubrirás en este capítulo. +Una parte importante (y difícil) cuando estas creando tu API es decidir el formato de salida. Afortunadamente algunas organizaciones ya tienen encarado este tipo de problema y tienen establecidas algunas convenciones que descubrirás en este capítulo. Puedes clonar el proyecoto hasta este punto: @@ -42,16 +42,16 @@ $ git checkout -b chapter06 == Presentación de https://jsonapi.org/[JSON:API] -Una parte importante y dificil de crear tu API es decidir el formato de salida. Afortunadamente algunas convenciones ya existen. Ciertamente las mas usada es https://jsonapi.org/[JSON:API]. +Una parte importante y difícil de crear tu API es decidir el formato de salida. Afortunadamente algunas convenciones ya existen. Ciertamente las más usada es https://jsonapi.org/[JSON:API]. La https://jsonapi.org/format/#document-structure[documentación de JSON:API] nos da algunas reglas a seguir respecto al formateado del documento JSON. En consecuencia, nuestro documento *debería* contener estas llaves: -* `data`: que contiene la información que develovemos +* `data`: que contiene la información que devolvemos * `errors` que contienen un arreglo de errores ocurridos -* `meta` que contiene un https://jsonapi.org/format/#document-meta[meta object] +* `meta` que contiene un https://jsonapi.org/format/#document-meta[meta objeto] El contenido de la llave `data` es demasiado estricto: @@ -61,7 +61,7 @@ El contenido de la llave `data` es demasiado estricto: En este capítulo vamos a personalizar la salida JSON usando la gema de Netflix: https://github.com/Netflix/fast_jsonapi[fast_jsonapi]. Afortunadamente ya implementa todas las especificaciones https://jsonapi.org/[JSON:API]. -Asi que instalemos la gema `fast_jsonapi`: +Así que instalemos la gema `fast_jsonapi`: [source,bash] ---- @@ -72,9 +72,9 @@ Deberias estar listo para continuar este tutorial. == Serializar el usuario -FastJSON API usa *serializers*. Los serializadores representan clases Ruby que serán responsables de convertur un modelo en un https://ruby-doc.org/core-2.6.3/Hash.html[`Hash`] o un JSON. +FastJSON API usa *serializers*. Los serializadores representan clases Ruby que serán responsables de convertir un modelo en un https://ruby-doc.org/core-2.6.3/Hash.html[`Hash`] o un JSON. -Asi que nedesitamos añador un archivo `user_serializer.rb`. Podemos hacerlo manualmente pero la gema provee una inteface de linea de comandos para hacerlo: +Así que necesitamos añadir un archivo `user_serializer.rb`. Podemos hacerlo manualmente, pero la gema provee una interface de línea de comandos para hacerlo: [source,bash] ---- @@ -82,7 +82,7 @@ $ rails generate serializer User email create app/serializers/user_serializer.rb ---- -Esto habra creado un archivo llamado `user_serializer.rb` bajo la ruta `app/serializers`. El nuevo archivo deberia lucir como el siguiente archivo: +Esto habrá creado un archivo llamado `user_serializer.rb` bajo la ruta `app/serializers`. El nuevo archivo debería lucir como el siguiente archivo: [source,ruby] .app/serializers/user_serializer.rb @@ -93,7 +93,7 @@ class UserSerializer end ---- -Este _serializer_ nos permitira convertur nusetro objeto `User` a JSON implementando todas las especificaciones JSON:API. Como especificamos `email` como `attributes` lo recibimos en un arreglo `data`. +Este _serializer_ nos permitirá convertir nuestro objeto `User` a JSON implementando todas las especificaciones JSON:API. Como especificamos `email` como `attributes` lo recibimos en un arreglo `data`. Vamos a intentar todo esto en la consola de rails con `rails console`: @@ -103,7 +103,7 @@ Vamos a intentar todo esto en la consola de rails con `rails console`: => {:data=>{:id=>"25", :type=>:user, :attributes=>{:email=>"tova@beatty.org"}}} ---- -Ahí tienes. Como puedes ver es realmente facil. Ahora podemos usar nuestro nuevo _serializer_ en nuestro _controller_: +Ahí tienes. Como puedes ver es realmente fácil. Ahora podemos usar nuestro nuevo _serializer_ en nuestro _controller_: .app/controllers/api/v1/users_controller.rb @@ -136,7 +136,7 @@ class Api::V1::UsersController < ApplicationController end ---- -No es demasiado facil? Como sea deberiamos tener una prueba que falla. Pruebalo por ti mismo: +¿No es demasiado fácil? Como sea deberíamos tener una prueba que falla. Pruébalo por ti mismo: [source,bash] ---- @@ -147,7 +147,7 @@ Expected: "one@one.org" Actual: nil ---- -Por alguna razón la respuesta no es lo que esperabamos. Esto es porque la gema modifica la respuesta que teníamos anteriormente definida. Así que para pasar esta prueba tenemos que modificarla: +Por alguna razón la respuesta no es lo que esperábamos. Esto es porque la gema modifica la respuesta que teníamos anteriormente definida. Así que para pasar esta prueba tenemos que modificarla: [source,ruby] .test/controllers/api/v1/users_controller_test.rb @@ -163,7 +163,7 @@ class Api::V1::UsersControllerTest < ActionDispatch::IntegrationTest end ---- -Si lo chisite ahora la prueba pasa: +Si lo hiciste ahora la prueba pasa: [source,bash] ---- @@ -171,7 +171,7 @@ $ rake test ........................ ---- -Guardemos estos cambios y sigamos moviendonos: +Guardemos estos cambios y sigamos moviéndonos: [source,bash] ---- @@ -181,7 +181,7 @@ $ git add . && git commit -am "Adds user serializer for customizing the json out == Serializado de productos -Ahora que entendemos como trabaja la gema de serialización es tiempo de personalizar la salida del producto. El primer paso es el mismo que hicimos en el capitulo previo. Necesitamos un serializador de producto. Asi que hagamoslo: +Ahora que entendemos cómo trabaja la gema de serialización es tiempo de personalizar la salida del producto. El primer paso es el mismo que hicimos en el capítulo previo. Necesitamos un serializador de producto. Así que hagámoslo: [source,bash] ---- @@ -236,7 +236,7 @@ class Api::V1::ProductsController < ApplicationController end ---- -I actualizamos nuestra prueba funcional: +Actualizamos nuestra prueba funcional: [source,ruby] .test/controllers/api/v1/products_controller_test.rb @@ -252,7 +252,7 @@ class Api::V1::ProductsControllerTest < ActionDispatch::IntegrationTest end ---- -Si quieres puedes revisar si la prueba pasa pero debería. Guardemos estos pequeños cambios: +Si quieres puedes revisar si la prueba pasa, pero debería. Guardemos estos pequeños cambios: [source, bash] ---- @@ -262,11 +262,11 @@ $ git commit -m "Adds product serializer for custom json output" === Serializar asociaciones -Hemos trabajado con serializadores y has notado que es muy simple. En algunos casos la decicipon dificil es nombrar tus rutas o estructurar la salida JSON. Cuando se estra trabajando con asociaciones entre modelos en la API hay muchos enfoque que puees tomar. +Hemos trabajado con serializadores y has notado que es muy simple. En algunos casos la decisión difícil es nombrar tus rutas o estructurar la salida JSON. Cuando se está trabajando con asociaciones entre modelos en la API hay muchos enfoques que puedes tomar. -No debemos preocuparnos de este problem en nuestro caso: Las especificaciones JSON:API lo hicieron por nosotros! +No debemos preocuparnos de este problema en nuestro caso: Las especificaciones JSON:API lo hicieron por nosotros! -Para recapitular tenemos un tipo de asociacion `has_many` entre usuarios y productos. +Para recapitular tenemos un tipo de asociación `has_many` entre usuarios y productos. [source,ruby] .app/models/user.rb @@ -286,19 +286,19 @@ class Product < ApplicationRecord end ---- -Es una buena idea integrar usuario en las salidas JSON de productos. Esto harala salida mas incomoda pero prevendra al cliente de la API ejecutar otras peticiones para recibir informacion del usuario relacionada a los productos. Este método realmente puede salvarte de un enorme cuello de botella. +Es una buena idea integrar usuario en las salidas JSON de productos. Esto hará la salida más incomoda pero prevendrá al cliente de la API ejecutar otras peticiones para recibir información del usuario relacionada a los productos. Este método realmente puede salvarte de un enorme cuello de botella. -== Teoría de la inyeccion de relaciones +== Teoría de la inyección de relaciones Imagina un escenario donde pides a la API productos, pero en este caso tienes que mostrar alguna información del usuario. -Una posible solución podria ser añadir el atributo `user_id` a el `product_serializer` asi podemos obtener el usuario correspondiente mas tarde. Esto puede sonar como una buena idea, pero si estar preocupado sobre el rendimiento, o si las transacciones de la base de datos no son suficientemente rápidas, deberias reconsiderar éste enfoque. Deberias entendes que de cada producto que recuperes, deberías de recuperar su usuario correspondiente. +Una posible solución podría ser añadir el atributo `user_id` a el `product_serializer` así podemos obtener el usuario correspondiente más tarde. Esto puede sonar como una buena idea, pero si estar preocupado sobre el rendimiento, o si las transacciones de la base de datos no son suficientemente rápidas, deberías reconsiderar éste enfoque. Deberías entender que de cada producto que recuperes, deberías recuperar su usuario correspondiente. -Enfrentando a este problema, tenemos varia alternativas. +Enfrentando a este problema, tenemos varias alternativas. === Integrar en un meta atributo -La primera solicion (una buena en my opinión) es integrar identificadores de usuarios enlazados a los productos un un meta atributo. Asi obtenemos un JSON como abajo: +La primera solución (una buena en mi opinión) es integrar identificadores de usuarios enlazados a los productos un meta atributo. Así obtenemos un JSON como abajo: [source,json] ---- @@ -310,11 +310,11 @@ La primera solicion (una buena en my opinión) es integrar identificadores de us } ---- -Asi que el cliente puede recuperar estos usuarios desde `user_ids`. +Así que el cliente puede recuperar estos usuarios desde `user_ids`. === Incorporando el objeto en el atributo -Otra solución es incorporar el objeto `user` en el objeto `product`. Esto debería hacer a la primera petición lenta, pero de esta forma el cliente no ncesita hacer otra petición adicional. Un ejempo deel resultado esperado se presenta a continuación: +Otra solución es incorporar el objeto `user` en el objeto `product`. Esto debería hacer a la primera petición lenta, pero de esta forma el cliente no necesita hacer otra petición adicional. Un ejemplo del resultado esperado se presenta a continuación: [source,json] ---- @@ -395,9 +395,9 @@ El problema con este enfoque es que tenemos duplicados del objeto `User' para ca === Incorporar las relaciones incluidas en `include -LA tercer solición (elegida por JSON:API) es una combinacion de las primeras dos. +LA tercer solución (elegida por JSON:API) es una combinación de las primeras dos. -Incluiremos todoas las relaciones en una llave `include` que contendrá todoas las relaciones de los objetos previamente mencionados. Tambien, cada objeto inclurá una llave de ralación que define la relación y que deberia encontrar en cada llave `include`. +Incluiremos todas las relaciones en una llave `include` que contendrá todas las relaciones de los objetos previamente mencionados. También, cada objeto incluirá una llave de relación que define la relación y que debería encontrar en cada llave `include`. Un JSON vale mas que mil palabras: @@ -452,11 +452,11 @@ Un JSON vale mas que mil palabras: } ---- -¿Ves la diferencia? Esta solución reduce drasticamente el tamaño de el JSON y por lo tanto el ancho de banda utilizado. +¿Ves la diferencia? Esta solución reduce drásticamente el tamaño del JSON y por lo tanto el ancho de banda utilizado. -== Aplicación de la injección de relaciones +== Aplicación de la inyección de relaciones -Asi que incorporaremos el objeto user en el producto. Vamos a iniciar por añador algunas pruebas. +Asi que incorporaremos el objeto user en el producto. Vamos a iniciar por añadir algunas pruebas. Simplemente modificaremos la prueba `Products#show` para verificar que lo estamos recuperando: @@ -480,15 +480,15 @@ class Api::V1::ProductsControllerTest < ActionDispatch::IntegrationTest end ---- -Ahora revisaremos tres cosas que el JSON deberia retornar: +Ahora revisaremos tres cosas que el JSON debería retornar: . este contiene el título del producto . este contiene el ID del usuario ligado al producto . la información del usuario esta incluida en la llave `include` -NOTE: Deberias haber notado que decidí suar el método https://ruby-doc.org/core-2.6.3/Hash.html#method-i-dig[`Hash#dig`]. Este es un metodo Ruby que permite recuperar elementos en un _Hash_ anidado evitando errores si un elemento no esta presente. +NOTE: Deberías haber notado que decidí usar el método https://ruby-doc.org/core-2.6.3/Hash.html#method-i-dig[`Hash#dig`]. Este es un método Ruby que permite recuperar elementos en un _Hash_ anidado evitando errores si un elemento no está presente. -PAra pasar esta prueba iniciaremos por incluir la relacion en el _serializer_: +Para pasar esta prueba iniciaremos por incluir la relación en el _serializer_: [source,ruby] .app/serializers/product_serializer.rb @@ -500,7 +500,7 @@ class ProductSerializer end ---- -Esta adicion añadirá una llave `relationship` conteniendo el identificador del usuario: +Esta adición añadirá una llave `relationship` conteniendo el identificador del usuario: [source,json] ---- @@ -525,7 +525,7 @@ Esta adicion añadirá una llave `relationship` conteniendo el identificador del } ---- -Esto nos permite corregir nuestras primeras dos afirmaciones. Ahora queremos incluir atrubutos de el usuario a quien pertenesca el producto. Para hacer esto siemplemente necesitamos pasar una opción `:include` al _serializer_ iinstanciado en el controlador _controller_. Entonces hagamoslo: +Esto nos permite corregir nuestras primeras dos afirmaciones. Ahora queremos incluir atributos de el usuario a quien pertenezca el producto. Para hacer esto simplemente necesitamos pasar una opción `:include` al _serializer_ instanciado en el controlador _controller_. Entonces hagámoslo: [source,ruby] .app/controllers/api/v1/products_controller.rb @@ -540,7 +540,7 @@ class Api::V1::ProductsController < ApplicationController end ---- -Ahí tienes. Ahora asi es como deberia lucir el JSON: +Ahí tienes. Ahora así es como debería lucir el JSON: [source,json] ---- @@ -560,7 +560,7 @@ Ahí tienes. Ahora asi es como deberia lucir el JSON: } ---- -Ahora las pruebas deberian pasar: +Ahora las pruebas deberían pasar: [source,bash] ---- @@ -579,7 +579,7 @@ $ git commit -am "Add user relationship to product serializer" === Recuperar productos del usuario -¿Entiendes el principio? tenemos incluida informacion del usuario en el JSON de los productos. Podemos hacer lo mismo incluyendo información del producto relacionada a un usuario para la página `/api/v1/users/1`. +¿Entiendes el principio? tenemos incluida información del usuario en el JSON de los productos. Podemos hacer lo mismo incluyendo información del producto relacionada a un usuario para la página `/api/v1/users/1`. Empecemos con la prueba: @@ -674,7 +674,7 @@ Ahí tienes. Obtenemos un JSON como el siguiente: } ---- -Fue realmente facil. Hagamos un _commit_: +Fue realmente fácil. Hagamos un _commit_: [source,bash] ---- @@ -683,24 +683,23 @@ $ git commit -am "Add products relationship to user#show" == Buscando productos -En esta ultima seccion continuaremos fortaleciendo la acción `Products#index` configurando un mecanismo de busqueda muy simple permitiendo a cualquier cliente filtrar los resultados. Esta sección es opcional asi que no tendrá impacto en los modulos de la aplicación. Pero si quiere practiar mas con las TDD (Test Driven Development) recomiendo que completes este último paso. +En esta última sección continuaremos fortaleciendo la acción `Products#index` configurando un mecanismo de búsqueda muy simple permitiendo a cualquier cliente filtrar los resultados. Esta sección es opcional así que no tendrá impacto en los módulos de la aplicación. Pero si quiere practicar mas con las TDD (Test Driven Development) recomiendo que completes este último paso. -Yo uso https://github.com/activerecord-hackery/ransack[Ransack] ó https://github.com/casecommons/pg_search[pg_search] para construir formas de busqueda extremamente rapido. Pero como el objetivo es aprender y buscar vamos a hacerlo muy sencillo. Creo que podemos contruir un motor de busqueda desde cero. Siplemento tenemos que considerar los tuterios por los cuales filtraremos los atributos. Quedate en tu asiento vamos a hacer este viaje juntos. +Yo uso https://github.com/activerecord-hackery/ransack[Ransack] ó https://github.com/casecommons/pg_search[pg_search] para construir formas de busqueda extremamente rápido. Pero como el objetivo es aprender y buscar vamos a hacerlo muy sencillo. Creo que podemos construir un motor de búsqueda desde cero. Simplemente tenemos que considerar los criterios por los cuales filtraremos los atributos. Quédate en tu asiento vamos a hacer este viaje juntos. -Por lo tanto filtraremos los productos de acurdo a los siguientes criterios: +Por lo tanto, filtraremos los productos de acuerdo a los siguientes criterios: * Por título * Por precio * Acomodar por fecha de creación -Esto pude verse pequeño y fácil, pero creeme, esto te dará dolor de cabeza si no lo planeas. -It may seem short and easy, but believe me, it will give you a headache if you don't plan it. +Esto parece pequeño y fácil, pero créeme, esto te dará dolor de cabeza si no lo planeas. -=== Por keyword +=== Por palabra clave -Crearemos un _scope_ para encontrar los registros que coinciden con un patrón de caracteres en particular. Vamos allamarlo `filter_by_title`. +Crearemos un _scope_ para encontrar los registros que coinciden con un patrón de caracteres en particular. Vamos a llamarlo `filter_by_title`. -Comenzaremos por añador algunos _fixtures_ con diferentes productos para probar: +Comenzaremos por añadir algunos _fixtures_ con diferentes productos para probar: [source,yaml] .test/fixtures/products.yml @@ -742,7 +741,7 @@ class ProductTest < ActiveSupport::TestCase end ---- -La siguiente prueba se asegura que el método `Product.filter_by_title` buscará correctamente los productos de acuerdo con su título. Usamos el término `tv` en minusculas para segurar que nuestra busqueda no sea sensitiva a mayusculas y minusculas. +La siguiente prueba se asegura que el método `Product.filter_by_title` buscará correctamente los productos de acuerdo con su título. Usamos el término `tv` en minúsculas para asegurar que nuestra búsqueda no sea sensitiva a mayúsculas y minúsculas. [source,ruby] .app/models/product.rb @@ -755,7 +754,7 @@ class Product < ApplicationRecord end ---- -NOTE: _scoping_ te permite especificar las consultas comunmente usadas que pueden ser referenciadas como llamada de metodo en los modelos. Con estos __scopes__ puedes enlazar metodos con Active Record methods como `where`, `joins` y `includes` porque un _scope_ siempre retorna un objeto https://api.rubyonrails.org/classes/ActiveRecord/Relation.html[`ActiveRecord::Relation`]. Te invito a que echos un vistaso en https://guides.rubyonrails.org/active_record_querying.html#scopes_record_querying.html#scopes[Rails documentation] +NOTE: _scoping_ te permite especificar las consultas comúnmente usadas que pueden ser referenciadas como llamada de método en los modelos. Con estos __scopes__ puedes enlazar métodos con Active Record como `where`, `joins` y `includes` porque un _scope_ siempre retorna un objeto https://api.rubyonrails.org/classes/ActiveRecord/Relation.html[`ActiveRecord::Relation`]. Te invito a que eches un vistazo en la https://guides.rubyonrails.org/active_record_querying.html#scopes_record_querying.html#scopes[documentación de Rail] Esta implementación es suficiente para que nuestras pruebas pasen: @@ -767,9 +766,9 @@ $ rake test === Por precio -Para filtrar por precio, las cosas pueden ser un poco mas delicadas. Separaremos la lógica del filtrado por precio en dos diferentes métodos: uno que bucará por productos con precio mayor al recivido y otro que busque aquellos que son menores que el precio. De esta forma, mantendremos algo de flexibilidad y podemos facilmente probar el _scope_. +Para filtrar por precio, las cosas pueden ser un poco más delicadas. Separaremos la lógica del filtrado por precio en dos diferentes métodos: uno que buscará por productos con precio mayor al recibido y otro que busque aquellos que son menores que el precio. De esta forma, mantendremos algo de flexibilidad y podemos fácilmente probar el _scope_. -Vamos a iniciar por construir las pruebas de el _scope_ `above_or_equal_to_price`: +Vamos a iniciar por construir las pruebas del _scope_ `above_or_equal_to_price`: [source,ruby] .test/models/product_test.rb @@ -804,7 +803,7 @@ $ rake test ........................... ---- -Puedes imaginar el comportamiento del metodo opuesto. Aquí esta la prueba: +Puedes imaginar el comportamiento del método opuesto. Aquí está la prueba: [source,ruby] .test/models/product_test.rb @@ -831,7 +830,7 @@ class Product < ApplicationRecord end ---- -Para nuestros motivos, vamos a hacer la prueba y revisar que todo esta hermosamente en verde: +Para nuestros motivos, vamos a hacer la prueba y revisar que todo está hermosamente en verde: [source,bash] ---- @@ -839,9 +838,9 @@ $ rake test ............................ ---- -Como puedes ver, no tuvimos muchos problemas. Vamos a añadir otro _scope_ para acomodar los registros por la fecha de la última actualización. En el caso cuando el propuetrio de los productos decide actualizar alguna informacion seguramente bucara acomodar sus productos por la fecha de creación. +Como puedes ver, no tuvimos muchos problemas. Vamos a añadir otro _scope_ para acomodar los registros por la fecha de la última actualización. En el caso cuando el propietario de los productos decide actualizar alguna información seguramente buscará acomodar sus productos por la fecha de creación. -=== Sort by creation date +=== Ordenas por fecha de creación Este _scope_ es muy fácil. Vamos a añadir algunas pruebas primero: @@ -872,7 +871,7 @@ class Product < ApplicationRecord end ---- -Todos nuestras pruebas deberían de pasar: +Todas nuestras pruebas deberían de pasar: [source,bash] ---- @@ -888,11 +887,11 @@ $ git commit -am "Adds search scopes on the product model" ---- -==== Motor de busqueda +==== Motor de búsqueda -Ahora que tenemos lo basico para el motor de busqueda que usaremos en nuestra aplicación, es tiempo para implementar un simple pero poderoso método de busqueda. Este getioanara toda la logica para recuperar los registros de los productos. +Ahora que tenemos lo básico para el motor de búsqueda que usaremos en nuestra aplicación, es tiempo para implementar un simple pero poderoso método de búsqueda. Este gestionará toda la lógica para recuperar los registros de los productos. -El metodo consistirá en enlacar todos los `scope` que creamos anteriormente y retornar el resultado. Comencemos añadiendo algunas pruebas: +El método consistirá en enlazar todos los `scope` que creamos anteriormente y retornar el resultado. Comencemos añadiendo algunas pruebas: [source,ruby] .test/models/product_test.rb @@ -921,7 +920,7 @@ class ProductTest < ActiveSupport::TestCase end ---- -Añadimos un montón de código pero te aseguro que la implementacion es muy fácil. Tu puedes ir mas lejos y añadir pruebas adicionales pero, en mi caso, No lo encontré necesario. +Añadimos un montón de código, pero te aseguro que la implementación es muy fácil. Tú puedes ir más lejos y añadir pruebas adicionales pero, en mi caso, no lo encontré necesario. [source,ruby] .app/models/product.rb @@ -941,7 +940,7 @@ class Product < ApplicationRecord end ---- -Es importante notar que retornamos los productos como un objeto https://api.rubyonrails.org/classes/ActiveRecord/Relation.html[`ActiveRecord::Relation`] así que podemos concatenar otros métodos si es necesario o paginarlos como veremos en los últimos capítulos.Simplemente acutalizar la acción para recuperar los productos desde el método de busqueda: +Es importante notar que retornamos los productos como un objeto https://api.rubyonrails.org/classes/ActiveRecord/Relation.html[`ActiveRecord::Relation`] así que podemos concatenar otros métodos si es necesario o paginarlos como veremos en los últimos capítulos. Simplemente actualizar la acción para recuperar los productos desde el método de búsqueda: [source,ruby] .app/controllers/api/v1/products_controller.rb @@ -956,7 +955,7 @@ class Api::V1::ProductsController < ApplicationController end ---- -Podemos correr la suit completa de pruebas para asegurar que la aplicación esta en buen estado hasta aquí: +Podemos correr la suit completa de pruebas para asegurar que la aplicación está en buen estado hasta aquí: [source,bash] ---- @@ -982,4 +981,4 @@ $ git merge chapter06 == Conclusión -Hasta ahora fue facil gracias a la gema https://github.com/Netflix/fast_jsonapi_jsonapi[fast_jsonapi]. En el próximo capítulo vamos a iniciar con la construcción del modelo `Order` (orden) que implicará usuarios en los productos. +Hasta ahora fue fácil gracias a la gema https://github.com/Netflix/fast_jsonapi_jsonapi[fast_jsonapi]. En el próximo capítulo vamos a iniciar con la construcción del modelo `Order` (orden) que implicará usuarios en los productos. diff --git a/rails6/es/chapter07-placing-orders.adoc b/rails6/es/chapter07-placing-orders.adoc index 87ee41d..fe8e36c 100644 --- a/rails6/es/chapter07-placing-orders.adoc +++ b/rails6/es/chapter07-placing-orders.adoc @@ -1,15 +1,15 @@ [#chapter07-placing-orders] = Colocando órdenes -En el capitulo previo manejamos asociaciones entre productos y usuarios y como serializarlos a fin de escalar rapido y fácil. Ahora es tiempo de empezar a color ordenes lo cual será una situacion algo mas compleja. Manejaremos asociaciones entre estos tres modelos. Debemos ser lo suficientemente inteligenes para manejar la salida JSON que estamos entregando. +En el capítulo previo manejamos asociaciones entre productos y usuarios y como serializarlos a fin de escalar rápido y fácil. Ahora es tiempo de empezar a color ordenes lo cual será una situación algo más compleja. Manejaremos asociaciones entre estos tres modelos. Debemos ser lo suficientemente inteligentes para manejar la salida JSON que estamos entregando. -En este capítulo haremos algunas cosas que estan listadas a continuación: +En este capítulo haremos algunas cosas que están listadas a continuación: * Crear un modelo `Order` con sus correspondientes especificaciones -* Manipular la salida JSON con asociacion entre los modelos orden de usuario y producto -* Enviar un mail de confirmacion con el resumen de la orden +* Manipular la salida JSON con asociación entre los modelos orden de usuario y producto +* Enviar un mail de confirmación con el resumen de la orden -Asi que ahora todo esta claro podemos ensuciarnos las manos. Puedes clonar el proyecto hasta este punto con: +Entonces ahora todo está claro podemos ensuciarnos las manos. Puedes clonar el proyecto hasta este punto con: [source,bash] ---- @@ -25,16 +25,16 @@ $ git checkout -b chapter07 == Modelando la orden -Si recuerdas asociaciones de modelos, el modelo `Order` esta asociado con usuarios y productos al mismo tiempo. Acutalmente esto es muy simple de lograr en Rails. La parte dificil es cuado vamos a serializar estos objetos. Hablare mas sobre esto en la siguiente sección. +Si recuerdas asociaciones de modelos, el modelo `Order` esta asociado con usuarios y productos al mismo tiempo. Actualmente esto es muy simple de lograr en Rails. La parte difícil es cuando vamos a serializar estos objetos. Hablare más sobre esto en la siguiente sección. -Vamos a empezar creando el modelo `order`con una forma especial: +Vamos a empezar creando el modelo `order` con una forma especial: [source,bash] ---- $ rails generate model order user:belongs_to total:decimal ---- -El comando anterior generará el modelo order pero estoy tomando ventaja del método `references` para crear la llaver foranea correspondiente para que la orden pertenezca a el usuario. Esto también añade la directiva `belongs_to` dentro del modelo. Vamos a migrar la base de datos. +El comando anterior generará el modelo order pero estoy tomando ventaja del método `references` para crear la llave foránea correspondiente para que la orden pertenezca a el usuario. Esto también añade la directiva `belongs_to` dentro del modelo. Vamos a migrar la base de datos. [source,bash] ---- @@ -56,7 +56,7 @@ class OrderTest < ActiveSupport::TestCase end ---- -La implementacion es demasiado simple: +La implementación es demasiado simple: [source,ruby] .app/models/order.rb @@ -68,7 +68,7 @@ class Order < ApplicationRecord end ---- -No olvides añadir la relación `orders` a nuestros usuarios espedivicando el borrado en cascada: +No olvides añadir la relación `orders` a nuestros usuarios especificando el borrado en cascada: [source,ruby] .app/models/user.rb @@ -99,7 +99,7 @@ $ git add . && git commit -m "Generate orders" === Ordenes y productos -Necesitamos configurar la asociacion entre la `order` y el `product` y esto se hace con una asociación *has-many-to-many*. Como muchos productos puden ser puestos en muchas ordenes y las ordenes puede tener multiples productos. Así en este caso necesiamos un modelo intermedio el cual unirá estos tros dos objetos y mapeara las asociaciones apropiadas. +Necesitamos configurar la asociación entre la `order` y el `product` y esto se hace con una asociación *has-many-to-many*. Como muchos productos pueden ser puestos en muchas ordenes y las ordenes puede tener múltiples productos. Así en este caso necesitamos un modelo intermedio el cual unirá estos otros dos objetos y mapeará las asociaciones apropiadas. Vamos a genera este modelo: @@ -115,7 +115,7 @@ Vamos a correr la migración en la base de datos: $ rake db:migrate ---- -LA implementación es como: +La implementación es como: [source,ruby] .app/models/product.rb @@ -138,7 +138,7 @@ class Order < ApplicationRecord end ---- -Si has etado siguiendo el tutorial para la implementacion , esta ya esta lista debido a las `references` (referencias) que forman parte del comando generador del modelo. Podriamos añadir la opción `inverse_of` a el modelo `placement` para cada llamada `belongs_to`. Esto da un pequeño impulso cuando referenciamos al objeto padre. +Si has estado siguiendo el tutorial para la implementación , esta ya está lista debido a las `references` (referencias) que forman parte del comando generador del modelo. Podríamos añadir la opción `inverse_of` a el modelo `placement` para cada llamada `belongs_to`. Esto da un pequeño impulso cuando referenciamos al objeto padre. [source,ruby] .app/models/placement.rb @@ -157,7 +157,7 @@ $ rake test .................................. ---- -Ahora que todo esta bieny en varde vamos a hacer commit de los cambios y continuar. +Ahora que todo está bien y en verde vamos a hacer commit de los cambios y continuar. [source,bash] ---- @@ -167,15 +167,15 @@ $ git add . && git commit -m "Associates products and orders with a placements m == Exponer el modelo usuario -Es tiempo de poner en orden el controlador para epxopner las ordenes correctas. Si recuerdas el capítulo previo donde https://github.com/Netflix/fast_jsonapi_jsonapi[fast_jsonapi] fue usada, deberias reorder que fue realmente fácil. +Es tiempo de poner en orden el controlador para exponer las ordenes correctas. Si recuerdas el capítulo previo donde https://github.com/Netflix/fast_jsonapi_jsonapi[fast_jsonapi] fue usada, deberías recordar que fue realmente fácil. -Vamos a definir prumero que acciones tomará: +Vamos a definir primero que acciones tomará: -. Una acción de indexación para recuperar las ordenes de usuaro actuales -. Una accion show para recuperar un commando particular desde el usuario actual -. Una accion de creación para generar la orden +. Una acción de indexación para recuperar las ordenes de usuario actuales +. Una acción show para recuperar un comando particular desde el usuario actual +. Una acción de creación para generar la orden -Vamos a inciiar con la acción `index`. Primero tenemos el comando para crear el conrolador: +Vamos a iniciar con la acción `index`. Primero tenemos el comando para crear el controlador: [source,bash] ---- @@ -184,11 +184,11 @@ $ rails generate controller api::v1::orders Hasta este punto y antes de empezar a escribir algo de código tenemos que preguntarnos a nosotros mismos: -> ¿Deberia dejar mis enpoints de ordenes anidado dentro de `UserController` o deberia aislarlas? +> ¿Debería dejar mis enpoints de ordenes anidado dentro de `UserController` o debería aislarlas? La respuesta es realmente simple: esto depende de la carga o información que quieras exponer al desarrollador. -En nuesro caso, no haremos esto porque recuperaremos los comandos del usuario desde la ruta `/orders`. Vamos a iniciar con algunas pruebas: +En nuestro caso, no haremos esto porque recuperaremos los comandos del usuario desde la ruta `/orders`. Vamos a iniciar con algunas pruebas: [source,ruby] .test/controllers/api/v1/orders_controller_test.rb @@ -216,7 +216,7 @@ class Api::V1::OrdersControllerTest < ActionDispatch::IntegrationTest end ---- -Si corremos la suit de pruebas ahora ambas pruebas deberian de fallar como ya esperabamos. Esto es porque estas no tienen establecidas las rutas o acciones correctas. Iniciemos añadiendo las rutas: +Si corremos la suit de pruebas ahora ambas pruebas deberían de fallar como ya esperábamos. Esto es porque estas no tienen establecidas las rutas o acciones correctas. Iniciemos añadiendo las rutas: [source,ruby] .config/routes.rb @@ -266,7 +266,7 @@ class Api::V1::OrdersController < ApplicationController end ---- -Y ahora todos nuestras pruebas deberían de pasar: +Y ahora todas nuestras pruebas deberían de pasar: [source,bash] ---- @@ -275,7 +275,7 @@ $ rake test 36 runs, 53 assertions, 0 failures, 0 errors, 0 skips ---- -Nos gustan que nuestros commits sean muy atomicos, asi que vamos a guardar estos cambios: +Nos gustan que nuestros commits sean muy atómicos, así que vamos a guardar estos cambios: [source,bash] ---- @@ -284,9 +284,9 @@ $ git add . && git commit -m "Adds the index action for order" === Renderizar una sola orden -Como ahora puedes imaginar esta ruta es muy facil. Unicamente hacemos algunas configuraciones (rutas, acción de controlador) y esta seccion estara terminada. Tabmien incluiremos productos relacionados a esta orden en la salida JSON. +Como ahora puedes imaginar esta ruta es muy fácil. Únicamente hacemos algunas configuraciones (rutas, acción de controlador) y esta sección estará terminada. También incluiremos productos relacionados a esta orden en la salida JSON. -Vamos ainiciar añadiendo algunas pruebas: +Vamos a iniciar añadiendo algunas pruebas: [source,ruby] .test/controllers/api/v1/orders_controller_test.rb @@ -307,9 +307,9 @@ class Api::V1::OrdersControllerTest < ActionDispatch::IntegrationTest end ---- -Como puedes ver, la segunda parte de la prueba verifica que el producto esta incluido en el JSON. +Como puedes ver, la segunda parte de la prueba verifica que el producto está incluido en el JSON. -Vamos añadir la implementacion para correr nuestras pruebas. En el archivo `routes.rb` añadimos la acción `show` a las rutas de comando: +Vamos añadir la implementación para correr nuestras pruebas. En el archivo `routes.rb` añadimos la acción `show` a las rutas de comando: [source,ruby] .config/routes.rb @@ -322,7 +322,7 @@ Rails.application.routes.draw do end ---- -Y la implementación deberia lucir como esto: +Y la implementación debería lucir como esto: [source,ruby] .app/controllers/api/v1/orders_controller.rb @@ -343,7 +343,7 @@ class Api::V1::OrdersController < ApplicationController end ---- -Nuestras pruebas deberian estar todas verdes: +Nuestras pruebas deberían estar todas verdes: [source,bash] ---- @@ -352,7 +352,7 @@ $ rake test 37 runs, 55 assertions, 0 failures, 0 errors, 0 skips ---- -Vamos a hacer commit de los cambios y parar a crear la accion de crear orden: +Vamos a hacer commit de los cambios y parar a crear la acción de crear orden: [source,bash] ---- @@ -361,15 +361,15 @@ $ git commit -am "Adds the show action for order" === Colocando y ordenando -Es tiempo ahora de dar la oportunidad de colocar algunas ordenes. ESto añadira complejidad a la aplicación, pero no te preocupes, vamos a hacer cada cosa en su tiempo. +Es tiempo ahora de dar la oportunidad de colocar algunas órdenes. Esto añadirá complejidad a la aplicación, pero no te preocupes, vamos a hacer cada cosa en su tiempo. -Antes de implementar esta caracteristica, tomare tiempo para pensar sobre la implicación de crear un comando en la applicación. No estoy hablando sobre configurar un servicio de transaccion como el de https://stripe.com/[Stripe] ó https://www.braintreepayments.com/[Braintree] pero algo como: +Antes de implementar esta característica, tomare tiempo para pensar sobre la implicación de crear un comando en la aplicación. No estoy hablando sobre configurar un servicio de transacción como el de https://stripe.com/[Stripe] ó https://www.braintreepayments.com/[Braintree] pero algo como: * gestionamiento de productos out-of-stock (fuera de stock) * reducir el inventario del producto -* añadir alguna validacion para el colocamiento de ordenes para asegurar que hay los sufcientes prodctos al momento de coloar la orden +* añadir alguna validación para el colocamiento de ordenes para asegurar que hay los suficientes productos al momento de colocar la orden -Parece que aún hay mucho por hacer pero creeme: estar mas cerca de lo que piensas y no es tan dificil como parece. Por ahora mandengamoslo simple y asumamos que aún tendremos suficientes productos para colocar cualquier numero de ordenes. Solo estamos preocupados sobre la respuesta del servidor por el momentos. +Parece que aún hay mucho por hacer pero créeme: estar más cerca de lo que piensas y no es tan difícil como parece. Por ahora mantengámoslo simple y asumamos que aún tendremos suficientes productos para colocar cualquier número de órdenes. Solo estamos preocupados sobre la respuesta del servidor por el momento. Si tu recuerdas el modelo de orden, necesitamos tres cosas: @@ -377,7 +377,7 @@ Si tu recuerdas el modelo de orden, necesitamos tres cosas: * usuario que coloca la orden * productos para la orden -Basado en esta información podemos empezar añadiendo alguns pruebas: +Basado en esta información podemos empezar añadiendo algunas pruebas: [source,ruby] .test/controllers/api/v1/orders_controller_test.rb @@ -413,9 +413,9 @@ class Api::V1::OrdersControllerTest < ActionDispatch::IntegrationTest end ---- -Como puedes ver estamos crean una variable `order_params` con los datos de la orden. ¿Puedes ver el problema aquí? Si no, lo explicare mas tarde. Justamente añadimos el código necesario para hacer pasar laprueba. +Como puedes ver estamos crean una variable `order_params` con los datos de la orden. ¿Puedes ver el problema aquí? Si no, lo explicare más tarde. Justamente añadimos el código necesario para hacer pasar la prueba. -Primero necesitamos añadir la acción a los recuros en el archivo de rutas: +Primero necesitamos añadir la acción a los recursos en el archivo de rutas: [source,ruby] .config/routes.rb @@ -428,7 +428,7 @@ Rails.application.routes.draw do end ---- -Entonces la implementacion es fácil: +Entonces la implementación es fácil: [source,ruby] .app/controllers/api/v1/orders_controller.rb @@ -464,11 +464,11 @@ $ rake test 39 runs, 59 assertions, 0 failures, 0 errors, 0 skips ---- -Ok, entonces tenemos todo correcto y en verde. Ahora deberíamos movernos al sigueinte capitulo, ¿correcto? Dejame detenerte justo aquí. Tenemos algunos errores serios en la applicación, y estos no estan relacionados al código por si mismo pero si en la parte del negocio. +Ok, entonces tenemos todo correcto y en verde. Ahora deberíamos movernos al siguiente capitulo, ¿correcto? Déjame detenerte justo aquí. Tenemos algunos errores serios en la aplicación, y estos no están relacionados al código por sí mismo, pero si en la parte del negocio. -No porque los las pruebas esten verdes, esto significa que la aplicación esta cibriendo la parte del negocio. Queria traer esto aqui porque en muchos casoses super facil solo recibir parametros y contruir objetos desde esos parametor pensando que siempre estamos recibiendo los datos correctos. En este caso particular no podemos confiar en eso, y la forma facil de ver esto, es que le estamos dando al cliente la oportunidad de poner el total, que locura! +No porque los las pruebas estén verdes, esto significa que la aplicación esta cubriendo la parte del negocio. Quería traer esto aquí porque en muchos casos es super fácil solo recibir parámetros y construir objetos desde esos parámetros pensando que siempre estamos recibiendo los datos correctos. En este caso particular no podemos confiar en eso, y la forma fácil de ver esto, es que le estamos dando al cliente la oportunidad de poner el total, ¡que locura! -Tenemos que añadir algunas validaciones o un callbacl para calcular el total de la orden y colocarlo entre el modelo. De esta forma ya no recibiremos mas el atributo del total y asi tener el control total sobre este atributo. Vamos a hacer esto: +Tenemos que añadir algunas validaciones o un callback para calcular el total de la orden y colocarlo entre el modelo. De esta forma ya no recibiremos más el atributo del total y asi tener el control total sobre este atributo. Vamos a hacer esto: Primer necesitamos algunas especificaciones a el modelo de la orden: @@ -495,7 +495,7 @@ class OrderTest < ActiveSupport::TestCase end ---- -Ahora podemos añadir la implementacion: +Ahora podemos añadir la implementación: [source,ruby] .app/models/order.rb @@ -519,7 +519,7 @@ class Order < ApplicationRecord end ---- -Hasta este punto nos aseguramos que el total esta siempre presente y es mayor o igual a cero. Esto significa que podemos quitar esas validaciones y quitar las especificaciones. Esperaré. Nuestros test deberian pasar por ahora: +Hasta este punto nos aseguramos que el total está siempre presente y es mayor o igual a cero. Esto significa que podemos quitar esas validaciones y quitar las especificaciones. Esperaré. Nuestras pruebas deberían pasar por ahora: [source,bash] ---- @@ -540,9 +540,9 @@ Finished in 0.542600s, 73.7191 runs/s, 110.5786 assertions/s. ---- -Oops! Obtuvimos un _failure_ (falla) en nuestra anterior prueba _Should have a positive total_. Es lógico desde que el total de la orden es calculado dinamicamente. Asi que podemos simplemente quitar esta prueba que ha quedado obsoleta. +¡Oops! Obtuvimos un _failure_ (falla) en nuestra anterior prueba _Should have a positive total_. Es lógico desde que el total de la orden es calculado dinámicamente. Así que podemos simplemente quitar esta prueba que ha quedado obsoleta. -Nustra prueba deberia pasar. Guardemos nuestros cambios: +Nuestra prueba debería pasar. Guardemos nuestros cambios: [source,bash] ---- @@ -550,11 +550,11 @@ $ git commit -am "Adds the create method for the orders controller" ---- -== Enviar email de confirmacion de la orden +== Enviar email de confirmación de la orden -La última seccion para este capítulo es para enviar el mail de confirmación al usuario que ordenó. Si quiere saltar esta parte e ir al siguiente capítulo hazlo. Esta seccion es mas como un calentamiento. +La última sección para este capítulo es para enviar el mail de confirmación al usuario que ordenó. Si quiere saltar esta parte e ir al siguiente capítulo hazlo. Esta sección es más como un calentamiento. -Tal vez estas familiarizado con la manipulacion de emails con Rails asi que intentaremos hacer esto fácil y rápido. Primero creamos el `order_mailer` con un email llamado `send_confirmation`: +Tal vez estas familiarizado con la manipulación de emails con Rails así que intentaremos hacer esto fácil y rápido. Primero creamos el `order_mailer` con un email llamado `send_confirmation`: [source,bash] ---- @@ -585,7 +585,7 @@ class OrderMailerTest < ActionMailer::TestCase end ---- -Yo simplemente copie/pegue las pruebas desde la documentacion y las adapte a nuesras necesidades. Ahora nos aseguramos que estas pruebas pasan. +Yo simplemente copie/pegue las pruebas desde la documentación y las adapte a nuestras necesidades. Ahora nos aseguramos que estas pruebas pasan. Primero, añadimos el método `OrderMailer#send_confirmation`: @@ -602,7 +602,7 @@ class OrderMailer < ApplicationMailer end ---- -Despues de añadir este código añadimos las vistas correspondientes. Es una buena practica incluir un texto de la versión como extra a la version HTML. +Después de añadir este código añadimos las vistas correspondientes. Es una buena práctica incluir un texto de la versión como extra a la versión HTML. [source,erb] @@ -636,7 +636,7 @@ $ rake test 40 runs, 66 assertions, 0 failures, 0 errors, 0 skips ---- -Y ahora, solo llamamos al método `OrderMailer#send_confirmation` en la accion de crear en el controlador de la orden: +Y ahora, solo llamamos al método `OrderMailer#send_confirmation` en la acción de crear en el controlador de la orden: [source,ruby] .app/controllers/api/v1/orders_controller.rb @@ -666,7 +666,7 @@ $ rake test 40 runs, 66 assertions, 0 failures, 0 errors, 0 skips ---- -Hagamos commit a todo para ya que esta completa esta sección: +Hagamos commit a todo para ya que está completa esta sección: [source,bash] ---- @@ -683,11 +683,11 @@ $ git merge chapter07 == Conclusión -Eso es! Lo hiciste! Puedes aplaudirte. Se que fue un largo tiemp pero creeme estas casi terminando. +¡Eso es! ¡Lo hiciste! Puedes aplaudirte. Se que fue un largo tiempo pero créeme estas casi terminando. -En siguientes capitulos continuaremos trabajando en la plantilla de la orden y añadir validaciones cuando se hace una orden. Algunos escenarios son: +En siguientes capítulos continuaremos trabajando en la plantilla de la orden y añadir validaciones cuando se hace una orden. Algunos escenarios son: -* Que pasa cuando los productos no estan disponibles? -* Reducir la cantidad de los productos en progreso cuadno se esta ordenando +* Que pasa cuando los productos no están disponibles? +* Reducir la cantidad de los productos en progreso cuando se está ordenando -El siguiente capítulo sera corto, pero es muy importante para la salud de la aplicación. Asi que no te lo saltes. +El siguiente capítulo será corto, pero es muy importante para la salud de la aplicación. así que no te lo saltes. diff --git a/rails6/es/chapter08-improve-orders.adoc b/rails6/es/chapter08-improve-orders.adoc index ea5e255..1c70856 100644 --- a/rails6/es/chapter08-improve-orders.adoc +++ b/rails6/es/chapter08-improve-orders.adoc @@ -1,12 +1,12 @@ [#chapter08-improve_orders] = Mejorando las ordenes -En el capítulo anterior extendimos nuestra API para ordenar y enviar email de confirmación al usuario (solo para mejorar la experiencia del usuario). Este capitulo cuida algunas validaciones en el modelo de la orden, solo para asegurarse que se puede ordenar, algo como: +En el capítulo anterior extendimos nuestra API para ordenar y enviar email de confirmación al usuario (solo para mejorar la experiencia del usuario). Este capítulo cuida algunas validaciones en el modelo de la orden, solo para asegurarse que se puede ordenar, algo como: - Reducir la cantidad del producto actual cuando se genera una orden - ¿Que pasa cuando no hay productos disponibles? -Probablemente necesitaremos actualiza un poco la salida JSON para las ordenes pero no estropeemos las cosas. +Probablemente necesitaremos actualiza un poco la salida JSON para las ordenes, pero no estropeemos las cosas. Asi que ahora que tenemos todo claro podemos ensuciarnos las manos. Puedes clonar el proyecto hasta este punto con: @@ -24,17 +24,17 @@ $ git checkout -b chapter08 == Decrementando la cantidad del producto -En esta primera parada vamos a trabajar en la actualizacion de la cantidad de productopara asegurar que cada pedido entregue la orden real. -Actualmente el modelo `product` no tiene un atributo `quantity`. ASo que vamos a hacer eso: +En esta primera parada vamos a trabajar en la actualización de la cantidad de producto para asegurar que cada pedido entregue la orden real. +Actualmente el modelo `product` no tiene un atributo `quantity`. Así que vamos a hacer eso: [source,bash] ---- $ rails generate migration add_quantity_to_products quantity:integer ---- -Espera, no corras las migraciones ahora. Le haremos unas pequeñas modificaciones. Como una buena practica me gusta añadir los valores por defecto a la base de datos solo para asegurarme que no me equivoco con valores `null`. ¡Este es un caso perfecto! +Espera, no corras las migraciones ahora. Le haremos unas pequeñas modificaciones. Como una buena práctica me gusta añadir los valores por defecto a la base de datos solo para asegurarme que no me equivoco con valores `null`. ¡Este es un caso perfecto! -Tu archivo de migración deberia lucir como esto: +Tu archivo de migración debería lucir como esto: [source,ruby] .db/migrate/20190621105101_add_quantity_to_products.rb @@ -53,7 +53,7 @@ Ahora podemos migrar la base de datos: $ rake db:migrate ---- -Y no olvidemos actualizar los _fixtures_ añadiendo el campo *quantity* (Yo elegi el valor `5` de manera aleatoria). +Y no olvidemos actualizar los _fixtures_ añadiendo el campo *quantity* (Yo elegí el valor `5` de manera aleatoria). [source,yml] .test/fixtures/products.yml @@ -72,11 +72,11 @@ another_tv: ---- -Es tiempo ahora de reducir la cantidad de productos meintras una `Orden` esta siendo procesada. La primera cosa probablemente que vien a la mente es hacerlo en el modelo `Order`. Esto es un misterio común. +Es tiempo ahora de reducir la cantidad de productos mientras una `Orden` está siendo procesada. La primera cosa probablemente que viene a la mente es hacerlo en el modelo `Order`. Esto es un misterio común. -Cuando trabajas con asociaciones _Many-to-Many_ (muchos a muchos), nos olvidamos completamente del modelo de union que en este caso es `Placement`. `Placement` es el mejor lugar para gestionar esto porque tiene accesos la orden y al producto. De esta forma, podemos facilmente reducir el stock de el producto. +Cuando trabajas con asociaciones _Many-to-Many_ (muchos a muchos), nos olvidamos completamente del modelo de unión que en este caso es `Placement`. `Placement` es el mejor lugar para gestionar esto porque tiene accesos la orden y al producto. De esta forma, podemos fácilmente reducir el stock del producto. -Antes de empezar a implementar código, necesitamos cambiar la forma que manipulamos la creación de ordenes porque ahora tenemos que aceptar la cantidad para cada producto. Si recuerdas estamos esperando por una tabla de indenfiricadores de producto. Intentaré mantener las cosas simples y enviar una tabla Hash con las llaves `product_id` y `quantity`. +Antes de empezar a implementar código, necesitamos cambiar la forma que manipulamos la creación de ordenes porque ahora tenemos que aceptar la cantidad para cada producto. Si recuerdas estamos esperando por una tabla de identificadores de producto. Intentaré mantener las cosas simples y enviar una tabla Hash con las llaves `product_id` y `quantity`. Un ejemplo rápido podria ser algo como esto: @@ -88,7 +88,7 @@ product_ids_and_quantities = [ ] ---- -Esto se ponde dificl pero quedate conmigo. Vamos primero a construor algunas pruebas: +Esto se pondrá difícil pero quédate conmigo. Vamos primero a construir algunas pruebas: [source,ruby] .test/models/order_test.rb @@ -130,7 +130,7 @@ class Order < ApplicationRecord end ---- + -Y si coremos nuestras pruebas, deberian estar bien y en verde: +Y si corremos nuestras pruebas, deberían estar bien y en verde: [source,bash] ---- @@ -139,7 +139,7 @@ $ rake test 40 runs, 60 assertions, 0 failures, 0 errors, 0 skips ---- -Lo que es `build_placements_with_product_ids_and_quantities` hará la colocacion de objetos y luego ejecutará el método `save` para la ordenar todo sera insertada en la base de datos. Un último paso antes de guardar esto es actualizar la prueba `orders_controller_test` junto con esta implementación. +Lo que es `build_placements_with_product_ids_and_quantities` hará la colocación de objetos y luego ejecutará el método `save` para la ordenar todo será insertada en la base de datos. Un último paso antes de guardar esto es actualizar la prueba `orders_controller_test` junto con esta implementación. Primero actualizamos el archivo `orders_controller_test`: @@ -203,9 +203,9 @@ end ---- -Nota que tambien modifique el método `OrdersController#order_params`. +Nota que también modifique el método `OrdersController#order_params`. -Por último pero no menos importante, necesitamos actualizar el archivo que fabrica productos para asignar un valor alto de cantidad para tener algunos productos en stock. +Por último, pero no menos importante, necesitamos actualizar el archivo que fabrica productos para asignar un valor alto de cantidad para tener algunos productos en stock. Hagamos commit de estos cambios y continuemos: @@ -215,14 +215,14 @@ $ git add . $ git commit -m "Allows the order to be placed along with product quantity" ---- -¿Notaste que no estamos guardando la cantidad por cada producto en ningun lado? Esta no es la forma de darle seguimiento. Esto puede ser reparado facilemente. Solo añadamos un atributo `quantity` a el modelo `Placement`. Asi de este modo para cada producto guardaremos su cantidad correspondiente. Vamos a iniciar creando la migración: +¿Notaste que no estamos guardando la cantidad por cada producto en ningún lado? Esta no es la forma de darle seguimiento. Esto puede ser reparado fácilmente. Solo añadamos un atributo `quantity` a el modelo `Placement`. De este modo para cada producto guardaremos su cantidad correspondiente. Vamos a iniciar creando la migración: [source,bash] ---- $ rails generate migration add_quantity_to_placements quantity:integer ---- -Como con el atrubuto para la cantidad del producto deberiamos añadir un valor por defecto igual a 0. Recuerda que esto es opcional pero me gusta este enfoque. El archivo de migración deberia lucir así: +Como con el atributo para la cantidad del producto deberíamos añadir un valor por defecto igual a 0. Recuerda que esto es opcional, pero me gusta este enfoque. El archivo de migración debería lucir así: [source,ruby] .db/migrate/20190621114614_add_quantity_to_placements.rb @@ -277,7 +277,7 @@ class Order < ApplicationRecord end ---- -Ahora nuestras pruebas deberian pasar: +Ahora nuestras pruebas deberían pasar: [source,bash] ---- @@ -293,9 +293,9 @@ Vamos a guardar los cambios: $ git add . && git commit -m "Adds quantity to placements" ---- -=== Eltendiendo el modelo Placement +=== Entendiendo el modelo Placement -Est tiempo de actualizar la cantidad del producto cada que la orden es guardada, o mas exacto cada que el placement (colocación) es creado. A fin de lograr esto vamos a añadir un metodo y entonces conectarlo con el callback `after_create`. +Es tiempo de actualizar la cantidad del producto cada que la orden es guardada, o más exacto cada que el placement (colocación) es creado. A fin de lograr esto vamos a añadir un método y entonces conectarlo con el callback `after_create`. [source,ruby] .test/models/placement_test.rb @@ -316,7 +316,7 @@ class PlacementTest < ActiveSupport::TestCase end ---- -La implementación es bastante facil como se muestra a continación: +La implementación es bastante fácil como se muestra a continuación: [source,ruby] .app/models/placement.rb @@ -339,13 +339,13 @@ Hagamos _commit_ a nuestros cambios: $ git commit -am "Decreases the product quantity by the placement quantity" ---- -== Validar la canridad de productos +== Validar la cantidad de productos -Desde el comienzo del capítulo, tenemos añadido el atributo `quantity` a el modelo del producto. Es ahora tiempo para validar si la cantidad de producto es sificiente para conciliar la orden. A fin de que hagamos las cosas mas intersantes, vamos a hacer usando un validador personalizado. +Desde el comienzo del capítulo, tenemos añadido el atributo `quantity` a el modelo del producto. Es ahora tiempo para validar si la cantidad de producto es suficiente para conciliar la orden. A fin de que hagamos las cosas más interesantes, vamos a hacer usando un validador personalizado. NOTE: puedes consultar https://guides.rubyonrails.org/active_record_validations.html#performing-custom-validations[la documentación]. -Primero necesitamos añadir un directorio `validators` en el directorio `app` (Rails lo incluira por lo que no necesitamos preocuparnos de cargarlo). +Primero necesitamos añadir un directorio `validators` en el directorio `app` (Rails lo incluirá por lo que no necesitamos preocuparnos de cargarlo). [source,bash] ---- @@ -353,7 +353,7 @@ $ mkdir app/validators $ touch app/validators/enough_products_validator.rb ---- -Antes que borremos cualquier linea de código, necesitamos asegurarnos de añadir especificaciones a el modelo `Order` par revisar si la orden puede ser realizada. +Antes que borremos cualquier línea de código, necesitamos asegurarnos de añadir especificaciones a el modelo `Order` para revisar si la orden puede ser realizada. [source,ruby] .test/models/order_test.rb @@ -370,9 +370,9 @@ class OrderTest < ActiveSupport::TestCase end ---- -Como puedes ver en la especificación, primero nos aseguramos que `placement_2` este tratando de pedir mas productos de los que stan disponibles, asi que en este caso suponemos que la `order` (orden) no es válida. +Como puedes ver en la especificación, primero nos aseguramos que `placement_2` este tratando de pedir mas productos de los que están disponibles, así que en este caso suponemos que la `order` (orden) no es válida. -La prueba por ahora deberia fallar, vamos a convertirla en verde añadiendo el código del validador: +La prueba por ahora debería fallar, vamos a convertirla en verde añadiendo el código del validador: [source,ruby] .app/validators/enough_products_validator.rb @@ -389,7 +389,7 @@ class EnoughProductsValidator < ActiveModel::Validator end ---- -Manipulo para añandir el mensaje a cada uno de los producto que estan fuera de stock, pero puede manejarlo diferente si quieres. Ahora solamente necesito añadir el validador al modelo `Order` de esta forma: +Manipulo para añadir el mensaje a cada uno de los producto que están fuera de stock, pero puede manejarlo diferente si quieres. Ahora solamente necesito añadir el validador al modelo `Order` de esta forma: [source,ruby] .app/models/order.rb @@ -402,7 +402,7 @@ class Order < ApplicationRecord end ---- -Gurademos los cambios: +Guardemos los cambios: [source,bash] ---- @@ -411,7 +411,7 @@ $ git add . && git commit -m "Adds validator for order with not enough products == Actualizando el total -Notaste que el `total` esta siendo calculado incorrectamente, porque actualmente este está añadiendo el precio para los productos en la orden independientemente de la cantidad solicitada. Dejame añadir el código para aclarar el problema: +Notaste que el `total` está siendo calculado incorrectamente, porque actualmente este está añadiendo el precio para los productos en la orden independientemente de la cantidad solicitada. Déjame añadir el código para aclarar el problema: Actualmente en el modelo `order` tenemos este método para calcular el monto a pagar: @@ -427,7 +427,7 @@ class Order < ApplicationRecord end ---- -Ahora en lugar de calcular el `total` solo añadiendo el precio del producto necesitamos multiplicarlo por la cantidad. Asi que vamos a actualizar las especificaciones primero: +Ahora en lugar de calcular el `total` solo añadiendo el precio del producto necesitamos multiplicarlo por la cantidad. Así que vamos a actualizar las especificaciones primero: [source,ruby] .test/models/order_test.rb @@ -449,7 +449,7 @@ class OrderTest < ActiveSupport::TestCase end ---- -Y la implementacion es muy sencilla: +Y la implementación es muy sencilla: [source,ruby] .app/models/order.rb @@ -465,7 +465,7 @@ class Order < ApplicationRecord end ---- -Y las especificaciones deberian ser verdes: +Y las especificaciones deberían ser verdes: [source,bash] ---- @@ -481,7 +481,7 @@ Vamos a guardar los cambios: $ git commit -am "Updates the total calculation for order" ---- -Y asi es como llegamos al final de nuesro capítulo, es tiempo de aplicar todas nuestras modificaciones a la rama master haciendo un _merge_: +Y así es como llegamos al final de nuestro capítulo, es tiempo de aplicar todas nuestras modificaciones a la rama master haciendo un _merge_: [source,bash] ---- @@ -491,6 +491,6 @@ $ git merge chapter08 == Conclusión -Oh, ahi tienes! Dejame felicirater! Es un lagro camingo desde el pimer capítulo. Pero estas un paso mas cerca, De echo, el próximo capítulo sera el último. Así que trata de aprovecharlo al máximo. +¡Oh, ahi tienes! ¡Déjame felicitarte! Es un largo camino desde el primer capítulo. Pero estas un paso más cerca, De hecho, el próximo capítulo será el último. Así que trata de aprovecharlo al máximo. -El último capítulo se enfocará en la forma de optimizar la API usando paginado, caché y tareas en segundo plano. Así que abrochate el cinturón, va a ser un viaje agitado. \ No newline at end of file +El último capítulo se enfocará en la forma de optimizar la API usando paginado, caché y tareas en segundo plano. Así que abróchate el cinturón, va a ser un viaje agitado. diff --git a/rails6/es/chapter09-optimization.adoc b/rails6/es/chapter09-optimization.adoc index f76753e..c9e6e68 100644 --- a/rails6/es/chapter09-optimization.adoc +++ b/rails6/es/chapter09-optimization.adoc @@ -1,16 +1,16 @@ [#chapter09-optimization] = Optimizaciones -Bienvenido a el ultimo capítulo de este libro. Ha sido un largo camino, pero estas solo a un paso de el final. En el capítulo anterior, completamos el modelado del modelo de la orden. Podriamos decir que el proyecto esta finalizado pero quiero cubrir algunos detalles importante sobre la optimización. Los temas que discutiremos seran: +Bienvenido a el último capítulo de este libro. Ha sido un largo camino, pero estas solo a un paso del final. En el capítulo anterior, completamos el modelado del modelo de la orden. Podríamos decir que el proyecto está finalizado, pero quiero cubrir algunos detalles importantes sobre la optimización. Los temas que discutiremos serán: -* paginacion +* paginación * caché * optimización de las consultas SQL -* la activacion de CORS +* la activación de CORS -Trataré de ir tan lejos como pueda intentando cubrir algunos escenarios comunes. Espero que estos escenarios sean utiles para algunos de tus proyectos. +Trataré de ir tan lejos como pueda intentando cubrir algunos escenarios comunes. Espero que estos escenarios sean útiles para algunos de tus proyectos. -Si tu empiezas leyendo hasta este punto, probanlemente quieras el código, puedes clonarlo con esto: +Si tu empiezas leyendo hasta este punto, probablemente quieras el código, puedes clonarlo con esto: [source,bash] ---- @@ -27,30 +27,30 @@ $ git checkout -b chapter09 == Paginación -Una estrategia muy común para optimizar un arreglo de registros desde la base de datos, es cargar solo algunos paginandolos y si tu estas familiarizado con esta técnica sabes que en Rails es realimente facil lograrlos sobretodo si estas usando https://github.com/mislav/will_paginate[will_paginate] ó https://github.com/amatsuda/kaminari[kaminari]. +Una estrategia muy común para optimizar un arreglo de registros desde la base de datos, es cargar solo algunos paginándolos y si tu estas familiarizado con esta técnica sabes que en Rails es realimente fácil lograrlos sobre todo si estas usando https://github.com/mislav/will_paginate[will_paginate] ó https://github.com/amatsuda/kaminari[kaminari]. -Entonces solo la parte dificil aqui es como soponemos manipular la salida JSON dando la suficiente información al cliente sobre como esta paginado el arreglo. Si recuerdas el primer capítulo compartí algunos recursos y practicas que iba a seguir aquí. Una de ellas fue http://jsonapi.org/ que es una pagina de mis favoritas. +Entonces solo la parte difícil aquí es como suponemos manipular la salida JSON dando la suficiente información al cliente sobre como esta paginado el arreglo. Si recuerdas el primer capítulo compartí algunos recursos y prácticas que iba a seguir aquí. Una de ellas fue http://jsonapi.org/ que es una página de mis favoritas. -Si leemos la seccion de formato encontraremos una sub seccion llamada http://jsonapi.org/format/#document-structure-top-level[Top Level] y en algunas palabras se mencionan algunas cosas sobre paginación: +Si leemos la sección de formato encontraremos una sub sección llamada http://jsonapi.org/format/#document-structure-top-level[Top Level] y en algunas palabras se mencionan algunas cosas sobre paginación: > "meta": meta-información sobre un recurso, como la paginación. -Esto no es muy descriptivo pero al menos tenemos una pista de que buscar despues sobre la implementación de la paginación, pero no te preocupues que es exactamente a donde estamos llendo ahora. +Esto no es muy descriptivo pero al menos tenemos una pista de que buscar después sobre la implementación de la paginación, pero no te preocupes que es exactamente a donde estamos yendo ahora. Comencemos con la lista de `products`. === Productos -Estamos iniciano bien y facil paginando la lista de producto ya que no tenemos ningún tipo de restricción de acceso que nos lleve a pruebas más fáciles. +Estamos iniciando bien y fácil paginando la lista de producto ya que no tenemos ningún tipo de restricción de acceso que nos lleve a pruebas más fáciles. -Primero necesiatamos añadir la gema https://github.com/amatsuda/kaminari[kaminari] a nuestro `Gemfile`: +Primero necesitamos añadir la gema https://github.com/amatsuda/kaminari[kaminari] a nuestro `Gemfile`: [source,bash] ---- $ bundle add kaminari ---- -Ahora podemos ir a la acción `index` en el controlador `products_controller` y añadir los mpetodos de paginación como se señala en la documentación: +Ahora podemos ir a la acción `index` en el controlador `products_controller` y añadir los métodos de paginación como se señala en la documentación: [source,ruby] .app/controllers/api/v1/products_controller.rb @@ -68,7 +68,7 @@ class Api::V1::ProductsController < ApplicationController end ---- -Hasta ahora la unica cosa que cambio es la consuta a la base de datos que justamente limita el resuldato a 25 por pagina que es el valor por defecto. Pero no tenemos añadida información extra a la salida JSON. +Hasta ahora la única cosa que cambio es la consulta a la base de datos que justamente limita el resultado a 25 por página que es el valor por defecto. Pero no tenemos añadida información extra a la salida JSON. Necesitamos proveer la información de paginación en el tag `meta` de la siguiente forma: @@ -87,7 +87,7 @@ Necesitamos proveer la información de paginación en el tag `meta` de la siguie } ---- -Ahora tenemos la estructura final para el tag `meta` que necesitamos en la salida de la repuesta JSON. Vamos primer a añadir algnas especificaciones-: +Ahora tenemos la estructura final para el tag `meta` que necesitamos en la salida de la repuesta JSON. Vamos primer a añadir algunas especificaciones-: [source,ruby] .test/controllers/api/v1/products_controller_test.rb @@ -109,7 +109,7 @@ class Api::V1::ProductsControllerTest < ActionDispatch::IntegrationTest end ---- -La prueba que acabamos de añador debería fallar: +La prueba que acabamos de añadir debería fallar: [source,bash] ---- @@ -121,7 +121,7 @@ Api::V1::ProductsControllerTest#test_should_show_products [test/controllers/api/ Expected nil to not be nil. ---- -Vamos a añador informaciónde paginación. Construiremos una parte de esto en _concerns_ para fragmentar mejor nuestro código: +Vamos a añadir información de paginación. Construiremos una parte de esto en _concerns_ para fragmentar mejor nuestro código: [source,ruby] .app/controllers/concerns/paginable.rb @@ -177,7 +177,7 @@ $ rake test 42 runs, 65 assertions, 0 failures, 0 errors, 0 skips ---- -Ahora tenemos tenemos echa una super optimicación para la ruta de lista de productos, depende del cliente para recuperar el parametro de la `page` (página) para los registros. +Ahora tenemos echa una super optimización para la ruta de lista de productos, depende del cliente para recuperar el parámetro de la `page` (página) para los registros. Vamos a hacer estos cambios y continuar con la lista de comandos. @@ -190,7 +190,7 @@ $ git commit -m "Adds pagination for the products index action to optimize respo === Lista de ordenes -Ahora es tiempo de hacer exactamente lo mismo para el enpoint de la lista de `orders` que deberia ser realmente fácil de implementar. Pero primero vamos a añadir algunas especificaciones al archivo `orders_controller_test.rb`: +Ahora es tiempo de hacer exactamente lo mismo para el enpoint de la lista de `orders` que debería ser realmente fácil de implementar. Pero primero vamos a añadir algunas especificaciones al archivo `orders_controller_test.rb`: [source,ruby] .test/controllers/api/v1/orders_controller_test.rb @@ -213,7 +213,7 @@ class Api::V1::OrdersControllerTest < ActionDispatch::IntegrationTest end ---- -Como ya deberias saber, nuestras pruebas no estarán pasando: +Como ya deberías saber, nuestras pruebas no estarán pasando: [source,bash] ---- @@ -225,7 +225,7 @@ Api::V1::OrdersControllerTest#test_should_show_orders [test/controllers/api/v1/o Expected nil to not be nil. ---- -Cambienmos el rojo en verde: +Cambiemos el rojo en verde: [source,ruby] @@ -255,7 +255,7 @@ class Api::V1::OrdersController < ApplicationController end ---- -Ahora todas las pruebas deberian pasar bien y en verde: +Ahora todas las pruebas deberían pasar bien y en verde: [source,bash] ---- @@ -265,7 +265,7 @@ $ rake test ---- -Hafamos un commit, por que se viene una refactorización: +Hagamos un commit, por que se viene una refactorización: [source,bash] ---- @@ -275,7 +275,7 @@ $ git commit -am "Adds pagination for orders index action" === Refactorizando la paginación -Si tu has segido este tutorial o si tienes experiencia previa como desarrollador Rails, probeblemente te guste mantener las cosas SECAS. Es posible que hayas notado que el código que acabamos de escribir está ducplicado. Pienso que es una buen hábito hacer limpieza del código un poco cuando la funcionalidad esta implementada. +Si tú has seguido este tutorial o si tienes experiencia previa como desarrollador Rails, probablemente te guste mantener las cosas SECAS. Es posible que hayas notado que el código que acabamos de escribir está duplicado. Pienso que es un buen hábito hacer limpieza del código un poco cuando la funcionalidad esta implementada. Primero limpiaremos estas pruebas que duplicamos en los archivos `orders_controller_test.rb` y `products_controller_test.rb`: @@ -287,7 +287,7 @@ assert_not_nil json_response.dig(:links, :next) assert_not_nil json_response.dig(:links, :prev) ---- -Para factorizarlo, vamos a mover estas afirmaciones a el archivo `test_helper.rb` en un metodo que usaremos: +Para factorizarlo, vamos a mover estas afirmaciones a el archivo `test_helper.rb` en un método que usaremos: [source,ruby] .test/test_helper.rb @@ -304,7 +304,7 @@ class ActiveSupport::TestCase end ---- -Este metodo puede ahora ser usado para remplazar las cuatro afirmaciones en los archivos `orders_controller_test.rb` y `products_controller_test.rb`: +Este método puede ahora ser usado para remplazar las cuatro afirmaciones en los archivos `orders_controller_test.rb` y `products_controller_test.rb`: [source,ruby] .test/controllers/api/v1/orders_controller_test.rb @@ -344,7 +344,7 @@ $ rake test ---- -Ahora tenemos terminado esta simple refactorización para las pruebas, podemos movernos a la implementacion de la paginación para loc controladores y limpiar cosas. Si tu recuerdas la acción de indexación para ambos controladores producto y orden, ambos tienen el mismo formato de paginación. Asiq ue vamos a mover esta logica dentro de un método llamado `get_links_serializer_options` en el archivo `paginable.rb`, así podemos acceder a el desde cualqueir controlador que necesite paginación. +Ahora tenemos terminado esta simple refactorización para las pruebas, podemos movernos a la implementación de la paginación para los controladores y limpiar cosas. Si tu recuerdas la acción de indexación para ambos controladores producto y orden, ambos tienen el mismo formato de paginación. Así que vamos a mover esta lógica dentro de un método llamado `get_links_serializer_options` en el archivo `paginable.rb`, así podemos acceder a el desde cualquier controlador que necesite paginación. [source,ruby] @@ -367,7 +367,7 @@ module Paginable end ---- -Y ahora podemos sustuir el has de paginacion en ambos controladores para el metodo. Justo así: +Y ahora podemos sustituir el hash de paginación en ambos controladores para el método. Justo así: [source,ruby] .app/controllers/api/v1/orders_controller.rb @@ -409,7 +409,7 @@ class Api::V1::ProductsController < ApplicationController end ---- -Si corres las especificaciones para cada archivo deberian estar todas bien y verdes: +Si corres las especificaciones para cada archivo deberían estar todas bien y verdes: [source,bash] ---- @@ -418,7 +418,7 @@ $ rake test 42 runs, 71 assertions, 0 failures, 0 errors, 0 skips ---- -Este deberia ser un buen momento para hacer un _commit_ a los cambios y movernos a la siguiente seccion sobre el caché: +Este debería ser un buen momento para hacer un _commit_ a los cambios y movernos a la siguiente sección sobre el caché: [source,bash] ---- @@ -427,9 +427,9 @@ $ git commit -am "Factorize pagination" == Almacenamiento en cache del API -Actualmente esta es una implementación para almacenar en caché la gema `fast_jsonapi` que es realmente facil de manipular. A pesar de que en la ultima version de la gema, esta implementación puede cambiar, esta hace el trabajo. +Actualmente esta es una implementación para almacenar en caché la gema `fast_jsonapi` que es realmente fácil de manipular. A pesar de que en la última versión de la gema, esta implementación puede cambiar, esta hace el trabajo. -Si hacemos una petición a la lista de productos, notaremos que el tiempode respuesta toma cerca de 174 milisegundos usando cURL: +Si hacemos una petición a la lista de productos, notaremos que el tiempo de respuesta toma cerca de 174 milisegundos usando cURL: [source,bash] ---- @@ -437,9 +437,9 @@ $ curl -w 'Total: %{time_total}\n' -o /dev/null -s http://localhost:3000/api/v1/ Total: 0,137088 ---- -NOTE: La opción `-w` nos permite recuperar el tiepo de petición, `-o` redirecciona la respuesa a un archivo y `-s` esconde la pantalla de cURL +NOTE: La opción `-w` nos permite recuperar el tiempo de petición, `-o` redirecciona la respuesta a un archivo y `-s` esconde la pantalla de cURL -Añadiendo solo una linea a la clase `ProductSerializer`, veremos un significante incremento en el tiempo de respuesta! +¡Añadiendo solo una línea a la clase `ProductSerializer`, veremos un significante incremento en el tiempo de respuesta! [source,ruby] .app/serializers/order_serializer.rb @@ -468,7 +468,7 @@ class UserSerializer end ---- -Y esto es todo! Vamos a revisar la mejora: +¡Y esto es todo! Vamos a revisar la mejora: [source,bash] ---- @@ -478,7 +478,7 @@ $ curl -w 'Total: %{time_total}\n' -o /dev/null -s http://localhost:3000/api/v1/ Total: 0,032341 ---- -Asi que fuimos de 174 ms a 21 ms. La mejora por lo tanto es enorme! Vamos a guardar nuestros cambios una última vez: +Así que fuimos de 174 ms a 21 ms. ¡La mejora por lo tanto es enorme! Vamos a guardar nuestros cambios una última vez: [source,ruby] ---- @@ -487,11 +487,11 @@ $ git commit -am "Adds caching for the serializers" == Consultas N+1 -Consultas N+1* son una herida donde podemos tener un enrome impacto en el rendimiento de una aplicación. Este fenomeno a menudo ocurre cuando usamos **ORM** porque este genera **automaticamente** consultas SQL por nosotros. Esta herramienta tan practica es de doble filo porque puede genera un **largo numero** de consultas SQL. +Consultas N+1* son una herida donde podemos tener un enrome impacto en el rendimiento de una aplicación. Este fenómeno a menudo ocurre cuando usamos **ORM** porque este genera **automáticamente** consultas SQL por nosotros. Esta herramienta tan practica es de doble filo porque puede genera un **largo número** de consultas SQL. -Algo que debemos saber sobre las consultas SQL es que es mejor limitar su numero. En otras palabras, una repuesta larga es a menudo mas eficiente que cientos de pequeñas. +Algo que debemos saber sobre las consultas SQL es que es mejor limitar su número. En otras palabras, una repuesta larga es a menudo más eficiente que cientos de pequeñas. -Aquí esta un ejemplo cuando queremos recuperar todos los usuarios que ya tiene un producto creado. Abre la consola de Rails con `rails console` y ejecuta el siguiente código Ruby: +Aquí está un ejemplo cuando queremos recuperar todos los usuarios que ya tiene un producto creado. Abre la consola de Rails con `rails console` y ejecuta el siguiente código Ruby: [source,ruby] ---- @@ -500,21 +500,21 @@ Product.all.map { |product| product.user } La consola interactiva de rails nos muestra consultas SQL que son generadas. Mira por ti mismo: -Vemos aqui que un largo numero de peticiones son generadas: +Vemos aquí que un largo número de peticiones son generadas: - `Product.all` = 1 petición para recuperar los productos - `product.user` = 1 petición `SELECT "users".* FROM "users" WHERE "users". "id" =? LIMIT 1 [[[["id", 1]]]` por producto recuperado -Por lo tanto el nombre "petición N+1" es ya que una solicitud se reailza a travéz de un enlace sencudario. +Por lo tanto el nombre "petición N+1" es ya que una solicitud se realiza a través de un enlace secundario. -Pordemos arreglar esto simplemente usando `includes`. `Includes` **pre-cargará** los objetos secundarios en una simple petición. Es muy facil de usar. Si repetimos el ejemplo anterior. Este es el resultado: +Podemos arreglar esto simplemente usando `includes`. `Includes` **pre-cargará** los objetos secundarios en una simple petición. Es muy fácil de usar. Si repetimos el ejemplo anterior. Este es el resultado: [source,ruby] ---- Product.includes(:user).all.map { |product| product.user } ---- -La consola interactiva de Rails nos muestra las consultas SQL que son generadas. Mira por tí mismo: +La consola interactiva de Rails nos muestra las consultas SQL que son generadas. Mira por ti mismo: [source,sql] ---- @@ -522,11 +522,11 @@ Product Load (0.3ms) SELECT "products".* FROM "products" User Load (0.8ms) SELECT "users".* FROM "users" WHERE "users"."id" IN (?, ?, ?) [["id", 28], ["id", 29], ["id", 30]] ---- -Rails crea una regunda petición que recuperará **todos** los usuarios a la vez. +Rails crea una segunda petición que recuperará **todos** los usuarios a la vez. === Prevencion de peticiones N + 1 -Imageina que queremos añadir propietarios de los productos a la ruta `/products`. Ya hemos visto que con la librería `fast_jsonapi` es muy facil de hacer esto: +Imagina que queremos añadir propietarios de los productos a la ruta `/products`. Ya hemos visto que con la librería `fast_jsonapi` es muy fácil de hacer esto: [source,ruby] .app/controllers/api/v1/products_controller.rb @@ -544,14 +544,14 @@ class Api::V1::ProductsController < ApplicationController end ---- -Ahora vamos a hacer ua petición con cURL. Te recuerdo que nosotros debimos obetener un token de autenticación antes de acceder a la pagina. +Ahora vamos a hacer ua petición con cURL. Te recuerdo que nosotros debimos obtener un token de autenticación antes de acceder a la pagina. [source,bash] ---- $ curl -X POST --data "user[email]=ockymarvin@jacobi.co" --data "user[password]=locadex1234" http://localhost:3000/api/v1/tokens ---- -NOTE: "ockymarvin@jacobi.co" corresponde a un suaurio creado en mi aplicación con el _seed_. En tu caso, probablemente fue diferente del mio desde que usamos la librería Faker. +NOTE: "ockymarvin@jacobi.co" corresponde a un usurio creado en mi aplicación con el _seed_. En tu caso, probablemente fue diferente del mío desde que usamos la librería Faker. Con la ayuda de el token obtenido, ahora podemos hacer una petición para acceder a los productos @@ -560,7 +560,7 @@ Con la ayuda de el token obtenido, ahora podemos hacer una petición para accede $ curl --header "Authorization=ey..." http://localhost:3000/api/v1/products ---- -DLo mas probable es que veas varias respuestas en la consola Rails corriendo el servidor web. +Lo más probable es que veas varias respuestas en la consola Rails corriendo el servidor web. [source,sql] ---- @@ -581,7 +581,7 @@ Processing by Api::V1::ProductsController#index as JSON CACHE User Load (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 36], ["LIMIT", 1]] ---- -Es por lo tanto desafortunadamente **muy fácil** para crear consultas N+1. Afortunadamentes, esta es una gema que nos permite **alertar** cuando este tipo de situación ocurre: https://github.com/flyerhzm/bullet[Bullet]. Bullet nos notificará (por correo, http://growl.info/[growl notification], https://slack.com[Slack], consola, etc...) cuando encuentra una peticion N+1. +Es por lo tanto desafortunadamente **muy fácil** para crear consultas N+1. Afortunadamentes, esta es una gema que nos permite **alertar** cuando este tipo de situación ocurre: https://github.com/flyerhzm/bullet[Bullet]. Bullet nos notificará (por correo, http://growl.info/[growl notification], https://slack.com[Slack], consola, etc...) cuando encuentra una petición N+1. Para instalarla, vamos añadir la _gema_ al _GemFile_ @@ -591,7 +591,7 @@ $ bundle add bullet --group development ---- -Y eso es suficiente para actializar la configuracion de nuestra aplicacion para el entorno de desarrollo. En nuestro caso solo activaremos el modo `rails_logger` el cual sera mostrado: +Y eso es suficiente para actualizar la configuración de nuestra aplicación para el entorno de desarrollo. En nuestro caso solo activaremos el modo `rails_logger` el cual será mostrado: [source,ruby] .config/environments/development.rb @@ -605,14 +605,14 @@ Rails.application.configure do end ---- -Reinicia el servidor web y reinicia la ultima peticion con cURL: +Reinicia el servidor web y reinicia la última petición con cURL: [source,bash] ---- $ curl --header "Authorization=ey..." http://localhost:3000/api/v1/products ---- -Y mira en la consola de Rails. Bullet nos dice que tiene justmente una peticion N+1 detectada. +Y mira en la consola de Rails. Bullet nos dice que tiene justamente una petición N+1 detectada. ---- GET /api/v1/products @@ -621,7 +621,7 @@ USE eager loading detected Add to your finder: :includes => [:user] ---- -Incluos nos dice como corregirla: +Incluso nos dice como corregirla: > Add to your search engine:: includes => [: user] @@ -648,7 +648,7 @@ class Api::V1::ProductsController < ApplicationController end ---- -Ahí tienes! Es tiempo de hacer nuestro _commit_. +¡Ahí tienes! Es tiempo de hacer nuestro _commit_. [source,bash] ---- @@ -657,25 +657,25 @@ $ git commit -am "Add bullet to avoid N+1 query" == Activación de CORS -En esta última sección, te hablaré sobre un ultimo problema que probablemente encontraste si tu has trabajado con tu propia API. +En esta última sección, te hablaré sobre un último problema que probablemente encontraste si tú has trabajado con tu propia API. -Cuando haces una petición a un sitio externo (por ejemplo una peticion via AJAX), encontraras un error de este tipo: +Cuando haces una petición a un sitio externo (por ejemplo una petición vía AJAX), encontraras un error de este tipo: > Failed to load https://example.com/ No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin "https://anfo.pl" is therefore not allowed access. If an opaque response serves your needs, set the request's mode to "no-cors" to fetch the resource with CORS disabled. -"¿Pero que significa _Access-Control-Allow-Origin_?". El comportamiento que obervas es el efecto de la implementación CORS del navegador. Antes de la estandarización de CORS, no había forma de llamar a una terminal de API bajo otro dominio por razones de seguridad. Esto ha sido (y todavia es hasta cierto punto) bloqueado por la politica de el mismo origen. +"¿Pero que significa _Access-Control-Allow-Origin_?". El comportamiento que observas es el efecto de la implementación CORS del navegador. Antes de la estandarización de CORS, no había forma de llamar a una terminal de API bajo otro dominio por razones de seguridad. Esto ha sido (y todavía es hasta cierto punto) bloqueado por la política de el mismo origen. -CORS es un mecanismo que tiene como objetivo permitir peticione echas en su nombre y al mismo tiempo bloque algunas peticion echa de modo desonesto por scripts y se activa cuando haces una peticion HTTP a: +CORS es un mecanismo que tiene como objetivo permitir peticione echas en su nombre y al mismo tiempo bloque algunas petición echa de modo deshonesto por scripts y se activa cuando haces una petición HTTP a: - un diferente campo - un diferente sub-dominio - un diferente puerto - un diferente protocolo -Vamos a habilitar manualmente esta caracteristica para que cualquier cliente puede hacer peticiones a nuestra API. +Vamos a habilitar manualmente esta característica para que cualquier cliente puede hacer peticiones a nuestra API. -Rails nos permite hacerlo esto facilmente. Mira el archivo `cors.rb` localizado en el directorio `initializers`. +Rails nos permite hacerlo esto fácilmente. Mira el archivo `cors.rb` localizado en el directorio `initializers`. [source,ruby] @@ -694,7 +694,7 @@ Rails nos permite hacerlo esto facilmente. Mira el archivo `cors.rb` localizado # end ---- -Ves. Es suficiente con descomentar el código y modificar un poco para limitar el acceso a algunos acciones o algunos vervos HTTP. En nuestro caso, esta configuración es muy conveniente para nosotros en este momento. +Ves. Es suficiente con quitar los comentarios del código y modificar un poco para limitar el acceso a algunos acciones o algunos verbos HTTP. En nuestro caso, esta configuración es muy conveniente para nosotros en este momento. [source,ruby] .config/initializers/cors.rb @@ -718,7 +718,7 @@ Debemos instalar la gema `rack-cors` que esta comentada en el `Gemfile`: $ bundle add rack-cors ---- -Ahi tienes! Es tiempo de hacer nuestro último commit y fusionar nuestros cambios en la rama master. +¡Ahí tienes! Es tiempo de hacer nuestro último commit y fusionar nuestros cambios en la rama master. [source,bash] @@ -730,10 +730,10 @@ $ git merge chapter09 == Conclusión -Si llegaste hasta este punto, eso significa que terminaste el libro. Buen trabajo! Te has convertido en un gran desarrollador API en Rails, tenlo por seguro. +Si llegaste hasta este punto, eso significa que terminaste el libro. ¡Buen trabajo! Te has convertido en un gran desarrollador API en Rails, tenlo por seguro. -Asi que juntos hemos construido una API solida y completa. Esta tiene todas las cualidades para destronar a https://www.amazon.com/[Amazon], esta seguro. Te agradezco por ir atravez de esta gran aventura conmigo, Espero que disfrutaras el viaje tanto como yo lo hice. +Así que juntos hemos construido una API sólida y completa. Esta tiene todas las cualidades para destronar a https://www.amazon.com/[Amazon], esta seguro. Te agradezco por ir a través de esta gran aventura conmigo, Espero que disfrutaras el viaje tanto como yo lo hice. -Me gutaría recordarte que el código fuente para este libro esta disponible en el formato https://asciidoctor.org[Asciidoctor] on https://github.com/asciidoctor/asciidoctor[GitHub]. Asi que no dudes en https://github.com/madeindjs/api_on_rails[forkear] el proyecto si quieres mejorarlo o corregir algún error que no ví. +Me gustaría recordarte que el código fuente para este libro esta disponible en el formato https://asciidoctor.org[Asciidoctor] en https://github.com/asciidoctor/asciidoctor[GitHub]. Así que no dudes en https://github.com/madeindjs/api_on_rails[forkear] el proyecto si quieres mejorarlo o corregir algún error que no vi. -Si te gusta este libro, no vaciles en hacermelo saber por correo mailto:contact@rousseau-alexandre.fr[contact@rousseau-alexandre.fr]. Estoy abierto cualquier critica, buena o mala, junto a una buena cerveza :). +Si te gusta este libro, no vaciles en hacérmelo saber por correo mailto:contact@rousseau-alexandre.fr[contact@rousseau-alexandre.fr]. Estoy abierto cualquier crítica, buena o mala, junto a una buena cerveza :). From 62025af210366ad35b25beb80153009d57d133fe Mon Sep 17 00:00:00 2001 From: Oscar Tellez Date: Wed, 18 Mar 2020 09:46:14 -0400 Subject: [PATCH 12/17] add spanish lang on Rakefile --- Rakefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rakefile b/Rakefile index 0bc5caf..71e8100 100644 --- a/Rakefile +++ b/Rakefile @@ -1,7 +1,7 @@ require 'asciidoctor' require 'asciidoctor-pdf' -LANGS = %w[en fr].freeze +LANGS = %w[en fr es].freeze VERSIONS = %w[5 6].freeze OUTPUT_DIR = File.join __dir__, 'build' THEMES_DIR = File.join __dir__, 'themes' From 029cf42a45e533a7ca0bb3728171c146aa6e547c Mon Sep 17 00:00:00 2001 From: Oscar Tellez Date: Wed, 18 Mar 2020 10:01:20 -0400 Subject: [PATCH 13/17] Add contributors section --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 2de1eb7..7745d27 100644 --- a/README.md +++ b/README.md @@ -49,3 +49,7 @@ rake build:pdf[version,lang] # Build a PDF version ## License This book is under [MIT license](https://opensource.org/licenses/MIT) and [Creative Common BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/) + +## Contributors + +- [Oscar Téllez](https://github.com/oscartzgz) (spanish translation) \ No newline at end of file From 73784cd6cd93ca5a2372f1891c4b8ab13e2bb815 Mon Sep 17 00:00:00 2001 From: Oscar Tellez Date: Wed, 18 Mar 2020 10:02:08 -0400 Subject: [PATCH 14/17] Add my name on authors --- rails6/es/api_on_rails.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rails6/es/api_on_rails.adoc b/rails6/es/api_on_rails.adoc index cca7d1d..0b1f013 100644 --- a/rails6/es/api_on_rails.adoc +++ b/rails6/es/api_on_rails.adoc @@ -11,7 +11,7 @@ v6.0.5, 2020-01-09 :copyright: CC-BY-SA 4.0, MIT :keywords: Rails, API, Ruby, Software :lang: es -:author: Alexandre Rousseau +:author: Alexandre Rousseau, Oscar Téllez :description: Aprende las mejores prácticas para construir una API usando Ruby on Rails 6 :front-cover-image: image:cover.svg[] :revdate: 2020-01-09 From 9e3740fffb844a185c73d0d7fae67cd546ebc5b4 Mon Sep 17 00:00:00 2001 From: Oscar Tellez Date: Wed, 18 Mar 2020 14:12:06 -0400 Subject: [PATCH 15/17] translate some words --- README.md | 55 --------------------------------- rails6/es/chapter00-before.adoc | 4 +-- 2 files changed, 2 insertions(+), 57 deletions(-) delete mode 100644 README.md diff --git a/README.md b/README.md deleted file mode 100644 index 7745d27..0000000 --- a/README.md +++ /dev/null @@ -1,55 +0,0 @@ -

- Api on Rails 6 -

- -Learn **best practices** to build an **API** using **Ruby on Rails** 5/6. The intention with this book it’s not only to teach you how to build an API with Rails. The purpose is also to teach you how to build **scalable** and **maintainable** API with Rails which means **improve** your current Rails knowledge. In this book you will learn to: - -- Build JSON responses -- Use Git for version controlling -- Testing your endpoints -- Optimize and cache the API - -This book is based on ["APIs on Rails: Building REST APIs with Rails"](http://apionrails.icalialabs.com/book/). It was initially published in 2014 by [Abraham Kuri](https://twitter.com/kurenn). Since the original work was not maintained, I wanted to update this excellent work. All the source code of this book is available in [Asciidoctor](https://asciidoctor.org/) format on this repository. So don’t hesitate to [fork the project](https://github.com/madeindjs/api_on_rails/fork) if you want to improve it or fix a mistake that I didn’t notice. - -Update & translation of the [API on Rails (EN)](http://apionrails.icalialabs.com/book) book. This book is written using [Asciidoctor](https://asciidoctor.org). - -## Support the project - -As you may know this project take me some times. So if you want to support me you can buy a version on Leanpub: - -- Rails 5 - - [English version](https://leanpub.com/apionrails5/) - - [French version](https://leanpub.com/apionrails5-fr) -- Rails 6 - - [English version](https://leanpub.com/apionrails6/) - - [French version](https://leanpub.com/apionrails6-fr) - -Or you can support me with Liberapay:
- -## Build book - -~~~bash -$ git clone https://github.com/madeindjs/api_on_rails/ -$ cd api_on_rails -$ bundle install -$ rake build:pdf[6,fr] -~~~ - -You can see all build available with `rake -T` - -~~~bash -$ rake -T -rake build:all[version,lang] # Build all versions -rake build:epub[version,lang] # Build an EPUB version -rake build:html[version,lang] # Build an HTML version -rake build:mobi[version,lang] # Build a MOBI version -rake build:pdf[version,lang] # Build a PDF version -~~~ - -## License - -This book is under [MIT license](https://opensource.org/licenses/MIT) and [Creative Common BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/) - -## Contributors - -- [Oscar Téllez](https://github.com/oscartzgz) (spanish translation) \ No newline at end of file diff --git a/rails6/es/chapter00-before.adoc b/rails6/es/chapter00-before.adoc index ae725f6..638f50a 100644 --- a/rails6/es/chapter00-before.adoc +++ b/rails6/es/chapter00-before.adoc @@ -1,7 +1,7 @@ [#chapter00-before] -= Before += Antes -== Foreword +== Prefacio "API on Rails 6" está basado en http://apionrails.icalialabs.com/book/["APIs on Rails: Building REST APIs with Rails"]. Fue publicado inicialmente en 2014 por https://twitter.com/kurenn[Abraham Kuri] bajo la licencia http://opensource.org/licenses/MIT[MIT] y http://people.freebsd.org/~phk/[Beerware]. From 62d7b62654e794fa8bbf9ff84862d9bc8ce31473 Mon Sep 17 00:00:00 2001 From: Oscar Tellez Date: Wed, 18 Mar 2020 14:13:38 -0400 Subject: [PATCH 16/17] Fix tag --- rails6/es/chapter05-user-products.adoc | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/rails6/es/chapter05-user-products.adoc b/rails6/es/chapter05-user-products.adoc index 9191f30..fa65300 100644 --- a/rails6/es/chapter05-user-products.adoc +++ b/rails6/es/chapter05-user-products.adoc @@ -750,6 +750,18 @@ Y como llegamos al final de nuestro capítulo, es tiempo de aplicar todas las mo $ git checkout master $ git merge chapter05 ---- +I make two little comments. I also see two things to update: + + add es lang in rakefile: https://github.com/madeindjs/api_on_rails/blob/master/Rakefile#L4 + upload the book on leanpubas YOUR book version and add a link https://github.com/madeindjs/api_on_rails#support-the-project (if you want it of course) + add a section "contributor" ith your name on readme: https://github.com/madeindjs/api_on_rails#license :) + +I make two little comments. I also see two things to update: + + add es lang in rakefile: https://github.com/madeindjs/api_on_rails/blob/master/Rakefile#L4 + upload the book on leanpubas YOUR book version and add a link https://github.com/madeindjs/api_on_rails#support-the-project (if you want it of course) + add a section "contributor" ith your name on readme: https://github.com/madeindjs/api_on_rails#license :) + == Conclusión From cb3285fb36fd718c0096198d6cdb3c2f53739fb9 Mon Sep 17 00:00:00 2001 From: Oscar Tellez Date: Wed, 18 Mar 2020 14:14:15 -0400 Subject: [PATCH 17/17] Add spanish version leanpub link --- README.md | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..9bca20a --- /dev/null +++ b/README.md @@ -0,0 +1,56 @@ +

+ Api on Rails 6 +

+ +Learn **best practices** to build an **API** using **Ruby on Rails** 5/6. The intention with this book it’s not only to teach you how to build an API with Rails. The purpose is also to teach you how to build **scalable** and **maintainable** API with Rails which means **improve** your current Rails knowledge. In this book you will learn to: + +- Build JSON responses +- Use Git for version controlling +- Testing your endpoints +- Optimize and cache the API + +This book is based on ["APIs on Rails: Building REST APIs with Rails"](http://apionrails.icalialabs.com/book/). It was initially published in 2014 by [Abraham Kuri](https://twitter.com/kurenn). Since the original work was not maintained, I wanted to update this excellent work. All the source code of this book is available in [Asciidoctor](https://asciidoctor.org/) format on this repository. So don’t hesitate to [fork the project](https://github.com/madeindjs/api_on_rails/fork) if you want to improve it or fix a mistake that I didn’t notice. + +Update & translation of the [API on Rails (EN)](http://apionrails.icalialabs.com/book) book. This book is written using [Asciidoctor](https://asciidoctor.org). + +## Support the project + +As you may know this project take me some times. So if you want to support me you can buy a version on Leanpub: + +- Rails 5 + - [English version](https://leanpub.com/apionrails5/) + - [French version](https://leanpub.com/apionrails5-fr) +- Rails 6 + - [English version](https://leanpub.com/apionrails6/) + - [French version](https://leanpub.com/apionrails6-fr) + - [Spanish version](https://leanpub.com/apionrails6-es) + +Or you can support me with Liberapay: + +## Build book + +~~~bash +$ git clone https://github.com/madeindjs/api_on_rails/ +$ cd api_on_rails +$ bundle install +$ rake build:pdf[6,fr] +~~~ + +You can see all build available with `rake -T` + +~~~bash +$ rake -T +rake build:all[version,lang] # Build all versions +rake build:epub[version,lang] # Build an EPUB version +rake build:html[version,lang] # Build an HTML version +rake build:mobi[version,lang] # Build a MOBI version +rake build:pdf[version,lang] # Build a PDF version +~~~ + +## License + +This book is under [MIT license](https://opensource.org/licenses/MIT) and [Creative Common BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/) + +## Contributors + +- [Oscar Téllez](https://github.com/oscartzgz) (spanish translation) \ No newline at end of file