From 31795312d8c9e6ba7b3f2807ea53f87ba6fd9f80 Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Wed, 16 Oct 2024 22:38:01 -0700 Subject: [PATCH 01/55] Add tools/cve_announcement.rb --- tools/cve_announcement.rb | 93 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 tools/cve_announcement.rb diff --git a/tools/cve_announcement.rb b/tools/cve_announcement.rb new file mode 100644 index 0000000000000..aa33f63ac390c --- /dev/null +++ b/tools/cve_announcement.rb @@ -0,0 +1,93 @@ +# frozen_string_literal: true + +require "net/http" +require "json" +require "uri" + +def versions_section(advisory) + desc = +"" + advisory[:vulnerabilities].each do |vuln| + package = vuln[:package][:name] + bad_versions = vuln[:vulnerable_version_range] + patched_versions = vuln[:patched_versions] + desc << "* #{package} #{bad_versions}" + desc << " (patched in #{patched_versions})" unless patched_versions.empty? + desc << "\n" + end + ["Versions affected", desc] +end + +def patches_section(advisory) + desc = +"" + advisory[:vulnerabilities].each do |vuln| + patched_versions = vuln[:patched_versions] + commit = IO.popen(%W[git log --format=format:%H --grep=#{advisory[:cve_id]} v#{patched_versions}], &:read) + raise "git log failed" unless $?.success? + branch = patched_versions[/^\d+\.\d+/] + desc << "* #{branch} - https://github.com/rails/rails/commit/#{commit}.patch\n" + end + ["Patches", desc] +end + +def format_advisory(advisory) + text = advisory[:description].dup + text.gsub!("\r\n", "\n") # yuck + + sections = text.split(/(?=\n[A-Z].+\n---+\n)/) + header = sections.shift.strip + header = < Date: Tue, 3 Dec 2024 19:32:22 +0100 Subject: [PATCH 02/55] Add canonical link tag to guides head The canonical link tells search engines what URL is preferred for the current document. By point to the latest released version, it makes sure search engines prefer the latest versions in search results. Guides that have been removed will have a canonical URL that will result in a 404. These will mostyl be ignored by search engines, but we can also not show the canonical url for guides that will be deleted in the next version. --- guides/rails_guides/generator.rb | 21 ++++++++++++++------- guides/source/layout.html.erb | 2 ++ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/guides/rails_guides/generator.rb b/guides/rails_guides/generator.rb index 496ec18c4e58c..a44626b615ec5 100644 --- a/guides/rails_guides/generator.rb +++ b/guides/rails_guides/generator.rb @@ -186,13 +186,14 @@ def generate_guide(guide, output_file) view = ActionView::Base.with_empty_template_cache.with_view_paths( [@source_dir], - edge: @edge, - version: @version, - epub: "epub/#{epub_filename}", - language: @language, - direction: @direction, - uuid: SecureRandom.uuid, - digest_paths: @digest_paths + edge: @edge, + version: @version, + epub: "epub/#{epub_filename}", + language: @language, + direction: @direction, + uuid: SecureRandom.uuid, + digest_paths: @digest_paths, + canonical_url: canonical_url(output_file) ) view.extend(Helpers) @@ -223,6 +224,12 @@ def generate_guide(guide, output_file) end if !dry_run? end + def canonical_url(path) + url = "https://guides.rubyonrails.org/" + url += path unless path == "index.html" + url + end + def warn_about_broken_links(html) anchors = extract_anchors(html) check_fragment_identifiers(html, anchors) diff --git a/guides/source/layout.html.erb b/guides/source/layout.html.erb index d2f5fa021ca90..9abb257004673 100644 --- a/guides/source/layout.html.erb +++ b/guides/source/layout.html.erb @@ -13,6 +13,8 @@ + + From fe38fe485c17fbf82d0d24065d625b95c66c1c6c Mon Sep 17 00:00:00 2001 From: Chris Oliver Date: Wed, 4 Dec 2024 15:26:19 -0600 Subject: [PATCH 03/55] Update Getting Started guide for Rails 8 Also introduces Install Ruby on Rails guide Co-authored-by: Xavier Noria Co-authored-by: Jeremy Daer Co-authored-by: Harriet Oughton <73295547+OughtPuts@users.noreply.github.com> Co-authored-by: julianduss Co-authored-by: Santiago Rodriguez <46354312+santiagorodriguez96@users.noreply.github.com> Co-authored-by: Amanda Perino <58528404+AmandaPerino@users.noreply.github.com> Co-authored-by: Matthew Draper Co-authored-by: Gianlo Co-authored-by: Collin Jilbert --- .../getting_started/article_with_comments.png | Bin 26814 -> 0 bytes .../images/getting_started/challenge.png | Bin 20347 -> 0 bytes .../images/getting_started/rails_welcome.png | Bin 42246 -> 16840 bytes guides/rails_guides/markdown/renderer.rb | 33 +- guides/source/documents.yaml | 4 + guides/source/getting_started.md | 3308 +++++++++-------- guides/source/install_ruby_on_rails.md | 131 + 7 files changed, 1940 insertions(+), 1536 deletions(-) delete mode 100644 guides/assets/images/getting_started/article_with_comments.png delete mode 100644 guides/assets/images/getting_started/challenge.png create mode 100644 guides/source/install_ruby_on_rails.md diff --git a/guides/assets/images/getting_started/article_with_comments.png b/guides/assets/images/getting_started/article_with_comments.png deleted file mode 100644 index 5ed34800fe8a0a7dc04187fe7a80ae1fe2a94569..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26814 zcmag_19W6x6fTH|9ou%t?%1|%b=Yw_wmPsGB= z2e-~Sci+9g{e9;~C@V@K!Q;b&fq@~(NQVoBPz)vlVvN&Qz|QU!SbO*pz0}22S}4yg8EU( zNWpF{`#Ne4*q1YJ6=LqIN>6*9ulSxf-~5lW?=k&Qp~(8;0dPT>il{q8s@x)|#g0Lw z(m*JY{|ZS%F`#I(Nn!uL%0=L*|F`A;U9I?LYisNC^D{S>`2F((+egsf-rnB9;Ti}O zai+-4&E@3e{Pyh|3!5Jf4$k$}mE-%t!A~Eb>@^l19#1DHvaRu*&CRLl>BifzurPNw zH{)?F@As!CO;S`AuEt})h7fFqvzHeyC+CPJ6rVB;rYZGU6QHZBi-?d=UO|D8f#K5w z0Xb~&&>0c}Vq$W#t*wpf2ZY!3-@h%btt0ykjEqG^ML;LQy!72(tF}OrIo^u!a^FldGz#LSZj1 z6BE%xm^?MP3)?z6IspC!<>gk!#;ExCosEs;CJZq#F&e6>ri0(0p^1oySVK3~*BgVG zn3zldo4t5=<^Y;3Txu_rtc zN$~MS#mzC6XJ=VhS^r%^OG--W>gvLO71q=ck&yWN`7uQR_V@PGH8fZ=8ylUarD4@x zrlt_nCe4*@Z*GJik~#qb0s?C?wJh@PG)BNT&nGLb zqT|w#nNo*VaQjM2M`mZ^EAW@vo0{0Oa=NL)(Kthc5*JB$>VMA%0e@PVLA6L6#|G5hjZn)+Fq0z(kc5#7dggUzp{Zbmhj8L~8LP0^n z4NZ|pHMH4VU*CTf#J;k!k{1@bP;bK>%Zu?VFdEK}o}OOpj=XjZz-H1AKV)uhJ^&{p zBXbhb+T2Wf60yJ-%go5g!NFl}X}M3!%F1eOZSB!ApxFL!T*qmP&4Q(YOW4)X5wRO+ zs3jr-ekB_EZc7k7u!w4G02HUrR#pr0ni=LaCn}>%7|FIUxnAadu z#N7a4C^9l|842af{t#k?cDHb}c2!tq@SAaM(>KQFL)Wf$g5)w1lk8=n|oQ~s* z+^ww)hu(~gzZ8$IiI`T5OibUbK@&Yw?y(2LIi-ZtxfURT*?5x>6aVaR_Gl=d!f5=^ z7*Wp7%FFBP>SDK}4R+F1@$|H{jUHCrV8%vnyuQA+w6y$&f{0j-nVwM*?)|sEt?l{l zZaxMLG=rS%Z0TT)Rw@eTc;28EFo0ZFi{<_E<6{LkA__`y5J5=fuTF=$x;hPReH|T> zLZ8q|Jq;4SDY&Tf^YhlS+o!QA*Eo$B$4O`zT?5{+w*R&}<<# zH8rKBrQw#9oQ{r;R#edHxnk!EL!8VHQ6XzQJwIdgQs^5PsD!l`TbP)D6P(S9i@C)L zDvKRo;@BAK>NaXr@l_m!Niio>fD0+oAyqfRj+~y-2*fN)<63V@X4v2e zR{H$>d}e9&`0~OXQ4Vr))DTnD-<18))djN!-e&-z%irqHR*6-^*m(etj~t_W3awCe zc_3>e$&Cf|40fi}XvkZ$n(m|lRf{ff0 zdpX6EL8Tpsb5Qt*Ec^c6g%`jkpDZ>#&!qp}rwpey9|&^~$H>gg+$6xv#mR|_RdSH7 zov%(25w-O?^b{SD)p8#Z0cWA3A;bO_sHXsmrS#;m?>_>w_{Qj1$7tdGvf&S2P!Pr; zlGimB6~ymg`{`@z0v0Q%qy&BfDHVSp{#m$5TW|@J7fujN>F>Nq{z>nh?7Hs9R#sj@A}kl`kJPfcG`i_b~&YHFI%kI(a%hrPUm`3q*J8z<-ZP{9?F9SOSHY zApZ2-CrC78&XWO(?!e>$XGWLELK0qg2Z8XpR%? zpl52~T-?VGHlaj9-&)wnAn4e! zs&NwTkhJLR1*!r@BaQsL$|KHuIBX-yJ5Bd79OZVoR3oE#R(AH{W6=qz(ycZOTGc)p z2_QP=7JR8B16iE#rkH z3;qY20}5R{sL(gWpGXZ5va9Tv0m99~82YG77etEsG{)9u{_E5NI-lqm5Y&5+K|^_F zGgDcf?EAZ_mt~Az;_8OHWSdfQvV+hUYB`d>=@LPqnH4Qo2D&`=d|^NeHcm&4K~)T* z+af?dM!ihF;2%U-IuYmHy%U7B+8L-5+DWRFD#{+y3TZE_JQ;FrN(b={qwpU7`!lMdV{!U7)#ObNHcPaj;jW1ZAb zlD1e(aVbnO=Yo{MXMAp!mNdlW1qcVU(5BO`uC7ewo5O1g;99}GtC6mAl-;=jE|)|5 zO#JWv?#rMakB&y3v|WPc(YW+>wzs`dtF<#n7kG+H2B$hxz5M+hw9G`bJ!uosI6tly zQhHqk2Gz15dceq9m3CG2LmOlnqJ1&7IC|f(MEd3}CoH@P&&k8??xc*b>8m zzkO@|6wPBF^s}h(;RC-fs4=p|g>X(j6B84QvvcBSlb{VErBbq5+l??MB3l;-v=dPL zeN!Do#C_p_ap*RW?N02sdXab)!>mzACfCGi|AkJ@GDw}5n;XJcXC<@hpF8}^Q86Il zR+0l-Nn5SXejwUu11u*t*alFEIHBq$b0DRUHK@nl6rOVuH#R*)Q(1>08g`$gg2(y-ZiX4Pci4~#)`_*j!) zTUa~#s{mQZE0PM;u!fT*{0v`eN5OxTgKzX*@7rEmRm^XGgH=l)z^I-Z{jcxdaBsn+gqni z9tSFyX{Z+|s)r?pJz7C673Ha^&kseM0lJ0jmY8L3WV}kcU3@a*{p>DYO`AZXez7>Z zdmn|>b^~yKb>SD2-34Qzb&mwd^nv4WN9WS#aR+Rm;2alD&)S#^1A+EUMm`T zs$8=rFQtixx9YBYOf^o-qZLY*4Iu$l$>bR^;jNrrh1lhBFFs2n@mTwdY;fX+fro@S zzC4MFNJ2IxwmxN%7TS`1F6QSXm_t>=byQRoD8^I=goLH>Rt%k88$f>CVu&oH*+9t= zz+v(rU&|uV^oUB+90|gTXox-~eCQ3S@kOEesL>-2IdcO#nn-=oSc>FMc#H_2> z$(`aRgPQTOaNG@Kk3T;wGeMSGk=I0!M_=5;iN!rs%t&~_TiT5~RJe)j^GX_z`ue$! zIFJ#xZpdU+FZ%{#DftId>tE}8IMi<5EJLn3&2O2k$|3k;_(&RwR5G3~cXn|`i@z0f zt|ppf0jWIvrG&XrJbZjjNvixGXmi9h^fPrXcwxh|4IQ0-cH$^Dtp?&~Pz8t`d1>v~ zaD{q?!3bHv#kjQISLo&ggw>UZfQV7|_D0Ra5zU4l87mnz1DlK&&!LVOVL z=yzFsR1SF{NP$h1)epz>Sf{z}L6gFAs{^@-Mej1g85m`L`5JDWm--V|tuOy9Aa;bO zk-hxH_!X60swJr(*9o_7q5miI2&#c1bw=9vVy7I@%+Z)X&w{lrfs(`YaCd@E>y#a2 z<~Cc3mJXx}3h!)1?%wJha(6;(+&JI>3PgH6qwnN&tx`)-zxQWd$PS*M$AOk;xvzPI zQezRW5mW~CVkrYKz{E46BIgCZpDa4ZA*i(>O%m+|vk?6?I}cT9P{*=s>eF_9wUgvi zUguS?GwzcZr zV_mToRfyindlF^g&U!rV9pz!p*8#mOrK(t7mfzM4Yb5-(-7ZCFcuCo1uA&i}Y0Ep)#f`{{ae$ z5Oshr=!iQ@PMs+9BLs;lRpcd1Wm2S*G+8{$x<_qX^lYLNDjJE;`5>derfi$GM+iS( z%&+3+R1eym77qJZkxA~AVA^rsAb$dNzIKAhJspO<5NMl77;>@&!r{Vb@a%`P2+dnm zs1Yc{tekid3CeJIh|FE@4IRFzge>r-HrLG`sji1O9ukL5banEh66c6|C&G@s|>Xew8B!qhT(_@L2iQd36|T9djI#t~LBMR;w59SKE+N zG!9@m58c>;F7Rvwe}t^KLQo}(c`_eU_atgU9j$Q`Sdi65qA2y4oK3Vj^)yaICo-=T z@S`kFxd#gjj;!(c6=#D!aV^? zf1@n*^*GT@>sCj|bP$%qR#MXE=XF>VZjWgN8|9G`fn`UNIkLKY| zq;eXns=Km!iYd`#GPn>s5~&5=q}$OuAZ3r?@r#_kZ!J#%QK+kWQ8)Vay>(mkv8wW; z^u*!=K2#%?42*Oc`Kk&f3ywy>(712#BW&FCNwFZn=DaIDr2j}cZVE?B4acgoIu>_C zyq;zz+{6w#BeV<=9iy@JT#bAN)>yCLpSa*@1&Ox^JUecQHXYoN+?330{a!9-`T!5OlzW-h?pgUd*aMySxeN3}Sy>Hf;?*aoII zVBDM(=f#UR!*G36J>{K)cGH4t|7x4k0|#$s*p zGW>9*IB@~HNs{?OiD7N~fdpdG(VqU{>cq<0pMJsR`Gr)aDtW9l9Wf%R4r4LTv7f*m zi@Db1N34tzN4M>wTjsIwc}#jsj6dDed7?;=k&Lx!VCHnxk&gO#k#J1k@EKGO(WC~8 zrAuGZD0T~(v?50ePKe}5aPLI!fW95UNbN9^fDr6pcN0Q27gs@Ql;M zWMf&yCti82EA>3s6m!mxZVWjHsGKpINpp^pHs~dMK7%_27ui@5tA{|5cK3`(v5WwQ zA-YJ%*TR-;e3%Q1yXyKWvkByOtWnHM9)(gG6shSlhsq>e2Q0Z=E1JDD3POHumISwzS)I8v* z=)BxAhpQ$w`W*aPv!4}`%-m(VS9aEgve%yb72(R&yiX3+mDYN>Xoa}R;`M4#U zD0gR797-5S0*;9c4}*zJL#WK1?*Rl5cE1a%oO3I?xhRSh zIgje?N8g5GiTK=Wd+xHY{8qwRp4!@3_`wGU2SMy^Hj)qoS@BI`Vq!21%6Cdi+@1_yq($nq~y= zA0LN9W22(H-!9t{Ox4uX{Qdg|q7xF(rucH*q@<)k#OAMGRdatwc4}*DkEZ+k11eaJ z=JK1Ho2#p%%SJ{=RbEo)wXhkqXJ==P&CJ+cj^us9rl%F}%fd0&H#T^bZZAR9jkU#D zYhhtfLipRCQSuPX+=PTcKm)L~wbkW#`s3OE>zQlY!|d^F3B4syJfEDQHDm9CI}5t$hm~#U}5##P0;s!eO$NMt~tLwU7nnr004jzxilu7#-XJp ztOCNi>cL4S9&w3Ex(63snIx+QHpl2$4VJslf~bOnHi~fX`*2HFYVJF z@8|i5+afq#&*v?7GgDJX7{Sk%{TKo+NpbPnCF(q%y{juhPEHHZurz_i1MQL$Mn1Qp zIXOAQA)9&*JuQA8UWC?yfMI)$`C#BOI1)ALODB2>=hqYiMX7@p~ZyLViMsO`R^3od$k=^9z?Mb|m{mfv0x% z3jtVLyhd7OK&_H|y+{!rUH85r^~50;9#y|UtdcOX-HJd@Yw{S4A89EiDK$^5WG%~<}yhiw)+ z7FxYqCZF=bCGN{t5>7^Ia@nnm=V`Ay^nGAg=&5smczAj3jodR7fGhH?^@h&SS_XiB761(N_1nMoQawp#%eNK?>^CpB z{93`KfIYI7(b}Z^tN3(vUf1WB4sg0oV>bFcEz9eD-u9-PZ#Po`W`oE=hwX0b==p@# zOpaeHM}PnRHAvESTM3N^sHv(7zU)Gpu|)ex`vN`8s8y0XY{#k!#7rHEas%P)vK@c^ z!J}p$RkOk}p)f{d@G50hVQZ|-QskVS8|R)StHQBs(B+C8@7LIl2BWtd z$D_^K;368P#@_k^A$< zsHZtv|i2ip7&YvMR)MBgz9fftGi7dicvj)sOsJ7|%&Z_>ZmN#3N` z*ox)jn00d&@`GTM2;|UCA)L)sbFA;Vc6^eWyO+xaG+=27A&K$vC&)7hxa@K7EtOD2 zrGWu6adCDJ{fwj`%FwW6A(%}6o$-KBRMmD=QFt6s3sG2FeOR(kg#S*ErT#DV|8ok*}JF}tTWQIn;Zho{A3xu6k%!TX6*JX)D%`)azw_- zsJ@YvlBs%#3f}_j^nb~1Af}#NgYsruTG={>>a(fXD&FR)7Sa~`-v=Tt3R+5U!g^v# zs*dAFN;TQjqjS||YN)UEEiQ!e}#oFXXZ*xa82Ps#BBtiCjB`j@rPuQwfde7fd zTkoIbto0s)QtoxOD9u6`X8yb*N8-uPHOZ|bhQ(JNOm1J~#~yuvyY5n&8UCN$ua|mM z8Az-5`#YW?T?aqZ-9}z9Z9f1br~3LbnqpddeInsTNVM6ENI^kCt19URk!!e^Eqa=vNywShR zgVnJx0+d9%0>w1}B1l3!6)a>+vuk2_>W>lGbe7|b&c>(Z*Id{({o>fQ>+4=6AA47Z z`73pG8H*(1 z?$Aa%L=VSNLPTk8&@4M6Q)^=nPKzMVsgwUl`L>_g?#X|#A@trXc2W=WW}_?(5E`E* z*pO_uYsr0L`CI%BTl6~Ye|@->3IG{9XV)D&D*{gSKB39&nl-Su4>Z{&rPUS%U<7iS z+UKS&{E^m|sjpWg)b}tHDB8=l&}X9ZIH9Aj*muiZp(GwyoA;7;TZ6zi9G-@u(8wyG zLN3{sPD91=v0t39WTi4#WfVqT8aQpL9TCth$!_B^<)yEcOLNlI4d5e4!5@RkKY#tY z0C{8qZeF(Kwe=nS4GC);;sdA1&XPYVR~gs1`)d7}ZrjrPBD@DJO|eJY)2V*@iMZ4J zqO|2#PZ2O3>})?|?JVve9r@)hjNhNiK*-0?Ofw>(kO`asm;jay%>-0#mTG{Ob{xx41 zz_Yc@C4NOW9Co_q`B0uZnqo2VH#bI7nz|T#{|kxmAzs|MOf=~qDbf(kSc})Ux3|~V zc8_F$-1?LQcIonEX}{c0&tGCQ!+$?5SKjV4Ws1G8Zn1KgM=2VXbevKU!LPJ1noQ~D zgfy1tEERn?L=O$r&&?7rQ}el@h6&Q%pCt^LADd+NIW!Dq?>iH^9RibPauyfs*@pTo zbIr=lIH^dKYMwgXxwF%jUR*2N@1nZV2T|~d|A1_(*2T^3sn(#&?P4_{F%d9It?nUV zxUl^%wwzZmKm4_lwxGnI<|BvEmrPLy)0uACYL^zCom|(J?b&6osvLJ9SUrH}HAYX< zmTD!~cTn4UeINP;%@H1%$!S0Y7mxUm!t1C;Ba&D3$M+%DUtgxjk?$5)?AkY&LN*>y zpqK&@;ORB1U&8!f!ur16`ivTX!kKU6Dg!$rmiR2zfplihTHSt-TJP(MhD1ms^Y$N` z-#NOgJ6hkK0$CylZ)7tzmc-HT#HWSSxtTMzH*sXs?beOVuOXz|6j(o$Wwr5~_^^u3 z=y`8$8ZMzOr*ZaYs&7lTtAJ>rKn9YHRLvj16z;CNu2t3k$?Q3@nGE&wS#GF_=kya0 zDE5RM#H(@&Nh8u7MK>vAA{G7Rq{TF*x>bD5A>4R*Q4hU8YhRj!T{yrKgKXI;@3O6R z{1FHEr&7tCr++Asr~N3Lg}6{k51M5v9zK3!ON;MS$MJvAHjB#vq=O2Qrzz&gmfvJA zP)yRUiZdu6M^|%uy24A$qO&{>JY}dWOAh)-K&qZ3D+#@)5VS1I{81}N*;b~F`IoAT zrYR*4pZEI{hZkDA%xo#N&CSj1Y{Epl zZ*s~au9KF_Aomk=!RC8ins@~$H(VoD)?1!#lmC;mbK>omgTl(A$S3$ORKbsiuqrlT zRH$r|4db-<5MbbfOMy!ZTjp?yLM3CUGdqQjpY6Hu12@T%^4M{*0!{*rJ1y$=9h;AS z7fpUTK88h};dKU~mAZ%)+8Tr)zE0~GNL@3f(~Z_fGFwSgd+SSMDjcHO>$_+~OPmSF zh5F`Dk|sZ?rz}_t6zmbd2tU~Mux;~(iM-CQiSF8DEpdtoyv5)b&RAYxaR~(1R=8Qf zN0b3Gb7U&KkYSzx{&jZ${n2MOGde4{KO}LesxR$T&r0?HXBnp;DyP}1W zH-vI}?7y2CG~95r}Y`X|Z&(xvSq{U-5fJ zi4`dQR+{B<@i%u*(4FDO5sROB16Qm;)1~8^4w)Rp^>VK=`U~qeG(!MS%S6=6X1Xg5 zXps_7aod*N+ZNqEG+~_lq+Cd1c?$-5D9}`L|8?(48s@MB4st_MhhToDA#FpM_6iwq zE@}S{SXP?z|B2Q>Q0@PX*8Xoa_kRPr|93q+2}myoC7hL&m7onkR#qpMmrK)zqWnnk zAaQnjVge+W5O%%yfq=V1&*L&EigVg;o*W%5FD^ddybCdgU{2TeyI*a3e*QcDI?ek$ z{rX_`*2-^?#}uz9rv#&=>=6M2hYA#h623;25f>1}B2}*_7R5rR(~!lYrGt(P(M@fc zr28B+ulZWA3{>ibsmS0vj-6Y}=yEilwfgZri53;MTC-~9+?#n}l+=YcXZ_NxYH8iR zZHC&Otu+4yHFQ7n%a-$3(^`1(rCZm=<&y_LX#bSe4?y{n&4=F?(D7v*06a}qEUVG1 z8a9oPEGl0eEmjo463m;)QKU|;UO4SGWah=12Bu9yKS@)Xa%KV9a1k4{qmTdP`TPF( z;X`*365#yCJefQSh(ZLX(gV9@lP^*A>M<-)4IMP8@7aJYiN~d(`7{Kg+VK-p4|Yr* z>N2G{U+>?-hKk-m2U~eL=FCbrlTb+>owjHKI(qOE>!c26 z8`Wq=4V$)YP{DY2F9jn6{`2WItaIQb5J8n@@OXL?FvW?29%NZq`A;^yxY1oHBDXH0L~xU$ zxqkP?#QpZu-|Od(K?9_iwVG8<7I=u?zE-;sa64)b40a7>T!i^=(S-7Y&{etqgPQ{mm!Tez{1m*C8$pF>|V^0;E;zaPV; zdN+t76#Yv^#@aaKYY(Sy)o~DMpbL}*)$5Y#ioabBVJLOK>_z2y?7`JMhHn7gL|}-1 z*l)J~UqNcFgH8LqTEFAY!T(J!`{f`>X&U5Cr_x!yuX~j_^cND=dRJy;?2xy%S?#pJu4fVd57(7zul8+u=I#1rtj zRYQ-Ls}wzLxf|`){T61j90-Dbg(c$st!%&Q3qq}}^&a}r@``zazI#Iv^B7G1UC9lZ zeNotqx(%kGPeU{QpDskBXcUs%zFT!vtuB{BF9Rs;aO2aUM7Oq(^-A;i*F~Tgty;2vjHnkhmJ|r}?B1!0PvzVPz7L zkcs)Vb>?af_#A_L+9NYyh&@P+rFMG`66FeeSuE#^Wl+bEc#6>vqGsHHv^3wEu@7Kr z#Kf3QakvgZ%XPbmx^9s3`1gl>X4P&=ZO@Y~fq~-ywB}Q)N)Ri+1pZx=Yc>2}n{I)>zYD~E9BjBDzKA5RdJiITt)6$^ zPh%KECK0CAoSI2d=*R6cwEpLc0vHQexb4=PX(Wq30wqN~_0%M38|EYl=7yk&U8Y$5 zUj|8vuXq#QE}E1Ufufxl1zI{}0Pn_=-?1ZvJEY3)u&4Y)AtbJgDhYGN#XoQX0$13F ztL2|O-XF_UnAxI*|7OW$(}*`fl+36YGLN_qMqUv$wH!)FFbR#M!RwNi(b^bkxz&pQ z)0(9U`i}D?V`1|xk^APH>imAs(>i^rUBfUos{((>JbI-0TrQ9xjGjj#Ke-7tkLqa= z)!KDfIC+vZRfEBbCVXSv;tcJI7WE2wmS?Yzo^GNX`SbI+Z#2IwLJ{h1L6NVfd7+sY z6UPFXh!^wd5#oZ$VMQ|^4hI##^uK`*kJLMtn1NOJb?nFTksn>RZrHP^{ac1a;V0dB zT4w6H*o$&f5_nW6_4#;Qfw@ShD1{sQhBeZ-xkuVOvFEhKxnWy`qJ@g=9j{+{8VDEH zQ2NIoapb2;4n3>L<1d7;e%s8&kAOOf{eH58dwjzMaW%%;oM2S%$0U6aE95RxFv0mKG`6ahcz7w_m!LBj)^_xQd0Q7jDw<97`-m z`%*+V1r6a^*C;j7Im!$g2)gy&yCJX<$Q1RHev=*e>!_wa=QVA$;_X-m5a#P3M?x4Y zEY?*`D;Ufg4GnM|1Cbo}V+W^eiR!RF&;`dNpaFqTFZ*$IKB(my_B*m6!4C>tTk27c zV_L-*%?pY)e%@lVsbmfzwzlRj5owsR#b_fXD5SXWfwV5xq)m;2rENmfZen0JV<| zj0xw^xfqJTqLF9d0wge=*8A%km6-H-Retm6yHA!eAOucDie~+J$KQXVc&+3IB|xVh zQPT`gG^Dpu++cRu@f+l|jg+@@X?L``{vcd5J<^t$-Q^W*y{VrV+2(?^v0J&OmNg&c z_w#L;i^s2R&tsx9Ps#l4PiE9wWafm7Tw*S-DSPwtuh*5nCorRg>(k5ysaV3(&iM;f zd~{$m7F|BQjTw=duZ2f^V3ZL4B)jGG12ztrb3o-zZ2?gaQ=CuWDEw@p5o3^y+Q9DJ zB=gGX-FvrRhzqs^63W?`csuek7ujZJ3vA!l=bI;$RkSL=P#5e+bWL6R9z4+th<$_W z&^5j3i-cu%ZGX!jsQq>F+aC+s#-m2+Ss@6c%*Ce>w@hw}Hrrw)J`t80A`_9lH>j2m zdcId+uWzDt$IlzgY+(dk>>-Ld(sPkeDF9mgs5a~(V)1*HE&5ESuardIxctnB`b&#z zJGOxx(N%#~fUIFnJT}T8T0oC}zWhFBwT@ocb=!{L!!4oW{%rX5V4zbw!H46Hvx+_; zD4cbR@mDudv*;7Kyt%?J- zd@KIQ2D&iG1QAt?>R9HO`!0ld?W6($0Y=NpZnx_EZUu<$7{TlDo7p0OG_-^~U^w93@JtWi~d z;cVxKi7GT#*u_*aQvh-~JEIiR?#qGp0zHa5RG#%)V<*qAxA}U&BS9ZZQepa1X?`;i z*)a$NHb$j}T(@}`lG;y#dfWPEqYXhkzSscne25y?XCpV_VV7pYZ8sy-kGp)!C96 z>l{6fn)0>st+az`%F$l&bubo+2RKwcTK5FS9TI2irOw$|_a&rh)fxq;`C|HYJWO;~ z1Pv5@@n2%OiG2&HAPops3AAB2q^uY;7&TRNAZUwt#Lbt=|5Wxr3X_wQ#XV$YWy$$L z){y(Z_WWO3{nzOKXDe}Tcw(R6v07HBJ@&iK`mbVA##cVLqaue?QWd}h=8*i(?>r~6 z0>j4SajTTHsSGDbMM{i(n}xK3v^V|g*z7Tb%lyY>&|!q8Q|-@hFW zZT*gj-?oNlne8+$LoMPUups#Hpyq-~o$LFwHlVFbuPI#kADRl{N_G8l^lL2XRobE#t*22}DFQofE+@gZAyA_>^m^x1ikvLa(kFp#_>fRNqi}L(G|CMIhM>-L?0u}i#Lypt$e(mMn zkirl;f_=XyjWO`1LSwoEFd{SFbaeLn~8TssYIavT}u5UNKqmYKUwSoIendb93!6ryNJSR2@m^U__f*%L7HW`UUaLA+a&gr zr}SF2LooIfwPn~^nK&xy(uH{)cZJ2@A;xwGf_p{)%uL`*yyw+s5c?bUU6;IW7v-n{ z0+wV8Y0OhuTyar>!Zi3S#$Zr}AUK>4N;~RYrg_5R>b*e=oQ&@1H-skTD08{d7WRc> z=mG9mDxWc5D1i`vBnu*e@YFxCSz!Y31V(?DjUXl%sUN#r&G-5HpU)mFg9Jw49|9}q zRgN|@Y57)H5IUYIo;n2_Wc*$TI7~mRo)?sa3`7fx>+Zxs(qRY3KiRZv zij%FbwuPE&e1nUB$9Z)ddz&#_+uU~)1ZzwH?wk&TW6nz4Tv$scLeDDAnvg?Y1kS5G zR{%>EBv{}cD6he;7>Ofc9@dcLP1A`g0b>@2e7`%KG+Fv-SOj7gZNnMa&QlrnCJGND zzt3dzEC8KGiKtgab5JLUgpZW`?l5p$^T!ZTW=#oSsP6NBIY~wY+$*`hR#=rvwb!V{#ho{g7<>hgZClTCSRHRB90NaF$1IGU{gvI7mi0mWh4 z(z>}Oq?wGz;>mmh7cP;>?jXPy^rBZ!_2Zt>CBku$m!@9-a_u}UpUf2jEj6yvpu|0n zr8cS3D4f?HYP+spHAA|7$aGCfXfi0Rfzm82=qp5b%?L-Q6xq$xsSXASuB-lV?<>Y_ zsqdya|0OremzvUc#=Vs~yJu6kBby_y6-*Jl?c~2UEEMS+^-p;0h;#_fzaCZqfPJz}+(^d|~($5ChQT8iIKrDodJt z+JN;9$rT!ijrLU2-|iV>GWbUTl$xc$!cg-RZ*IiO>!d$w@#IVWl?Rqt&g3?`%3=X7 z&7DQK4@>sG%aN1r=mk)Z{c=KZiXU+r>-<}71f_8p@UOpUO){4EjOG&}o)Y*-vDxBC zWy+mDF(0u(g@Gm=Iw2b}*c7hN)|gBwp=t-6@*c}~B@L519J&Kq6h~>8u)A2gkSxiD zz0wl(J)w{;g3Ss)5u zCnW6qVX3ogh4g)Sb42_-hzG^ zEt$M+ZT32@Rcq(JC^sr%cQHPFEYg6q4s&KufDO#} zT$wcU--K-z@1#PMtGF|ltRNLP#yMa0eM7hQrJK%zjUZZAdkin^G7#Ih(S`;NIt*c+ zYwNdKJF%gdME@@jdf*&48u4wIktK<|5!2=UGUNVM)XK`TDYhfiO=O_0iaX9Xae{#E zkEe|#kVz)W$MOWl{`h{YR^#`pW#E)!UrX%+wmYxrQ0W7e z&Y7)`?mARVC`@*IY2+d=5mw6W7^YtZI-jK9BuIA%CFM=Wq4Y_}Nh2We98xDPPp?)8 zTqH7}WVZR+%9;|x!jMw6+V%`x-fPQ?hMAD-#q)eaFW9@y>UR)$)}%lTv_XW@=5sX7 z?-T7sxTFEE1Z+KL^w+%_`VkNc;VELh%Yv!R$p56^CJ6e`Wiss0y00kme!$j1L2B^F zMmfweI6SF6V5wd~mYnbxN-k=MNcgD}WIc<}#LU6> zP&-i5*cyMEc?l{NeQwoe#5%kgQ-B6E`brPX;&x48Z%U+EXAJ2C)H0|Nm6QOyZBe~l z_3(p^=&U%#*~TjrU;S7Dh(M8;i|LTWbj0$o=wU_;Wc6Da+%iuxf&zk0+~TMKp5RC* z&m?nCn&9w~AYy*1!!GBzAo5S)WZ{(Wha5C}h$cDF=(}jdCR6b9{_h@Bx~HHQNE1-c z$nknnBk2%#sEEmv6kNuncGtjgrv#-F>Jegy_^|U*n+_1dIgkob#Z!MG{ITbwd|VB` zpenn;i4fX_v#05jm+T4ozyQ6S@lL@5Zq1MvzMT2Pn*6$-H&RLle^0+zc5}9pl#oru z2gqJb-AKvb%YxTzIAXwz5|jtagJw^c@u-F?=gJ$gf*%JuD!lwK+G(>ws#GffkGDN(s$+FZJ+zUEWt!er0*LDl=`6} zbRl(#EdusX!?!tJ%+}rxj>*ww% z;_c}X*lK>0$y9kQ)b-9k@*Oq8oo`eB=J0_?%hYaAGPgTm7AvBP($>#xMZYVtl~106 z25SU%+TtWYDIb8m^p3epYaDC@JFN@{ohk|zjB$Smfes}jNFuJ3z+PCzkYm)}T31^C zotp=(n^9Wn1&vOwQeOz*3C=5BAYm)r$|hgKaU-@O&ZOF0l>O3RV={x#t!6q8Ego3JDDxG2^*v{hQc5% zLXpP#bi9VoO>Y=j-2-qB2-7X$aC&0E9$P`p!%mu-0a4tscgK5DNKqm-MTW=a(rH2Kr|cyR7ty|n=u%r`j`PU#fX;={UDOU+UL_KpwaYM_oQ1J%<^(^R-5&P9HRdXLe%l42 zYV;4`1{;6L6e*AU1WK7zNJL?8IP~?10in+d{|#LjBQ{f>$zvA}YZwPm{U3dubyQVf zxA$pj4kZoJ-O}Air~K#!=@iLB9$EoWQo5AxZcs!z1f&n$APo}l@)`Gz=iX=B@&0uN z`>eCiX0usq&hMPx&zfOHMXd{SAK_=Vs!8E5>&w!I;8G*tF{8pwBKe4O1mDmXrS7Zz zxgog;yH*>N7T$=O*ZX-{77w3m`Kj*3OZgJ(T7cDN*XZcfCwh}(5$7U$FS5!T&I_G2eWU$xs1Cor ztt06LV%MKtR2a_uP(=Og&u202FByGD)A^M~I4Wx`g7XxdaCn1GMgzpLOb;k7^;QX- zKR=Du%LHi{tcb@v>RE;FyTYs(<0&C~y_RyC$Q+OiiIslp!IsQ>{vL@-QSf104$qHM zGlk^j*PdWszM{7_;b%v~cfnaxlX|4u@9we(uxN=7mF z4$jw%Q6YoN^^LQV5^}agh)1P&dekM?8Pj&&OM7oxj^R*MCPM&6A@51^;McOSYooqI z*HsfoBC^4>jn(N+_UescN~t^V6X#t+N17C!q-J`-M<0px9XC`y4V9K%{={aW){0j* z*XI>$dOPq~+EVQ$e)+aJxhhYi&>3AKtsfdtC|@Ss99_cKR33de{^MnC-e)douVA}& zIa{Pt_HXA>67C;-L!ERcsp2GkAuzU<-JMy5z+!gZ$Ki^)KRt7UW~%yp zxRju%)Poy){lY>PfxwXD0J>1y4IR7aad6dinv8lStdZ$yA@tWris+ zM`_Vj{I3)mx`*QPFM;#_2!sAh8~vX$=>Hz|S3vy_8T8-h`d>2Z{}^?Q2$#W&(1^~3 zU{mMo6UmD=2WX)ZOv|qPU9CO11Vap#&3=P>ou88&`M^Z`xkKzjN1AJWde9wB>sKnSwrq0rQ()7OUk+x zAb2g*JLppO<(f(LY6o8KP7QF09o7>z_8p&bF~G27_0Ox&6F^xy<~nc|^F>6#uIN$o zdWMi&Cn)6#I!yMj95ya!&~@zTLDBs)1)L(=C}s`r(UlOEfvkf*AWbRXzLLq%o5bR^ zLRmA$-fZ=+lZ@to#`J8M!}BlsnIpQriHP zv%k4MkkDLF)|F3+5FpSY&L$#u{QMj+Hjb+mUG?Dwa~4DBwhHuiDhHQR7}5)BUXQo< zm9)%7O%sSuD67GTsXf2bl9;$7k_Tzsy>zxF8i17hB(e(MfAShlN|jY6=8(EO=>wuw zy8j1}wP<>-_LE-V6)5)1cq%5RP0!~eDI|tuwES6 zk{Fa|8uAqYZ4wjjM&fF5eGy1V7~Lm{Ij?67E3x=n#2!Im1oRe6j?`=c{p%W7I*jY~ zn8Z)IkzK3Ax|!#xNJ<|RT69vMx!uC?f|YYt9v`lV+=-tORnquvB&F>hyEHA?1qBYS z9wyI&@-J1ZPfm2g;M4QJDPi#azB#iIIYlpVpOMqE**bg8@t~W{)PZ<1`s}eBsmHT1 zsVdHm#wDMH<&J23UU zxI25bIN@~|(AVZ+12qLjOd6lQbHQr;SMS&2xJ&t1taV-Ego8G|C*`8@6uof=O9`i0 zdoyT==Z%VXD^N;E>3IN67%ZmY@V+R}u|t+6O!wQY!(aPbEtQy$H`m;W$4eip1HM<| z&kS%JMlaqZNL;!f&DB_Uq>1}^0*8&dmjdw12#zc@a-f=I2#ic`Z4j#RJRa1ILDE$H zocH>L-vH4{g$*kB9tm1!-UXFJeHR~Ij9^>7W%i=q1S`QhtHoIR<}I;txJRkqfc+(`RjRuK@~aEq^lEK)c_W-AKmI z0wui5foyDl9JrlPgiC0sOr(Iz{PQ?GD$x(28D!mMPvBiQBW_?`=Js*R@KP{HN3AG# z>tWR`ZvXv_V#Qm8Cit!<1-cqKc&e1?(ny9ateq(xn2glSGEOB04V-I5ZTe+LF@~Ry zs_X)Z-C<^sZRk1S8yB!2rA*{sd+NXvQbe)&lEuq{=_vlZ=V@+H<|~Fo3|ZY04VV}D z^PO?g8SgX1g&z|ra4wMncgU(st&B!LGP-Nr!1EDB%7`DmT(TZ*K8GC2ILS+N8FSG> zyx&QN#5%9nu(;;#@m3sBS&j8Ud zk5JNDQ!$!Kosw@p-!#htUBHqD|IWzwBxG0RZ*28MVgbDw#3P=&>a$nSVB58Co0LK6ZL(OWZG|EnL@5S4( zc^DfVWlLJew-IPXhv&a+ls*`F8X}?GB;pq_?%5^ZT$qtmd-O7j@Q1nv%bHOOD}O@S zpdpssa6*1^;Wu;vJUl#w(*yiZ=#3)J``jU!E<;c{GgSn(0DF3vyJSkRm#`tK`nW@y zDbZQ#dw1bPyV?H6R}j}Z08Bz1J3xPOJnl7Y9Q zh6`U^?KW=i?2J5Vmq8SvFzDr}$|^1N!ZKJ{S*Z$HVR&h8+iqo?oo|~DfqHonNCW5&3^5sneQU-dqzCE?A4tEk z{F4#Bh9U7C>-V6PLbDn%x!U`i8^5P{y7(Igzv%H~QQ!CeNStzM*Wew~PLt8WWGZWH zs;SXteLlXlxENofs`oy!CB!iMYf#vUmMuxBT!$bTZ;Q1T)$^!EJ5^Z! zI6HJUa8<(F5rEB32hpUYq=r0qlPr7n7W`f@Kd6BYmG0bfkKNC9%s7$yu~C1#c0VNS z_GsXr7B=iijG%plMiBBj$R=^N@pBO~6S`(vSu0i)>?9;4q=%+{v%rGu{IbP5Ao(Oa zvCw@1FmRfNQL@b@SyHdLNQ%t<@e;)}CtRTDHdXcWoq>|cZ%eN}&zYjEEqS(RpWBcs zh{)QC@1%zRU`+pseQUp0Fq9B#^KnwxM~_D(zN1N!vW{4@@k~qU=<@|!M_w;EXMyHjWr#>lh22z2QT?3>Y1+%LC5!Ui<73J+8k>qYe@eRdA2`Ju*T9!l z5Uz%I6J-&6@3hK1)j8KMn9<@S`C1c>D20bc2M4x4_tDX>0tx7>?6;dQ^dfcZw7b4s z>*~U+u)5o%a7!9c3XXE=5T9lzSgVrrDoUVlHx>2M7jo8TqEg^LaeQ`YQ}Tk6w481Z zmvE6!y1&keeSSVT%w3h=zF!%UuLripB2VL+$jMV4Bm6+s%WfqkBB*3k?B`iotAVc} z)vQD=E#letMK@{d?H2G(@aSo3gMM1Ls;ls?A5M ziaGo*0_GCRq*hVWxjetn5s+PK&Y=;itGi!>DZh6mxO<_&?I52QviuRnilRQ-G-An$ z$`vbJ(|-tYDKCUn@{Kh%dic_Q(jMwzn%c7ErLj16##lXUR<3JkXlQGTLs6gLqeU;t z;Nu-6U@lm2_<%7h5Q4<$$EApau&SgtEUk6^>zCp8iR3VT%1MU#K81Zeqs4tKU&8K8 zHhR`d9?!)NZw(H~rOkbnA3 z4Nd4(?{|*bXC8-|7RgTHj>iN;NhrhE^in+Dd@c_*())t!Ff?)LZ2x9HQn9&*OD3<< zo7sDjt?t>veqb8xd-G3h^vHLlMkg=jC(-0g%=yDxkPidriRt&Sld7vn58fH^EZ2%( zZt}JCC42J;^EQ*og|R)iD^0Au_!_hsoKEIn7HRnay#ttk5=C9=k& zFPlcmKA(%}8P zBYmndsy9g9x^Gpdfo=H@5s?OnQL<1LLRhBc1R`=O+Wl4*bEbMM^@XKTKVZA_SYN(V(s3aBO`#K1Mu!{doD&<*rdmg9ywRK_1F7s{j>pn}qtH`di+EX!23K6oar<$e3kAFh>EXdv z^0@XDF>_50xW|yGmgq&Gk``QfqV-{>3iYt8)Jy3!cWlCBd+bY2*WK6Pd4&JVVgA3Y z=UT%5_N4#Ij}Ai)Hopcwb3=WnitTjT)l|%}di|}fI?*(N-C;DlCD-_*X>$@We$%an zRaLm3seLcsdR|2X_Lco}{_x54hvSZ%yaO@YRDS*|dvxOEgCz{5trd!a-z9w3-$eO+ ztiPqPejM+FRH(?$f1qolV<^gpsTj%UtG^J-XR#^!T>k|k5&6K4YaaaeMk4f20MBU9 zQ622+#$@(2UQTdhn)^|*NdTD7e@&hK+LRfbeNV&r{q0@XA7(eB+FgZc>{blc`z94E zPe+j`Ey=8AW4bL|6Y zgMop8z%JX+pzqrS$c&m@RaIlpsi!}6jQkEv>yyaT_mz`FZgcKjTkJx`l2mlvu$#0| zTtPF00gM|m!COJgo{{+sWtg5U$IzE15d|p{m0aN9>wHtA1 z$pn|MU}<5I3(W9MO{r}s#)|}{UmxJ*C%`!`vfG8V6uwCNL}Qv@0WcJ=^DYySMV2Ll zZa-vYy)|B!g5Ft}jSb}BK|y86(h?3l#=t`^_Q!3d^XJS=?4$a_*>*xO{^+VCE9(m` z=?D-gNbuKH8Ip~yt)EAH08%IP-Z3gAC8e^G%P(T|>sQoQROg#RB-}&&{r&6f z^vOXbO$`k#oLzr_@qP6Ao9X)5_SliNF+Z7Y7o$d;v05`vb4dxaloZX|N0m2lb>0alY!Qxb~r++8&^-hJ-L zvA~Fg5rFV>Eg0#DJvfyL6BBcBg;~c0G}(P;tR|c%m%2!-ti97&Uw{7R%8jS&H6t_gkY_=?NjzsnXImR#(W82^#WfXN zCnVaPTw`(t-<2Kq{6#YnLhQFMO>YK9-|sy>TeQYUe-qnTC~;>_l_%LCB`#ji!zAt+ zqaplLf9Ce)jQM*~drJ$3xS_YLZBhN@=B5I^@vf@FQi45;&ef^h^XZ}ix-+dT#ez;> zVTcC2XoUp;5%dg_p%l}46ST1%(#^eMNDddv%h@|r+wUOi-DcXRciiD88S+y8Whd!A zfq|6NSTIqKi1|(o@c+8Z`sBvP*;&n;cY(44M>WwIJPbZq z3XP=DCY6R?1!s3kha(c8GZ3MKgrE=`O%6}6@m_jmo;v2XVIB9pG_T87GDl2-#(cMJ zY49l6oGdthWpTumRu2A%_ir#s2lss(Yhd8VU$8eKx89-xjHQc-!-Fdche1+?4E%o;}S;Ir0fK4Z# z!g>(Z$(V^5E^KP|eQ$U#F)pToFpCMCg%Vb9l91<4^sZN17*px*&+L>$mF`;2z1qHI z@0t+P@c1>j0uR=Z+DMiiQC!-VFd-<+^FMV&LPhhlkBnWTW%x8 zHJ>)qs@d87hSR7He%~TS?7nL$I(9J%Cl~?*AypL}1%{f>@hmJ1q9x5>!)VwXi}V_i z0_KdMqe5+LaW0IJ&9Cf@W=2jy;pOE8{At*<5vr`>f`Tci(iKU7Df|7q^78~&5gSXb@;{41ycpFTk< z$szz)YJJ@r&CudxXXkTgXJ>Bi>iPMHO?j~5l$QV`^Lx}pEK=F%F@x))BQCBWfB)Ox zzwr}-0s_SN`Q`M?|M_Q?!|7^vW@c>xdFM>yWh6yfg6|~Fn4jVu-G{`7|=_KioiR>#KflN<^^Mb z1-ZJq3X*9Cm0T`P&bM)K1w;ew5Nc{OeSHPe?Zgm(8GqMkxS!JlFv@XpSk$AYF)=X! zYTW433Vg=Yq@>a9PteeS+gMCgw6?bP%6=^#{yiiiLJ>6N7=%L$RV-%Na7rFnUwa`6&w-&YS`bhW@Jn3xgHfHj!X!omOsne^E?&@#f(xhG1)bLJv zwwi+{OASTX>zfCnq@*OnnVl46*_bV$y@N2p#2KCTKu7qHS7j5#6|X$Ky@_-?>g(k@ z)%h3aTN5YAiJN-;{K^;-7ZVWJ+}<9zh=lYi z!i-S`389Z?NID+6XTd$s?rtd6#JImryrw^JqA;cHoNlt!+udo^w&-gD4xyuG;c zlCO{~X0X41{+|)4DNPbNtqO);9(!?ssW3&ne$St=&}D)hs#@}7Fbd(g>Ln)Y-Q67} zER)kURrZ;4J2A!wBCf&{?lSjf`uDvsBCfyu4^=oIg6K2@ItX~r9q>R{O@N^kGqv|o zv)}FVDCBFR@VmZiP@O1UV>D4=b;J5hN5`Pe=B@V(5&cOV!SrHN=A`ZHp`w7jzvNHG zPw8p)k8%Q1m?iEZy{wdf@lXK#Bm)P$ClHYnX}}R+y28@%|2RJL^gqVoVm6iZ!K+QU z1b#+?Px}-k(5DVLx#{Pg z%T~uf97Yu{TTHe%sEAyfej57;WsbNDn^ev6rw{9bOA6@7##}k{K`SqabZW>rU3L~^ z6Mq`luy8oEnO}EqRdekN&po;DuUP-0#iCdCGpG7piql28rc9g2xCd2RANMAhw&pGhn$+{Ac9C za&*v#&*L1PjCAhaW58sIaQ_fh!hcS@nm+Uq%hHJIga7tDSI~><#J$RdX*6vz#q3Y% z!OYCN^mZ?$QqF%l7b{F|dZuTd`FD`_<lMAa{%pK15ni2Blw>HR-*czI><-5uJkGA+3D0bgn>s{P6&! zld%wSDcvi_ulMm&%hlT5Jr!=$Jru9qgzG9$QU({wV8K^Eq3cFvC3WKKE>N&#?72Kc z#F;#3!bO4@sa!_%(5EK&f`On&XV+WQrD;Kr?c>hz6B;|Uby z5o*jgTust=mQOpq;a;VxlcU;0r;etYFaRp(q0sO?T93io?XwnW#*(WLL5x=22GO`< zL%${yA?LN^0%ENRmz-++p)3?wcnM-9+q`4H=9fYxbI9tRQzs0nJOQ5Kx?kWC_3x<6 z%SjGfop{wRT{0y?ognT-CpU=xuH}Q*_hoVdGMmwxa zw#hzSng--FEO&^lT=}iM_8tS`0}%)V0LuPNaIln!2*&aKeGo}iMTN(f6*Jx(AhqQ- z#5Sl5@h$+Ac3vJWC9nv&kK@s1Y9UAq=mYxsnw6JV^5c*gw5<-LQ@*})d${OuRaI3m zpLJeEGS11#3HDKd(p`Z~3&ay$9UZM=+Jit0t{dd(ISCZ+e{8!@CYF{e z7`QM|pt#n{#*MW0CR`^^Z7&xVGFweGWZGW)alB&07vy3ar%Ueu(ZL{JERrCX#_@Z@ zkdZNUMgq9UDZsiJN+%*CBLm5#@(n#3zE`Wq>)39cB7+K@q6JwHm}r&X?$9R&T0#Q@ z%U^C6ts?#efezUDBVZ%+favcYCD#+LK?=>y#WgcKOAmG;d1|nz|J0&o>)fZx(OCh? zE8>Y6AXE|jlojoBb$@0frQ59y7V9h{k{FWDl8o!?8wE3qb&wIidGki`x}6}TC{v4- ztHu3bYHA8>k^*w#tt$Go^mP2pL?$Mt@=874&eqnwH`5An?z|BvrTXaG>1_WfmR#to zwnZ*ooSy@#KW{6@B{z1AKF+9JfW$~{NWn$R5^vAQDp+A^W~SIgNjP|GLs}ru<6Lxk zZf+b4;mcelGKB$2j5_x>ENfS1*}5<3jO4r0O1XQ>AVJE=AOI~!6Rn-`Cl?p5U-SpP z;9BD-UOU^eoZj+q!h}jD7^kCNqQbdSN`}dL3mlhKgM)%>oj_p8=19zOC5M0MOHZLB zydsD0kBBG2RIcn3uQtbyxVN|WyaPd3uWxJx>i@H-?Cj%+QAj=>cZzPJ*nWp#hn>AW zXOs=asbj#Q&>ePu5-t1C_STkfGf}voWP&0V+z>&rvZ@OIonc31y$Y;ia&5w25jk9 zH1BQM*y02u3>sv8iF(VVX-(wHVlOecI-6rVhmtYi6Pk>ROy&9!uJM-N!#l~Fc7X)vD_L)Ol@zMJnn89*eYZvl$>ASDQG`1e`ARS zXIG#VkIjcr|M!bFdO|)+gpxK1X^4~B2bMW6$J3=nTdVwL!+Wuf)B7_$T L8uFF07GeJn7eMNO diff --git a/guides/assets/images/getting_started/challenge.png b/guides/assets/images/getting_started/challenge.png deleted file mode 100644 index d05ef31bbe95f848acdfa5b8efce499ffb34aafc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20347 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 LwG_(bEZ+Zrq@Zc2 literal 42246 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 nI{9^SS?i>v$k+TH&Hqetzf diff --git a/guides/rails_guides/markdown/renderer.rb b/guides/rails_guides/markdown/renderer.rb index 9015a32c1792a..88e7d1db0ee5b 100644 --- a/guides/rails_guides/markdown/renderer.rb +++ b/guides/rails_guides/markdown/renderer.rb @@ -5,6 +5,21 @@ # Add more common shell commands Rouge::Lexers::Shell::BUILTINS << "|bin/rails|brew|bundle|gem|git|node|rails|rake|ruby|sqlite3|yarn" +# Register an IRB lexer for Rails 7.2+ console prompts like "store(dev)>" +class Rouge::Lexers::GuidesIRBLexer < Rouge::Lexers::IRBLexer + tag "irb" + + def prompt_regex + %r( + ^.*? + ( + (irb|pry|\w+\(\w+\)).*?[>"*] | + [>"*]> + ) + )x + end +end + module RailsGuides class Markdown class Renderer < Redcarpet::Render::HTML # :nodoc: @@ -15,7 +30,8 @@ class Renderer < Redcarpet::Render::HTML # :nodoc: cattr_accessor :edge, :version def block_code(code, language) - formatter = Rouge::Formatters::HTML.new + language, lines = split_language_highlights(language) + formatter = Rouge::Formatters::HTMLLineHighlighter.new(Rouge::Formatters::HTML.new, highlight_lines: lines) lexer = ::Rouge::Lexer.find_fancy(lexer_language(language)) formatted_code = formatter.format(lexer.lex(code)) <<~HTML @@ -160,6 +176,21 @@ def api_link(url) url.sub(/(?<=\.org)/, "/#{version}") end end + + # Parses "ruby#3,5-6,10" into ["ruby", [3,5,6,10]] for highlighting line numbers in code blocks + def split_language_highlights(language) + return [nil, []] unless language + + language, lines = language.split("#", 2) + lines = lines.to_s.split(",").flat_map{ parse_range(_1) } + + [language, lines] + end + + def parse_range(range) + first, last = range.split("-", 2).map(&:to_i) + Range.new(first, last || first).to_a + end end end end diff --git a/guides/source/documents.yaml b/guides/source/documents.yaml index 494ae6da57693..4c41b1dc135f7 100644 --- a/guides/source/documents.yaml +++ b/guides/source/documents.yaml @@ -5,6 +5,10 @@ name: Getting Started with Rails url: getting_started.html description: Everything you need to know to install Rails and create your first application. + - + name: Install Ruby on Rails + url: install_ruby_on_rails.html + description: Learn how to install the Ruby programming language and Ruby on Rails. - name: Models documents: diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md index 3bef8cc7aa6bc..d39637d22b59f 100644 --- a/guides/source/getting_started.md +++ b/guides/source/getting_started.md @@ -7,1150 +7,1548 @@ This guide covers getting up and running with Ruby on Rails. After reading this guide, you will know: -* How to install Rails, create a new Rails application, and connect your - application to a database. +* How to install Rails, create a new Rails application, and connect your application to a database. * The general layout of a Rails application. * The basic principles of MVC (Model, View, Controller) and RESTful design. * How to quickly generate the starting pieces of a Rails application. - +* How to deploy your app to production using Kamal. -------------------------------------------------------------------------------- -Guide Assumptions ------------------ - -This guide is designed for beginners who want to get started with creating a Rails -application from scratch. It does not assume that you have any prior experience -with Rails. +Introduction +------------ -Rails is a web application framework running on the Ruby programming language. -If you have no prior experience with Ruby, you will find a very steep learning -curve diving straight into Rails. There are several curated lists of online resources -for learning Ruby: +Welcome to Ruby on Rails! In this guide, we'll walk through the core concepts of building web applications with Rails. You don't need any experience with Rails to follow along with this guide. -* [Official Ruby Programming Language website](https://www.ruby-lang.org/en/documentation/) -* [List of Free Programming Books](https://github.com/EbookFoundation/free-programming-books/blob/master/books/free-programming-books-langs.md#ruby) +Rails is a web framework built for the Ruby programming language. Rails takes advantage of many features of Ruby so we **strongly** recommend learning the basics of Ruby so that you understand some of the basic terms and vocabulary you will see in this tutorial. -Be aware that some resources, while still excellent, cover older versions of -Ruby, and may not include some syntax that you will see in day-to-day -development with Rails. +- [Official Ruby Programming Language website](https://www.ruby-lang.org/en/documentation/) +- [List of Free Programming Books](https://github.com/EbookFoundation/free-programming-books/blob/master/books/free-programming-books-langs.md#ruby) -What is Rails? --------------- +Rails Philosophy +---------------- -Rails is a web application development framework written in the Ruby programming language. -It is designed to make programming web applications easier by making assumptions -about what every developer needs to get started. It allows you to write less -code while accomplishing more than many other languages and frameworks. -Experienced Rails developers also report that it makes web application -development more fun. +Rails is a web application development framework written in the Ruby programming language. It is designed to make programming web applications easier by making assumptions about what every developer needs to get started. It allows you to write less code while accomplishing more than many other languages and frameworks. Experienced Rails developers also report that it makes web application development more fun. -Rails is opinionated software. It makes the assumption that there is a "best" -way to do things, and it's designed to encourage that way - and in some cases to -discourage alternatives. If you learn "The Rails Way" you'll probably discover a -tremendous increase in productivity. If you persist in bringing old habits from -other languages to your Rails development, and trying to use patterns you -learned elsewhere, you may have a less happy experience. +Rails is opinionated software. It makes the assumption that there is a "best" way to do things, and it's designed to encourage that way - and in some cases to discourage alternatives. If you learn "The Rails Way" you'll probably discover a tremendous increase in productivity. If you persist in bringing old habits from other languages to your Rails development, and trying to use patterns you learned elsewhere, you may have a less happy experience. The Rails philosophy includes two major guiding principles: -* **Don't Repeat Yourself:** DRY is a principle of software development which - states that "Every piece of knowledge must have a single, unambiguous, authoritative - representation within a system". By not writing the same information over and over - again, our code is more maintainable, more extensible, and less buggy. -* **Convention Over Configuration:** Rails has opinions about the best way to do many - things in a web application, and defaults to this set of conventions, rather than - require that you specify minutiae through endless configuration files. - -Creating a New Rails Project ----------------------------- - -TIP: You can create new Rails apps with a preconfigured Dev Container development environment. This -is the fastest way to get started with Rails. -For instructions see [Getting Started with Dev Containers](getting_started_with_devcontainer.html) - -The best way to read this guide is to follow it step by step. All steps are -essential to run this example application and no additional code or steps are -needed. - -By following along with this guide, you'll create a Rails project called -`blog`, a (very) simple weblog. Before you can start building the application, -you need to make sure that you have Rails itself installed. - -NOTE: The examples below use `$` to represent your terminal prompt in a UNIX-like OS, -though it may have been customized to appear differently. If you are using Windows, -your prompt will look something like `C:\source_code>`. - -### Installing Rails - -Before you install Rails, you should check to make sure that your system has the -proper prerequisites installed. These include: - -* Ruby -* SQLite3 - -#### Installing Ruby - -Open up a command line prompt. On macOS open Terminal.app; on Windows choose -"Run" from your Start menu and type `cmd.exe`. Any commands prefaced with a -dollar sign `$` should be run in the command line. Verify that you have a -current version of Ruby installed: - -```bash -$ ruby --version -ruby 3.2.0 -``` - -Rails requires Ruby version 3.2.0 or later. It is preferred to use the latest Ruby version. -If the version number returned is less than that number (such as 2.3.7, or 1.8.7), -you'll need to install a fresh copy of Ruby. - -To install Rails on Windows, you'll first need to install [Ruby Installer](https://rubyinstaller.org/). - -For more installation methods for most Operating Systems take a look at -[ruby-lang.org](https://www.ruby-lang.org/en/documentation/installation/). - -#### Installing SQLite3 +- **Don't Repeat Yourself:** DRY is a principle of software development which states that "Every piece of knowledge must have a single, unambiguous, authoritative representation within a system". By not writing the same information over and over again, our code is more maintainable, more extensible, and less buggy. +- **Convention Over Configuration:** Rails has opinions about the best way to do many things in a web application, and defaults to this set of conventions, rather than require that you define them yourself through endless configuration files. -You will also need an installation of the SQLite3 database. -Many popular UNIX-like OSes ship with an acceptable version of SQLite3. -Others can find installation instructions at the [SQLite3 website](https://www.sqlite.org). +Creating a New Rails App +------------------------ -Verify that it is correctly installed and in your load `PATH`: +We're going to build a project called `store` - a simple e-commerce app that demonstrates several of Rails' built-in features. -```bash -$ sqlite3 --version -``` +TIP: Any commands prefaced with a dollar sign `$` should be run in the terminal. -The program should report its version. +### Prerequisites -#### Installing Rails +For this project, you will need: -To install Rails, use the `gem install` command provided by RubyGems: +* Ruby 3.2 or newer +* Rails 8.0.0 or newer +* A code editor -```bash -$ gem install rails -``` +Follow the [Install Ruby on Rails Guide](install_ruby_on_rails.html) if you need to install Ruby and/or Rails. -To verify that you have everything installed correctly, you should be able to -run the following in a new terminal: +Let's verify the correct version of Rails is installed. To display the current version, open a terminal and run the following. You should see a version number printed out: ```bash $ rails --version Rails 8.1.0 ``` -If it says something like "Rails 8.1.0", you are ready to continue. +The version shown should be Rails 8.0.0 or higher. -### Creating the Blog Application +### Creating Your First Rails App -Rails comes with a number of scripts called generators that are designed to make -your development life easier by creating everything that's necessary to start -working on a particular task. One of these is the new application generator, -which will provide you with the foundation of a fresh Rails application so that -you don't have to write it yourself. +Rails comes with several commands to make life easier. Run `rails --help` to see all of the commands. -To use this generator, open a terminal, navigate to a directory where you have -rights to create files, and run: +`rails new` generates the foundation of a fresh Rails application for you, so let's start there. + +To create our `store` application, run the following command in your terminal: ```bash -$ rails new blog +$ rails new store ``` -This will create a Rails application called Blog in a `blog` directory and -install the gem dependencies that are already mentioned in `Gemfile` using -`bundle install`. - -TIP: You can see all of the command line options that the Rails application -generator accepts by running `rails new --help`. +NOTE: You can customize the application Rails generates by using flags. To see these options, run `rails new --help`. -After you create the blog application, switch to its folder: +After your new application is created, switch to its directory: ```bash -$ cd blog +$ cd store ``` -The `blog` directory will have a number of generated files and folders that make -up the structure of a Rails application. Most of the work in this tutorial will -happen in the `app` folder, but here's a basic rundown on the function of each -of the files and folders that Rails creates by default: +### Directory Structure + +Let's take a quick glance at the files and directories that are included in a new Rails application. You can open this folder in your code editor or run `ls -la` in your terminal to see the files and directories. | File/Folder | Purpose | | ----------- | ------- | -|app/|Contains the controllers, models, views, helpers, mailers, jobs, and assets for your application. You'll focus on this folder for the remainder of this guide.| +|app/|Contains the controllers, models, views, helpers, mailers, jobs, and assets for your application. **You'll focus mostly on this folder for the remainder of this guide.**| |bin/|Contains the `rails` script that starts your app and can contain other scripts you use to set up, update, deploy, or run your application.| |config/|Contains configuration for your application's routes, database, and more. This is covered in more detail in [Configuring Rails Applications](configuring.html).| -|config.ru|Rack configuration for Rack-based servers used to start the application. For more information about Rack, see the [Rack website](https://rack.github.io/).| +|config.ru|[Rack](https://rack.github.io) configuration for Rack-based servers used to start the application.| |db/|Contains your current database schema, as well as the database migrations.| |Dockerfile|Configuration file for Docker.| -|Gemfile
Gemfile.lock|These files allow you to specify what gem dependencies are needed for your Rails application. These files are used by the Bundler gem. For more information about Bundler, see the [Bundler website](https://bundler.io).| +|Gemfile
Gemfile.lock|These files allow you to specify what gem dependencies are needed for your Rails application. These files are used by the [Bundler](https://bundler.io) gem.| |lib/|Extended modules for your application.| |log/|Application log files.| |public/|Contains static files and compiled assets. When your app is running, this directory will be exposed as-is.| |Rakefile|This file locates and loads tasks that can be run from the command line. The task definitions are defined throughout the components of Rails. Rather than changing `Rakefile`, you should add your own tasks by adding files to the `lib/tasks` directory of your application.| |README.md|This is a brief instruction manual for your application. You should edit this file to tell others what your application does, how to set it up, and so on.| |script/|Contains one-off or general purpose [scripts](https://github.com/rails/rails/blob/main/railties/lib/rails/generators/rails/script/USAGE) and [benchmarks](https://github.com/rails/rails/blob/main/railties/lib/rails/generators/rails/benchmark/USAGE).| -|storage/|Active Storage files for Disk Service. This is covered in [Active Storage Overview](active_storage_overview.html).| +|storage/|Contains SQLite databases and Active Storage files for Disk Service. This is covered in [Active Storage Overview](active_storage_overview.html).| |test/|Unit tests, fixtures, and other test apparatus. These are covered in [Testing Rails Applications](testing.html).| |tmp/|Temporary files (like cache and pid files).| |vendor/|A place for all third-party code. In a typical Rails application this includes vendored gems.| |.dockerignore|This file tells Docker which files it should not copy into the container.| -|.gitattributes|This file defines metadata for specific paths in a git repository. This metadata can be used by git and other tools to enhance their behavior. See the [gitattributes documentation](https://git-scm.com/docs/gitattributes) for more information.| +|.gitattributes|This file defines metadata for specific paths in a Git repository. This metadata can be used by Git and other tools to enhance their behavior. See the [gitattributes documentation](https://git-scm.com/docs/gitattributes) for more information.| +|.git/|Contains Git repository files.| |.github/|Contains GitHub specific files.| -|.gitignore|This file tells git which files (or patterns) it should ignore. See [GitHub - Ignoring files](https://help.github.com/articles/ignoring-files) for more information about ignoring files.| +|.gitignore|This file tells Git which files (or patterns) it should ignore. See [GitHub - Ignoring files](https://help.github.com/articles/ignoring-files) for more information about ignoring files.| +|.kamal/|Contains Kamal secrets and deployment hooks.| |.rubocop.yml|This file contains the configuration for RuboCop.| |.ruby-version|This file contains the default Ruby version.| +### Model-View-Controller Basics + +Rails code is organized using the Model-View-Controller (MVC) architecture. With MVC, we have three main concepts where the majority of our code lives: + +* Model - Manages the data in your application. Typically, your database tables. +* View - Handles rendering responses in different formats like HTML, JSON, XML, etc. +* Controller - Handles user interactions and the logic for each request. + +Now that we've got a basic understanding of MVC, let's see how it's used in Rails. + Hello, Rails! ------------- -To begin with, let's get some text up on screen quickly. To do this, you need to -get your Rails application server running. - -### Starting Up the Web Server +Let's start easy and boot up our Rails server for the first time. -You actually have a functional Rails application already. To see it, you need to -start a web server on your development machine. You can do this by running the -following command in the `blog` directory: +In your terminal, run the following command in the `store` directory: ```bash $ bin/rails server ``` -TIP: If you are using Windows, you have to pass the scripts under the `bin` -folder directly to the Ruby interpreter e.g. `ruby bin\rails server`. +This will start up a web server called Puma that will serve static files and your Rails application: -TIP: JavaScript asset compression requires you to -have a JavaScript runtime available on your system, in the absence -of a runtime you will see an `execjs` error during asset compression. -Usually macOS and Windows come with a JavaScript runtime installed. -`therubyrhino` is the recommended runtime for JRuby users and is added by -default to the `Gemfile` in apps generated under JRuby. You can investigate -all the supported runtimes at [ExecJS](https://github.com/rails/execjs#readme). +```bash +=> Booting Puma +=> Rails 8.0.0 application starting in development +=> Run `bin/rails server --help` for more startup options +Puma starting in single mode... +* Puma version: 6.4.3 (ruby 3.3.5-p100) ("The Eagle of Durango") +* Min threads: 3 +* Max threads: 3 +* Environment: development +* PID: 12345 +* Listening on http://127.0.0.1:3000 +* Listening on http://[::1]:3000 +Use Ctrl-C to stop +``` -This will start up Puma, a web server distributed with Rails by default. To see -your application in action, open a browser window and navigate to -. You should see the Rails default information page: +To see your Rails application, open http://localhost:3000 in your browser. You will see the default Rails welcome page: -![Rails startup page screenshot](images/getting_started/rails_welcome.png) +![Rails welcome page](images/getting_started/rails_welcome.png) -When you want to stop the web server, hit Ctrl+C in the terminal window where -it's running. In the development environment, Rails does not generally -require you to restart the server; changes you make in files will be -automatically picked up by the server. +It works! -The Rails startup page is the _smoke test_ for a new Rails -application: it makes sure that you have your software configured correctly -enough to serve a page. +This page is the *smoke test* for a new Rails application, ensuring that everything is working behind the scenes to serve a page. -### Say "Hello", Rails +To stop the Rails server anytime, press `Ctrl-C` in your terminal. -To get Rails saying "Hello", you need to create at minimum a *route*, a -*controller* with an *action*, and a *view*. A route maps a request to a -controller action. A controller action performs the necessary work to handle the -request, and prepares any data for the view. A view displays data in a desired -format. +### Automatic Reloading in Development -In terms of implementation: Routes are rules written in a Ruby [DSL -(Domain-Specific Language)](https://en.wikipedia.org/wiki/Domain-specific_language). -Controllers are Ruby classes, and their public methods are actions. And views -are templates, usually written in a mixture of HTML and Ruby. +Developer happiness is a cornerstone philosophy of Rails and one way of achieving this is with automatic code reloading in development. -Let's start by adding a route to our routes file, `config/routes.rb`, at the -top of the `Rails.application.routes.draw` block: +Once you start the Rails server, new files or changes to existing files are detected and automatically loaded or reloaded as necessary. This allows you to focus on building without having to restart your Rails server after every change. -```ruby -Rails.application.routes.draw do - get "/articles", to: "articles#index" +You may also notice that Rails applications rarely use `require` statements. Rails uses naming conventions to require files automatically so you can focus on writing your application code. - # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html -end -``` +See [Autoloading and Reloading Constants](autoloading_and_reloading_constants.html) for more details. -The route above declares that `GET /articles` requests are mapped to the `index` -action of `ArticlesController`. +Creating a Database Model +------------------------- -To create `ArticlesController` and its `index` action, we'll run the controller -generator (with the `--skip-routes` option because we already have an -appropriate route): +Active Record is a feature of Rails that maps relational databases to Ruby code. It helps generate the structured query language (SQL) for interacting with the database like creating, updating, and deleting tables and records. Our application is using SQLite which is the default for Rails. + +Let's start by adding a database table to our Rails application to add products to our simple e-commerce store. ```bash -$ bin/rails generate controller Articles index --skip-routes +$ bin/rails generate model Product name:string ``` -Rails will create several files for you: +This command tells Rails to generate a model named `Product` which has a `name` column and type of `string` in the database. Later on, you'll learn how to add other column types. +You'll see the following in your terminal: + +```bash + invoke active_record + create db/migrate/20240426151900_create_products.rb + create app/models/product.rb + invoke test_unit + create test/models/product_test.rb + create test/fixtures/products.yml ``` -create app/controllers/articles_controller.rb -invoke erb -create app/views/articles -create app/views/articles/index.html.erb -invoke test_unit -create test/controllers/articles_controller_test.rb -invoke helper -create app/helpers/articles_helper.rb -invoke test_unit -``` -The most important of these is the controller file, -`app/controllers/articles_controller.rb`. Let's take a look at it: +This command does several things. It creates... + +1. a migration in the `db/migrate` folder +2. an Active Record model in `app/models/product.rb` +3. tests and test fixtures for this model + +NOTE: Model names are *singular*, because an instantiated model represents a single record in the database (i.e., You are creating a _product_ to add to the database.). + +### Database Migrations + +A _migration_ is a set of changes we want to make to our database. + +By defining migrations, we're telling Rails how to change the database to add, change, or remove tables, columns or other attributes of our database. This helps keep track of changes we make in development (only on our computer) so they can be deployed to production (live, online!) safely. + +In your code editor, open the migration Rails created for us so we can see what the migration does. This is located in `db/migrate/_create_products.rb`: ```ruby -class ArticlesController < ApplicationController - def index +class CreateProducts < ActiveRecord::Migration[8.0] + def change + create_table :products do |t| + t.string :name + + t.timestamps + end end end ``` -The `index` action is empty. When an action does not explicitly render a view -(or otherwise trigger an HTTP response), Rails will automatically render a view -that matches the name of the controller and action. Convention Over -Configuration! Views are located in the `app/views` directory. So the `index` -action will render `app/views/articles/index.html.erb` by default. +This migration is telling Rails to create a new database table named `products`. -Let's open `app/views/articles/index.html.erb`, and replace its contents with: +NOTE: In contrast to the model above, Rails makes the database table names _plural_, because the database holds all of the instances of each model (i.e., You are creating a database of _products_). -```html -

Hello, Rails!

+The `create_table` block then defines which columns and types should be defined in this database table. + +`t.string :name` tells Rails to create a column in the `products` table called `name` and set the type as `string`. + +`t.timestamps` is a shortcut for defining two columns on your models: `created_at:datetime` and `updated_at:datetime`. You'll see these columns on most Active Record models in Rails and they are automatically set by Active Record when creating or updating records. + +### Running Migrations + +Now that you have defined what change to make to the database, use the following command to run the migrations: + +```bash +$ bin/rails db:migrate +``` +This command checks for any new migrations and applies them to your database. Its output looks like this: + +```bash +== 20240426151900 CreateProducts: migrating =================================== +-- create_table(:products) + -> 0.0030s +== 20240426151900 CreateProducts: migrated (0.0031s) ========================== ``` -If you previously stopped the web server to run the controller generator, -restart it with `bin/rails server`. Now visit , -and see our text displayed! +TIP: If you make a mistake, you can run `rails db:rollback` to undo the last migration. -### Setting the Application Home Page +Rails Console +------------- -At the moment, still displays a page with the Ruby on Rails logo. -Let's display our "Hello, Rails!" text at as well. To do -so, we will add a route that maps the *root path* of our application to the -appropriate controller and action. +Now that we have created our products table, we can interact with it in Rails. Let's try it out. -Let's open `config/routes.rb`, and add the following `root` route to the top of -the `Rails.application.routes.draw` block: +For this, we're going to use a feature of Rails called the *console*. The console is a helpful, interactive tool for testing our code in our Rails application. -```ruby -Rails.application.routes.draw do - root "articles#index" +```bash +$ bin/rails console +``` - get "/articles", to: "articles#index" -end +You will be presented with a prompt like the following: + +```irb +Loading development environment (Rails 8.1.0) +store(dev)> ``` -Now we can see our "Hello, Rails!" text when we visit , -confirming that the `root` route is also mapped to the `index` action of -`ArticlesController`. +Here we can type code that will be executed when we hit `Enter`. Let's try printing out the Rails version: -TIP: To learn more about routing, see [Rails Routing from the Outside In]( -routing.html). +```irb +store(dev)> Rails.version +=> "8.0.0" +``` -Autoloading ------------ +It works! -Rails applications **do not** use `require` to load application code. +Active Record Model Basics +------------------------- -You may have noticed that `ArticlesController` inherits from `ApplicationController`, but `app/controllers/articles_controller.rb` does not have anything like +When we ran the Rails model generator to create the `Product` model, it created a file at `app/models/product.rb`. This file creates a class that uses Active Record for interacting with our `products` database table. ```ruby -require "application_controller" # DON'T DO THIS. +class Product < ApplicationRecord +end ``` -Application classes and modules are available everywhere, you do not need and **should not** load anything under `app` with `require`. This feature is called _autoloading_, and you can learn more about it in [_Autoloading and Reloading Constants_](autoloading_and_reloading_constants.html). +You might be surprised that there is no code in this class. How does Rails know what defines this model? -You only need `require` calls for two use cases: +When the `Product` model is used, Rails will query the database table for the column names and types and automatically generate code for these attributes. Rails saves us from writing this boilerplate code and instead takes care of it for us behind the scenes so we can focus on our application logic instead. -* To load files under the `lib` directory. -* To load gem dependencies that have `require: false` in the `Gemfile`. +Let's use the Rails console to see what columns Rails detects for the Product model. -MVC and You ------------ +Run: + +```irb +store(dev)> Product.column_names +``` -So far, we've discussed routes, controllers, actions, and views. All of these -are typical pieces of a web application that follows the [MVC (Model-View-Controller)]( -https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller) pattern. -MVC is a design pattern that divides the responsibilities of an application to -make it easier to reason about. Rails follows this design pattern by convention. +And you should see: -Since we have a controller and a view to work with, let's generate the next -piece: a model. +```irb +=> ["id", "name", "created_at", "updated_at"] +``` -### Generating a Model +Rails asked the database for column information above and used that information to define attributes on the `Product` class dynamically so you don't have to manually define each of them. This is one example of how Rails makes development a breeze. -A *model* is a Ruby class that is used to represent data. Additionally, models -can interact with the application's database through a feature of Rails called -*Active Record*. +### Creating Records -To define a model, we will use the model generator: +We can instantiate a new Product record with the following code: -```bash -$ bin/rails generate model Article title:string body:text +```irb +store(dev)> product = Product.new(name: "T-Shirt") +=> # ``` -NOTE: Model names are **singular**, because an instantiated model represents a -single data record. To help remember this convention, think of how you would -call the model's constructor: we want to write `Article.new(...)`, **not** -`Articles.new(...)`. +The `product` variable is an instance of `Product`. It has not been saved to the database, and so does not have an ID, created_at, or updated_at timestamps. -This will create several files: +We can call `save` to write the record to the database. +```irb +store(dev)> product.save + TRANSACTION (0.1ms) BEGIN immediate TRANSACTION /*application='Store'*/ + Product Create (0.9ms) INSERT INTO "products" ("name", "created_at", "updated_at") VALUES ('T-Shirt', '2024-11-09 16:35:01.117836', '2024-11-09 16:35:01.117836') RETURNING "id" /*application='Store'*/ + TRANSACTION (0.9ms) COMMIT TRANSACTION /*application='Store'*/ +=> true ``` -invoke active_record -create db/migrate/_create_articles.rb -create app/models/article.rb -invoke test_unit -create test/models/article_test.rb -create test/fixtures/articles.yml + +When `save` is called, Rails takes the attributes in memory and generates an `INSERT` SQL query to insert this record into the database. + +Rails also updates the object in memory with the database record `id` along with the `created_at` and `updated_at` timestamps. We can see that by printing out the `product` variable. + +```irb +store(dev)> product +=> # ``` -The two files we'll focus on are the migration file -(`db/migrate/_create_articles.rb`) and the model file -(`app/models/article.rb`). +Similar to `save`, we can use `create` to instantiate and save an Active Record model in a single call. -### Database Migrations +```irb +store(dev)> Product.create(name: "Pants") + TRANSACTION (0.1ms) BEGIN immediate TRANSACTION /*application='Store'*/ + Product Create (0.4ms) INSERT INTO "products" ("name", "created_at", "updated_at") VALUES ('Pants', '2024-11-09 16:36:01.856751', '2024-11-09 16:36:01.856751') RETURNING "id" /*application='Store'*/ + TRANSACTION (0.1ms) COMMIT TRANSACTION /*application='Store'*/ +=> # +``` -*Migrations* are used to alter the structure of an application's database. In -Rails applications, migrations are written in Ruby so that they can be -database-agnostic. +### Querying Records -Let's take a look at the contents of our new migration file: +We can also look up records from the database using our Active Record model. -```ruby -class CreateArticles < ActiveRecord::Migration[8.1] - def change - create_table :articles do |t| - t.string :title - t.text :body +To find all the Product records in the database, we can use the `all` method. This is a _class_ method, which is why we can use it on Product (versus an instance method that we would call on the product instance, like `save` above). - t.timestamps - end - end -end +```irb +store(dev)> Product.all + Product Load (0.1ms) SELECT "products".* FROM "products" /* loading for pp */ LIMIT 11 /*application='Store'*/ +=> [#, + #] ``` -The call to `create_table` specifies how the `articles` table should be -constructed. By default, the `create_table` method adds an `id` column as an -auto-incrementing primary key. So the first record in the table will have an -`id` of 1, the next record will have an `id` of 2, and so on. +This generates a `SELECT` SQL query to load all records from the `products` table. Each record is automatically converted into an instance of our Product Active Record model so we can easily work with them from Ruby. -Inside the block for `create_table`, two columns are defined: `title` and -`body`. These were added by the generator because we included them in our -generate command (`bin/rails generate model Article title:string body:text`). +TIP: The `all` method returns an `ActiveRecord::Relation` object which is an Array-like collection of database records with features to filter, sort, and execute other database operations. -On the last line of the block is a call to `t.timestamps`. This method defines -two additional columns named `created_at` and `updated_at`. As we will see, -Rails will manage these for us, setting the values when we create or update a -model object. +### Filtering & Ordering Records -Let's run our migration with the following command: +What if we want to filter the results from our database? We can use `where` to filter records by a column. -```bash -$ bin/rails db:migrate +```irb +store(dev)> Product.where(name: "Pants") + Product Load (1.5ms) SELECT "products".* FROM "products" WHERE "products"."name" = 'Pants' /* loading for pp */ LIMIT 11 /*application='Store'*/ +=> [#] ``` +This generates a `SELECT` SQL query but also adds a `WHERE` clause to filter the records that have a `name` matching `"Pants"`. This also returns an `ActiveRecord::Relation` because multiple records may have the same name. -The command will display output indicating that the table was created: +We can use `order(name: :asc)` to sort records by name in ascending alphabetical order by `name`. -``` -== CreateArticles: migrating =================================== --- create_table(:articles) - -> 0.0018s -== CreateArticles: migrated (0.0018s) ========================== +```irb +store(dev)> Product.order(name: :asc) + Product Load (0.3ms) SELECT "products".* FROM "products" /* loading for pp */ ORDER BY "products"."name" ASC LIMIT 11 /*application='Store'*/ +=> [#, + #] ``` -TIP: To learn more about migrations, see [Active Record Migrations]( -active_record_migrations.html). +### Finding Records -Now we can interact with the table using our model. +What if we want to find one specific record? -### Using a Model to Interact with the Database +We can do this by using the `find` class method to look up a single record by ID. Call the method and pass in the specific ID by using the following code: -To play with our model a bit, we're going to use a feature of Rails called the -*console*. The console is an interactive coding environment just like `irb`, but -it also automatically loads Rails and our application code. +```irb +store(dev)> Product.find(1) + Product Load (0.2ms) SELECT "products".* FROM "products" WHERE "products"."id" = 1 LIMIT 1 /*application='Store'*/ +=> # +``` -Let's launch the console with this command: +This generates a `SELECT` query but specifies a `WHERE` for the `id` column matching the ID of `1` that was passed in. It also adds a `LIMIT` to only return a single record. -```bash -$ bin/rails console -``` +This time, we get a `Product` instance instead of an `ActiveRecord::Relation` since we're only retrieving a single record from the database. -You should see a rails console prompt like: +### Updating Records -```irb -Loading development environment (Rails 8.1.0) -blog(dev)> -``` +Records can be updated in 2 ways: using `update` or assigning attributes and calling `save`. -At this prompt, we can initialize a new `Article` object: +We can call `update` on a Product instance and pass in a Hash of new attributes to save to the database. This will assign the attributes, run validations, and save the changes to the database in one method call. ```irb -blog(dev)> article = Article.new(title: "Hello Rails", body: "I am on Rails!") +store(dev)> product = Product.find(1) +store(dev)> product.update(name: "Shoes") + TRANSACTION (0.1ms) BEGIN immediate TRANSACTION /*application='Store'*/ + Product Update (0.3ms) UPDATE "products" SET "name" = 'Shoes', "updated_at" = '2024-11-09 22:38:19.638912' WHERE "products"."id" = 1 /*application='Store'*/ + TRANSACTION (0.4ms) COMMIT TRANSACTION /*application='Store'*/ +=> true ``` -It's important to note that we have only *initialized* this object. This object -is not saved to the database at all. It's only available in the console at the -moment. To save the object to the database, we must call [`save`]( -https://api.rubyonrails.org/classes/ActiveRecord/Persistence.html#method-i-save): +This updated the name of the "T-Shirt" product to "Shoes" in the database. Confirm this by running `Product.all` again. ```irb -blog(dev)> article.save -(0.1ms) begin transaction -Article Create (0.4ms) INSERT INTO "articles" ("title", "body", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["title", "Hello Rails"], ["body", "I am on Rails!"], ["created_at", "2020-01-18 23:47:30.734416"], ["updated_at", "2020-01-18 23:47:30.734416"]] -(0.9ms) commit transaction -=> true +store(dev)> Product.all ``` -The above output shows an `INSERT INTO "articles" ...` database query. This -indicates that the article has been inserted into our table. And if we take a -look at the `article` object again, we see something interesting has happened: +You will see two products: Shoes and Pants. ```irb -blog(dev)> article -=> #
+ Product Load (0.3ms) SELECT "products".* FROM "products" /* loading for pp */ LIMIT 11 /*application='Store'*/ +=> +[#, + #] ``` -The `id`, `created_at`, and `updated_at` attributes of the object are now set. -Rails did this for us when we saved the object. +Alternatively, we can assign attributes and call `save` when we're ready to validate and save changes to the database. -When we want to fetch this article from the database, we can call [`find`]( -https://api.rubyonrails.org/classes/ActiveRecord/FinderMethods.html#method-i-find) -on the model and pass the `id` as an argument: +Let's change the name "Shoes" back to "T-Shirt". ```irb -blog(dev)> Article.find(1) -=> #
+store(dev)> product = Product.find(1) +store(dev)> product.name = "T-Shirt" +=> "T-Shirt" +store(dev)> product.save + TRANSACTION (0.1ms) BEGIN immediate TRANSACTION /*application='Store'*/ + Product Update (0.2ms) UPDATE "products" SET "name" = 'T-Shirt', "updated_at" = '2024-11-09 22:39:09.693548' WHERE "products"."id" = 1 /*application='Store'*/ + TRANSACTION (0.0ms) COMMIT TRANSACTION /*application='Store'*/ +=> true ``` -And when we want to fetch all articles from the database, we can call [`all`]( -https://api.rubyonrails.org/classes/ActiveRecord/Scoping/Named/ClassMethods.html#method-i-all) -on the model: +### Deleting Records + +The `destroy` method can be used to delete a record from the database. ```irb -blog(dev)> Article.all -=> #]> +store(dev)> product.destroy + TRANSACTION (0.1ms) BEGIN immediate TRANSACTION /*application='Store'*/ + Product Destroy (0.4ms) DELETE FROM "products" WHERE "products"."id" = 1 /*application='Store'*/ + TRANSACTION (0.1ms) COMMIT TRANSACTION /*application='Store'*/ +=> # ``` -This method returns an [`ActiveRecord::Relation`]( -https://api.rubyonrails.org/classes/ActiveRecord/Relation.html) object, which -you can think of as a super-powered array. +This deleted the T-Shirt product from our database. We can confirm this with `Product.all` to see that that it only returns Pants. -TIP: To learn more about models, see [Active Record Basics]( -active_record_basics.html) and [Active Record Query Interface]( -active_record_querying.html). +```irb +store(dev)> Product.all + Product Load (1.9ms) SELECT "products".* FROM "products" /* loading for pp */ LIMIT 11 /*application='Store'*/ +=> +[#] +``` -Models are the final piece of the MVC puzzle. Next, we will connect all of the -pieces together. +### Validations -### Showing a List of Articles +Active Record provides *validations* which allows you to ensure data inserted into the database adheres to certain rules. -Let's go back to our controller in `app/controllers/articles_controller.rb`, and -change the `index` action to fetch all articles from the database: +Let's add a `presence` validation to the Product model to ensure that all products must have a `name`. ```ruby -class ArticlesController < ApplicationController - def index - @articles = Article.all - end +class Product < ApplicationRecord + validates :name, presence: true end ``` -Controller instance variables can be accessed by the view. That means we can -reference `@articles` in `app/views/articles/index.html.erb`. Let's open that -file, and replace its contents with: +Let's try to create a Product without a name in the Rails console. -```html+erb -

Articles

- -
    - <% @articles.each do |article| %> -
  • - <%= article.title %> -
  • - <% end %> -
+```irb +store(dev)> product = Product.new +store(dev)> product.save +=> false ``` -The above code is a mixture of HTML and *ERB*. ERB, short for [Embedded Ruby](https://docs.ruby-lang.org/en/master/ERB.html), is a templating system that -evaluates Ruby code embedded in a document. Here, we can see two types of ERB -tags: `<% %>` and `<%= %>`. The `<% %>` tag means "evaluate the enclosed Ruby -code." The `<%= %>` tag means "evaluate the enclosed Ruby code, and output the -value it returns." Anything you could write in a regular Ruby program can go -inside these ERB tags, though it's usually best to keep the contents of ERB tags -short, for readability. +This time `save` returns `false` because the `name` attribute wasn't specified. -Since we don't want to output the value returned by `@articles.each`, we've -enclosed that code in `<% %>`. But, since we *do* want to output the value -returned by `article.title` (for each article), we've enclosed that code in -`<%= %>`. +Rails automatically runs validations during create, update, and save operations to ensure valid input. To see a list of errors generated by validations, we can call `errors` on the instance. -We can see the final result by visiting . (Remember that -`bin/rails server` must be running!) Here's what happens when we do that: +```irb +store(dev)> product.errors +=> #]> +``` -1. The browser makes a request: `GET http://localhost:3000`. -2. Our Rails application receives this request. -3. The Rails router maps the root route to the `index` action of `ArticlesController`. -4. The `index` action uses the `Article` model to fetch all articles in the database. -5. Rails automatically renders the `app/views/articles/index.html.erb` view. -6. The ERB code in the view is evaluated to output HTML. -7. The server sends a response containing the HTML back to the browser. +This returns an `ActiveModel::Errors` object that can tell us exactly which errors are present. -We've connected all the MVC pieces together, and we have our first controller -action! Next, we'll move on to the second action. +It also can generate friendly error messages for us that we can use in our user interface. -CRUDit Where CRUDit Is Due --------------------------- +```irb +store(dev)> product.errors.full_messages +=> ["Name can't be blank"] +``` -Almost all web applications involve [CRUD (Create, Read, Update, and Delete)]( -https://en.wikipedia.org/wiki/Create,_read,_update,_and_delete) operations. You -may even find that the majority of the work your application does is CRUD. Rails -acknowledges this, and provides many features to help simplify code doing CRUD. +Now let's build a web interface for our Products. -Let's begin exploring these features by adding more functionality to our -application. +We are done with the console for now, so you can exit out of it by running `exit`. -### Showing a Single Article +A Request's Journey Through Rails +--------------------- -We currently have a view that lists all articles in our database. Let's add a -new view that shows the title and body of a single article. +To get Rails saying "Hello", you need to create at minimum a _route_, a _controller_ with an _action_, and a _view_. A route maps a request to a controller action. A controller action performs the necessary work to handle the request, and prepares any data for the view. A view displays data in a desired format. -We start by adding a new route that maps to a new controller action (which we -will add next). Open `config/routes.rb`, and insert the last route shown here: +In terms of implementation: Routes are rules written in a Ruby [DSL (Domain-Specific Language)](https://en.wikipedia.org/wiki/Domain-specific_language). Controllers are Ruby classes, and their public methods are actions. And views are templates, usually written in a mixture of HTML and Ruby. -```ruby -Rails.application.routes.draw do - root "articles#index" +That's the short of it, but we’re going to walk through each of these steps in more detail next. - get "/articles", to: "articles#index" - get "/articles/:id", to: "articles#show" -end -``` +Routes +------ -The new route is another `get` route, but it has something extra in its path: -`:id`. This designates a route *parameter*. A route parameter captures a segment -of the request's path, and puts that value into the `params` Hash, which is -accessible by the controller action. For example, when handling a request like -`GET http://localhost:3000/articles/1`, `1` would be captured as the value for -`:id`, which would then be accessible as `params[:id]` in the `show` action of -`ArticlesController`. +In Rails, a route is the part of the URL that determines how an incoming HTTP request is directed to the appropriate controller and action for processing. First, let's do a quick refresher of URLs and HTTP Request methods. -Let's add that `show` action now, below the `index` action in -`app/controllers/articles_controller.rb`: +### Parts of a URL -```ruby -class ArticlesController < ApplicationController - def index - @articles = Article.all - end +Let's examine the different parts of a URL: - def show - @article = Article.find(params[:id]) - end -end ``` - -The `show` action calls `Article.find` ([mentioned -previously](#using-a-model-to-interact-with-the-database)) with the ID captured -by the route parameter. The returned article is stored in the `@article` -instance variable, so it is accessible by the view. By default, the `show` -action will render `app/views/articles/show.html.erb`. - -Let's create `app/views/articles/show.html.erb`, with the following contents: - -```html+erb -

<%= @article.title %>

- -

<%= @article.body %>

+http://example.org/products?sale=true&sort=asc ``` -Now we can see the article when we visit ! +In this URL, each part has a name: -To finish up, let's add a convenient way to get to an article's page. We'll link -each article's title in `app/views/articles/index.html.erb` to its page: +- `https` is the **protocol** +- `example.org` is the **host** +- `/products` is the **path** +- `?sale=true&sort=asc` are the **query parameters** -```html+erb -

Articles

+### HTTP Methods and Their Purpose - -``` +HTTP requests use methods to tell a server what action it should perform for a given URL. Here are the most common methods: + +- A `GET` request tells the server to retrieve the data for a given URL (e.g., loading a page or fetching a record). +- A `POST` request will submit data to the URL for processing (usually creating a new record). +- A `PUT` or `PATCH` request submits data to a URL to update an existing record. +- A `DELETE` request to a URL tells the server to delete a record. -### Resourceful Routing +### Rails Routes -So far, we've covered the "R" (Read) of CRUD. We will eventually cover the "C" -(Create), "U" (Update), and "D" (Delete). As you might have guessed, we will do -so by adding new routes, controller actions, and views. Whenever we have such a -combination of routes, controller actions, and views that work together to -perform CRUD operations on an entity, we call that entity a *resource*. For -example, in our application, we would say an article is a resource. +A `route` in Rails refers to a line of code that pairs an HTTP Method and a URL path. The route also tells Rails which `controller` and `action` should respond to a request. -Rails provides a routes method named [`resources`]( -https://api.rubyonrails.org/classes/ActionDispatch/Routing/Mapper/Resources.html#method-i-resources) -that maps all of the conventional routes for a collection of resources, such as -articles. So before we proceed to the "C", "U", and "D" sections, let's replace -the two `get` routes in `config/routes.rb` with `resources`: +To define a route in Rails, let's go back to your code editor and add the following route to `config/routes.rb` ```ruby Rails.application.routes.draw do - root "articles#index" - - resources :articles + get "/products", to: "products#index" end ``` -We can inspect what routes are mapped by running the `bin/rails routes` command: +This route tells Rails to look for GET requests to the `/products` path. In this example, we specified `"products#index"` for where to route the request. -```bash -$ bin/rails routes - Prefix Verb URI Pattern Controller#Action - root GET / articles#index - articles GET /articles(.:format) articles#index - new_article GET /articles/new(.:format) articles#new - article GET /articles/:id(.:format) articles#show - POST /articles(.:format) articles#create -edit_article GET /articles/:id/edit(.:format) articles#edit - PATCH /articles/:id(.:format) articles#update - PUT /articles/:id(.:format) articles#update - DELETE /articles/:id(.:format) articles#destroy -``` - -The `resources` method also sets up URL and path helper methods that we can use -to keep our code from depending on a specific route configuration. The values -in the "Prefix" column above plus a suffix of `_url` or `_path` form the names -of these helpers. For example, the `article_path` helper returns -`"/articles/#{article.id}"` when given an article. We can use it to tidy up our -links in `app/views/articles/index.html.erb`: - -```html+erb -

Articles

- - -``` - -However, we will take this one step further by using the [`link_to`]( -https://api.rubyonrails.org/classes/ActionView/Helpers/UrlHelper.html#method-i-link_to) -helper. The `link_to` helper renders a link with its first argument as the -link's text and its second argument as the link's destination. If we pass a -model object as the second argument, `link_to` will call the appropriate path -helper to convert the object to a path. For example, if we pass an article, -`link_to` will call `article_path`. So `app/views/articles/index.html.erb` -becomes: - -```html+erb -

Articles

- -
    - <% @articles.each do |article| %> -
  • - <%= link_to article.title, article %> -
  • - <% end %> -
-``` +When Rails sees a request that matches, it will send the request to the `ProductsController` and the `index` action inside of that controller. This is how Rails will the request and return a response to the browser. -Nice! +You'll notice that we don't need to specify the protocol, domain, or query params in our routes. That's basically because the protocol and domain make sure the request reaches your server. From there, Rails picks up the request and knows which path to use for responding to the request based on what routes are defined. The query params are like options that Rails can use to apply to the request, so they are typically used in the controller for filtering the data. -TIP: To learn more about routing, see [Rails Routing from the Outside In]( -routing.html). +Let's look at another example. Add this line after the previous route: -### Creating a New Article +```ruby +post "/products", to: "products#create" +``` -Now we move on to the "C" (Create) of CRUD. Typically, in web applications, -creating a new resource is a multi-step process. First, the user requests a form -to fill out. Then, the user submits the form. If there are no errors, then the -resource is created and some kind of confirmation is displayed. Else, the form -is redisplayed with error messages, and the process is repeated. +Here, we've told Rails to take POST requests to "/products" and process them with the `ProductsController` using the `create` action. -In a Rails application, these steps are conventionally handled by a controller's -`new` and `create` actions. Let's add a typical implementation of these actions -to `app/controllers/articles_controller.rb`, below the `show` action: +Routes may also need to match URLs with certain patterns. So how does that work? ```ruby -class ArticlesController < ApplicationController - def index - @articles = Article.all - end +get "/products/:id", to: "products#show" +``` - def show - @article = Article.find(params[:id]) - end +This route has `:id` in it. This is called a `parameter` and it captures a portion of the URL to be used later for processing the request. - def new - @article = Article.new - end +If a user visits `/products/1`, the `:id` param is set to `1` and can be used in the controller action to look up and display the Product record with an ID of 1. `/products/2` would display Product with an ID of 2 and so on. - def create - @article = Article.new(title: "...", body: "...") +Route parameters don't have to be Integers, either. - if @article.save - redirect_to @article - else - render :new, status: :unprocessable_entity - end - end -end -``` +For example, you could have a blog with articles and match /blog/hello-world with the following route: -The `new` action instantiates a new article, but does not save it. This article -will be used in the view when building the form. By default, the `new` action -will render `app/views/articles/new.html.erb`, which we will create next. +```ruby +get "/blog/:title", to: "blog#show" +``` -The `create` action instantiates a new article with values for the title and -body, and attempts to save it. If the article is saved successfully, the action -redirects the browser to the article's page at `"http://localhost:3000/articles/#{@article.id}"`. -Else, the action redisplays the form by rendering `app/views/articles/new.html.erb` -with status code [422 Unprocessable Entity](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422). -The title and body here are dummy values. After we create the form, we will come -back and change these. +Rails will capture “hello-world” out of “/blog/hello-world” and this can be used to look up the blog post with the matching title. -NOTE: [`redirect_to`](https://api.rubyonrails.org/classes/ActionController/Redirecting.html#method-i-redirect_to) -will cause the browser to make a new request, -whereas [`render`](https://api.rubyonrails.org/classes/AbstractController/Rendering.html#method-i-render) -renders the specified view for the current request. -It is important to use `redirect_to` after mutating the database or application state. -Otherwise, if the user refreshes the page, the browser will make the same request, and the mutation will be repeated. -#### Using a Form Builder +```ruby +get "/blog/:slug", to: "blog#show" +``` -We will use a feature of Rails called a *form builder* to create our form. Using -a form builder, we can write a minimal amount of code to output a form that is -fully configured and follows Rails conventions. +#### CRUD Routes -Let's create `app/views/articles/new.html.erb` with the following contents: +There are 4 common actions you will generally need for a resource: Create, Read, Update, Delete (CRUD). This translates to 7 typical routes: -```html+erb -

New Article

+* Index - Shows all the records +* New - Renders a form for creating a new record +* Create - Processes the new form submission, handling errors and creating the record +* Show - Renders a specific record for viewing +* Edit - Renders a form for updating a specific record +* Update - Handles the edit form submission, handling errors and updating the record +* Destroy - Handles deleting a specific record -<%= form_with model: @article do |form| %> -
- <%= form.label :title %>
- <%= form.text_field :title %> -
+We can add routes for these CRUD actions with the following: -
- <%= form.label :body %>
- <%= form.textarea :body %> -
+```ruby +get "/products", to: "products#index" -
- <%= form.submit %> -
-<% end %> -``` +get "/products/new", to: "products#new" +post "/products", to: "products#create" -The [`form_with`](https://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-form_with) -helper method instantiates a form builder. In the `form_with` block we call -methods like [`label`](https://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html#method-i-label) -and [`text_field`](https://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html#method-i-text_field) -on the form builder to output the appropriate form elements. +get "/products/:id", to: "products#show" -The resulting output from our `form_with` call will look like: +get "/products/:id/edit", to: "products#edit" +patch "/products/:id", to: "products#update" +put "/products/:id", to: "products#update" -```html -
- +delete "/products/:id", to: "products#destroy" +``` -
-
- -
+#### Resource Routes -
-
- -
+Typing out these routes every time is redundant, so Rails provides a shortcut for defining them. To create all of the same CRUD routes, replace the above routes with this single line: -
- -
-
+```ruby +resources :products ``` -TIP: To learn more about form builders, see [Action View Form Helpers]( -form_helpers.html). +TIP: If you don’t want all these CRUD actions, you specify exactly what you need. Check out the [routing guide](routing.html) for details. -#### Using Strong Parameters +### Routes Command -Submitted form data is put into the `params` Hash, alongside captured route -parameters. Thus, the `create` action can access the submitted title via -`params[:article][:title]` and the submitted body via `params[:article][:body]`. -We could pass these values individually to `Article.new`, but that would be -verbose and possibly error-prone. And it would become worse as we add more -fields. +Rails provides a command that displays all the routes your application responds to. -Instead, we will pass a single Hash that contains the values. However, we must -still specify what values are allowed in that Hash. Otherwise, a malicious user -could potentially submit extra form fields and overwrite private data. In fact, -if we pass the unfiltered `params[:article]` Hash directly to `Article.new`, -Rails will raise a `ForbiddenAttributesError` to alert us about the problem. -So we will use a feature of Rails called *Strong Parameters* to filter `params`. -Think of it as [strong typing](https://en.wikipedia.org/wiki/Strong_and_weak_typing) -for `params`. +In your terminal, run the following command. -Let's add a private method to the bottom of `app/controllers/articles_controller.rb` -named `article_params` that filters `params`. And let's change `create` to use -it: +```bash +$ bin/rails routes +``` -```ruby -class ArticlesController < ApplicationController - def index - @articles = Article.all - end +You'll see this in the output which are the routes generated by `resources :products` - def show - @article = Article.find(params[:id]) - end +``` + Prefix Verb URI Pattern Controller#Action + products GET /products(.:format) products#index + POST /products(.:format) products#create + new_product GET /products/new(.:format) products#new + edit_product GET /products/:id/edit(.:format) products#edit + product GET /products/:id(.:format) products#show + PATCH /products/:id(.:format) products#update + PUT /products/:id(.:format) products#update + DELETE /products/:id(.:format) products#destroy +``` - def new - @article = Article.new - end +You'll also see routes from other built-in Rails features like health checks. - def create - @article = Article.new(article_params) +Controllers & Actions +--------------------- - if @article.save - redirect_to @article - else - render :new, status: :unprocessable_entity - end - end +Now that we've defined routes for Products, let's implement the controller and actions to handle requests to these URLs. - private - def article_params - params.expect(article: [:title, :body]) - end -end -``` +This command will generate a ProductsController with an index action. Since we've already set up routes, we can skip that part of the generator using a flag. -TIP: To learn more about Strong Parameters, see [Action Controller Overview § -Strong Parameters](action_controller_overview.html#strong-parameters). +```bash +$ bin/rails generate controller Products index --skip-routes + create app/controllers/products_controller.rb + invoke erb + create app/views/products + create app/views/products/index.html.erb + invoke test_unit + create test/controllers/products_controller_test.rb + invoke helper + create app/helpers/products_helper.rb + invoke test_unit +``` -#### Validations and Displaying Error Messages +This command generates a handful of files for our controller: -As we have seen, creating a resource is a multi-step process. Handling invalid -user input is another step of that process. Rails provides a feature called -*validations* to help us deal with invalid user input. Validations are rules -that are checked before a model object is saved. If any of the checks fail, the -save will be aborted, and appropriate error messages will be added to the -`errors` attribute of the model object. +* The controller itself +* A views folder for the controller we generated +* A view file for the action we specified when generating the controller +* A test file for this controller +* A helper file for extracting logic in our views -Let's add some validations to our model in `app/models/article.rb`: +Let's take a look at the ProductsController defined in `app/controllers/products_controller.rb`. It looks like this: ```ruby -class Article < ApplicationRecord - validates :title, presence: true - validates :body, presence: true, length: { minimum: 10 } +class ProductsController < ApplicationController + def index + end end ``` -The first validation declares that a `title` value must be present. Because -`title` is a string, this means that the `title` value must contain at least one -non-whitespace character. +NOTE: You may notice the file name `products_controller.rb` is an underscored version of the Class this file defines, `ProductsController`. This pattern helps Rails to automatically load code without having to use `require` like you may have seen in other languages. -The second validation declares that a `body` value must also be present. -Additionally, it declares that the `body` value must be at least 10 characters -long. +The `index` method here is an Action. Even though it's an empty method, Rails will default to rendering a template with the matching name. -NOTE: You may be wondering where the `title` and `body` attributes are defined. -Active Record automatically defines model attributes for every table column, so -you don't have to declare those attributes in your model file. +The `index` action will render `app/views/products/index.html.erb`. If we open up that file in our code editor, we'll see the HTML it renders. -With our validations in place, let's modify `app/views/articles/new.html.erb` to -display any error messages for `title` and `body`: +```erb +

Products#index

+

Find me in app/views/products/index.html.erb

+``` -```html+erb -

New Article

+### Making Requests -<%= form_with model: @article do |form| %> -
- <%= form.label :title %>
- <%= form.text_field :title %> - <% @article.errors.full_messages_for(:title).each do |message| %> -
<%= message %>
- <% end %> -
+Let's see this in our browser. First, run `bin/rails server` in your terminal to start the Rails server. Then open http://localhost:3000 and you will see the Rails welcome page. -
- <%= form.label :body %>
- <%= form.textarea :body %>
- <% @article.errors.full_messages_for(:body).each do |message| %> -
<%= message %>
- <% end %> -
+If we open http://localhost:3000/products in the browser, Rails will render the products index HTML. -
- <%= form.submit %> -
-<% end %> +Our browser requested `/products` and Rails matched this route to `products#index`. Rails sent the request to the `ProductsController` and called the `index` action. Since this action was empty, Rails rendered the matching template at `app/views/products/index.html.erb` and returned that to our browser. Pretty cool! + +If we open `config/routes.rb`, we can tell Rails the root route should render the Products index action by adding this line: + +```ruby +root "products#index" ``` -The [`full_messages_for`](https://api.rubyonrails.org/classes/ActiveModel/Errors.html#method-i-full_messages_for) -method returns an array of user-friendly error messages for a specified -attribute. If there are no errors for that attribute, the array will be empty. +Now when you visit http://localhost:3000, Rails will render Products#index. + +### Instance Variables -To understand how all of this works together, let's take another look at the -`new` and `create` controller actions: +Let's take this a step further and render some records from our database. + +In the `index` action, let's add a database query and assign it to an instance variable. Rails uses instance variables (variables that start with an @) to share data with the views. ```ruby - def new - @article = Article.new +class ProductsController < ApplicationController + def index + @products = Product.all end +end +``` - def create - @article = Article.new(article_params) +In `app/views/products/index.html.erb`, we can replace the HTML with this ERB: - if @article.save - redirect_to @article - else - render :new, status: :unprocessable_entity - end - end +```erb +<%= debug @products %> ``` -When we visit , the `GET /articles/new` -request is mapped to the `new` action. The `new` action does not attempt to save -`@article`. Therefore, validations are not checked, and there will be no error -messages. +ERB is short for [Embedded Ruby](https://docs.ruby-lang.org/en/master/ERB.html) and allows us to execute Ruby code to dynamically generate HTML with Rails. The `<%= %>` tag tells ERB to execute the Ruby code inside and output the return value. In our case, this takes `@products`, converts it to YAML, and outputs the YAML. -When we submit the form, the `POST /articles` request is mapped to the `create` -action. The `create` action *does* attempt to save `@article`. Therefore, -validations *are* checked. If any validation fails, `@article` will not be -saved, and `app/views/articles/new.html.erb` will be rendered with error -messages. +Now refresh http://localhost:3000/ in your browser and you'll see that the output has changed. What you're seeing is the records in your database being displayed in YAML format. -TIP: To learn more about validations, see [Active Record Validations]( -active_record_validations.html). To learn more about validation error messages, -see [Active Record Validations § Working with Validation Errors]( -active_record_validations.html#working-with-validation-errors). +The `debug` helper prints out variables in YAML format to help with debugging. For example, if you weren't paying attention and typed singular `@product` instead of plural `@products`, the debug helper could help you identify that the variable was not set correctly in the controller. -#### Finishing Up +TIP: Check out the [Action View Helpers guide](action_view_helpers.html) to see more helpers that are available. -We can now create an article by visiting . -To finish up, let's link to that page from the bottom of -`app/views/articles/index.html.erb`: +Let's update `app/views/products/index.html.erb` to render all of our product names. -```html+erb -

Articles

+```erb +

Products

-
    - <% @articles.each do |article| %> -
  • - <%= link_to article.title, article %> -
  • +
    + <% @products.each do |product| %> +
    + <%= product.name %> +
    <% end %> -
- -<%= link_to "New Article", new_article_path %> + ``` -### Updating an Article +Using ERB, this code loops through each product in the `@products` `ActiveRecord::Relation` object and renders a `
` tag containing the product name. + +We've used a new ERB tag this time as well. `<% %>` evaluates the Ruby code but does not output the return value. That ignores the output of `@products.each` which would output an array that we don't want in our HTML. + +### CRUD Actions -We've covered the "CR" of CRUD. Now let's move on to the "U" (Update). Updating -a resource is very similar to creating a resource. They are both multi-step -processes. First, the user requests a form to edit the data. Then, the user -submits the form. If there are no errors, then the resource is updated. Else, -the form is redisplayed with error messages, and the process is repeated. +We need to be able to access individual products. This is the R in CRUD to read a resource. -These steps are conventionally handled by a controller's `edit` and `update` -actions. Let's add a typical implementation of these actions to -`app/controllers/articles_controller.rb`, below the `create` action: +We've already defined the route for individual products with our `resources :products` route. This generates `/products/:id` as a route that points to `products#show`. + +Now we need to add that action to the `ProductsController` and define what happens when it is called. + +### Showing Individual Products + +Open the Products controller and add the `show` action like this: ```ruby -class ArticlesController < ApplicationController +class ProductsController < ApplicationController def index - @articles = Article.all + @products = Product.all end def show - @article = Article.find(params[:id]) + @product = Product.find(params[:id]) end +end +``` - def new - @article = Article.new - end +The `show` action here defines the *singular* `@product` because it's loading a single record from the database, in other words: Show this one product. We use plural `@products` in `index` because we're loading multiple products. - def create - @article = Article.new(article_params) +To query the database, we use `params` to access the request parameters. In this case, we're using the `:id` from our route `/products/:id`. When we visit `/products/1`, the params hash contains `{id: 1}` which results in our `show` action calling `Product.find(1)` to load Product with ID of `1` from the database. - if @article.save - redirect_to @article +We need a view for the show action next. Following the Rails naming conventions, the `ProductsController` expects views in `app/views` in a subfolder named `products`. + +The `show` action expects a file in `app/views/products/show.html.erb`. Let's create that file in our editor and add the following contents: + +```erb +

<%= @product.name %>

+ +<%= link_to "Back", products_path %> +``` + +It would be helpful for the index page to link to the show page for each product so we can click on them to navigate. We can update the `index.html.erb` view to link to this new page to use an anchor tag to the path for the `show` action. + +```erb#6,8 +

Products

+ +
+ <% @products.each do |product| %> + + <% end %> +
+``` + +Refresh this page in your browser and you'll see that this works, but we can do better. + +Rails provides helper methods for generating paths and URLs. When you run `rails routes`, you'll see the Prefix column. This prefix matches the helpers you can use for generating URLs with Ruby code. + +``` + Prefix Verb URI Pattern Controller#Action + products GET /products(.:format) products#index + product GET /products/:id(.:format) products#show +``` + +These route prefixes give us helpers like the following: + +* `products_path` generates `"/products`"` +* `products_url` generates `"http://localhost:3000/products`"` +* `product_path(1)` generates `"/products/1"` +* `product_url(1)` generates `"http://localhost:3000/products/1"` + +`_path` returns a relative path which the browser understands is for the current domain. +`_url` returns a full URL including the protocol, host, and port. + +URL helpers are useful for rendering emails that will be viewed outside of the browser. + +Combined with the `link_to` helper, we can generate anchor tags and use the URL helper to do this cleanly in Ruby. `link_to` accepts the display content for the link (`product.name`)and the path or URL to link to for the `href` attribute (`product`). + +Let's refactor this to use these helpers: + +```erb#6 +

Products

+ +
+ <% @products.each do |product| %> +
+ <%= link_to product.name, product %> +
+ <% end %> +
+``` + +### Creating Products + +So far we've had to create products in the Rails console, but let's make this work in the browser. + +We need to create two actions for create: + +1. The new product form to collect product information +2. The create action in the controller to save the product and check for errors + +Let's start with our controller actions. + +```ruby +class ProductsController < ApplicationController + def index + @products = Product.all + end + + def show + @product = Product.find(params[:id]) + end + + def new + @product = Product.new + end +end +``` + +The `new` action instantiates a new `Product` which we will use for displaying the form fields. + +We can update `app/views/products/index.html.erb` to link to the new action. + +```erb#3 +

Products

+ +<%= link_to "New product", new_product_path %> + +
+ <% @products.each do |product| %> +
+ <%= link_to product.name, product %> +
+ <% end %> +
+``` + +Let's create `app/views/products/new.html.erb` to render the form for this new `Product`. + +```erb +

New product

+ +<%= form_with model: @product do |form| %> +
+ <%= form.label :name %> + <%= form.text_field :name %> +
+ +
+ <%= form.submit %> +
+<% end %> +``` + +In this view, we are using the Rails `form_with` helper to generate an HTML form to create products. This helper uses a *form builder* to handle things like CSRF tokens, generating the URL based upon the `model:` provided, and even tailoring the submit button text to the model. + +If you open this page in your browser and View Source, the HTML for the form will look like this: + +```html +
+ + +
+ + +
+ +
+ +
+
+``` + +The form builder has included a CSRF token for security, configured the form for UTF-8 support, set the input field names and even added a disabled state for the submit button. + +Because we passed a new `Product` instance to the form builder, it automatically generated a form configured to send a `POST` request to `/products`, which is the default route for creating a new record. + +To handle this, we first need to implement the `create` action in our controller. + +```ruby#14,15,16,17,18,19,20,21,22,23,24,25,26,27 +class ProductsController < ApplicationController + def index + @products = Product.all + end + + def show + @product = Product.find(params[:id]) + end + + def new + @product = Product.new + end + + def create + @product = Product.new(product_params) + if @product.save + redirect_to @product + else + render :new, status: :unprocessable_entity + end + end + + private + + def product_params + params.expect(product: [ :name ]) + end +end +``` + +#### Strong Parameters + +The `create` action handles the data submitted by the form, but it needs to be filtered for security. That's where the `product_params` method comes into play. + +In `product_params`, we tell Rails to inspect the params and ensure there is a key named `:product` with an array of parameters as the value. The only permitted parameters for products is `:name` and Rails will ignore any other parameters. This protects our application from malicious users who might try to hack our application. + +#### Handling errors + +After assigning these params to the new `Product`, we can try to save it to the database. `@product.save` tells Active Record to run validations and save the record to the database. + +If `save` is successful, we want to redirect to the new product. When `redirect_to` is given an Active Record object, Rails generates a path for that record's show action. + +```ruby +redirect_to @product +``` + +Since `@product` is a `Product` instance, Rails pluralizes the model name and includes the object's ID in the path to produce `"/products/2"` for the redirect. + +When `save` is unsuccessful and the record wasn't valid, we want to re-render the form so the user can fix the invalid data. In the `else` clause, we tell Rails to `render :new`. Rails knows we're in the `Products` controller, so it should render `app/views/products/new.html.erb`. Since we've set the `@product` variable in `create`, we can render that template and the form will be populated with our `Product` data even though it wasn't able to be saved in the database. + +We also set the HTTP status to 422 Unprocessable Entity to tell the browser this POST request failed and to handle it accordingly. + +### Editing Products + +The process of editing records is very similar to creating records. Instead of `new` and `create` actions, we will have `edit` and `update`. + +Let's implement them in the controller with the following: + +```ruby#23-34 +class ProductsController < ApplicationController + def index + @products = Product.all + end + + def show + @product = Product.find(params[:id]) + end + + def new + @product = Product.new + end + + def create + @product = Product.new(product_params) + if @product.save + redirect_to @product + else + render :new, status: :unprocessable_entity + end + end + + def edit + @product = Product.find(params[:id]) + end + + def update + @product = Product.find(params[:id]) + if @product.update(product_params) + redirect_to @product + else + render :new, status: :unprocessable_entity + end + end + + private + + def product_params + params.expect(product: [ :name ]) + end +end +``` + +Next we can add an Edit link to `app/views/products/show.html.erb`: + +```erb#4 +

<%= @product.name %>

+ +<%= link_to "Back", products_path %> +<%= link_to "Edit", edit_product_path(@product) %> +``` + +#### Before Actions + +Since `edit` and `update` require an existing database record like `show` we can deduplicate this into a `before_action`. + +A `before_action` allows you to extract shared code between actions and run it *before* the action. In the above controller code, `@product = Product.find(params[:id])` is defined in three different methods. Extracting this query to a before action called `set_product` cleans up our code for each action. + +This is a good example of the DRY (Don't Repeat Yourself) philosophy in action. + +```ruby#2,8-9,24-25,27-33,37-39 +class ProductsController < ApplicationController + before_action :set_product, only: %i[ show edit update ] + + def index + @products = Product.all + end + + def show + end + + def new + @product = Product.new + end + + def create + @product = Product.new(product_params) + if @product.save + redirect_to @product else render :new, status: :unprocessable_entity end end def edit - @article = Article.find(params[:id]) end - def update - @article = Article.find(params[:id]) + def update + if @product.update(product_params) + redirect_to @product + else + render :new, status: :unprocessable_entity + end + end + + private + + def set_product + @product = Product.find(params[:id]) + end + + def product_params + params.expect(product: [ :name ]) + end +end +``` + +#### Extracting Partials + +We've already written a form for creating new products. Wouldn't it be nice if we could reuse that for edit and update? We can, using a feature called "partials" that allows you to reuse a view in multiple places. + +We can move the form into a file called `app/views/products/_form.html.erb`. The filename starts with an underscore to denote this is a partial. + +We also want to replace any instance variables with a local variable, which we can define when we render the partial. We'll replace `@product` with `product`. + +```erb#1 +<%= form_with model: product do |form| %> +
+ <%= form.label :name %> + <%= form.text_field :name %> +
+ +
+ <%= form.submit %> +
+<% end %> +``` + +To use this partial in our new view, we can replace the form with a render call: + +```erb#3 +

New product

+ +<%= render "form", product: @product %> +<%= link_to "Cancel", products_path %> +``` + +The edit view becomes almost the exact same thing thanks to the form partial. Let's create `app/views/products/edit.html.erb` with the following: + +```erb#3 +

Edit product

+ +<%= render "form", product: @product %> +<%= link_to "Cancel", @product %> +``` + +To learn more about view partials, check out the [Action View Guide](action_view_overview.html). + +### Deleting Products + +The last feature we need to implement is deleting products. We will add a `destroy` action to our `ProductsController` to handle `DELETE /products/:id` requests. + +Adding `destroy` to `before_action :set_product` let's us set the `@product` instance variable in the same way we do for the other actions. + +```ruby#2,35-38 +class ProductsController < ApplicationController + before_action :set_product, only: %i[ show edit update destroy ] + + def index + @products = Product.all + end + + def show + end + + def new + @product = Product.new + end + + def create + @product = Product.new(product_params) + if @product.save + redirect_to @product + else + render :new, status: :unprocessable_entity + end + end + + def edit + end + + def update + if @product.update(product_params) + redirect_to @product + else + render :new, status: :unprocessable_entity + end + end + + def destroy + @product.destroy + redirect_to products_path + end + + private + + def set_product + @product = Product.find(params[:id]) + end + + def product_params + params.expect(product: [ :name ]) + end +end +``` + +To make this work, we need to add a Delete button to `app/views/products/show.html.erb`: + +```erb +

<%= @product.name %>

+ +<%= link_to "Back", products_path %> +<%= link_to "Edit", edit_product_path(@product) %> +<%= button_to "Delete", @product, method: :delete, data: { turbo_confirm: "Are you sure?" } %> +``` + +`button_to` generates a form with a single button in it with the "Delete" text. When this button is clicked, it submits the form which makes a `DELETE` request to `/products/:id` which triggers the `destroy` action in our controller. + +The `turbo_confirm` data attribute tells the Turbo JavaScript library to ask the user to confirm before submitting the form. We'll dig more into that shortly. + +Adding Authentication +--------------------- + +Anyone can edit or delete products which isn't safe. Let's add some security by requiring a user to be authenticated to manage products. + +Rails comes with an authentication generator that we can use. It creates User and Session models and the controllers and views necessary to login to our application. + +Head back to your terminal and run the following command: + +```bash +$ bin/rails generate authentication +``` + +Then migrate the database to add the User and Session tables. + +```bash +$ bin/rails db:migrate +``` + +Open the Rails console to create a User. + +```bash +$ bin/rails console +``` + +Use `User.create!` method to create a User in the Rails console. Feel free to use your own email and password instead of the example. + +```irb +store(dev)> User.create! email_address: "you@example.org", password: "s3cr3t", password_confirmation: "s3cr3t" +``` + +Restart your Rails server so it picks up the `bcrypt` gem added by the generator. BCrypt is used for securely hashing passwords for authentication. + +```bash +$ bin/rails server +``` + +When you visit any page, Rails will prompt for a username and password. Enter the email and password you used when creating the User record. + +Try it out by visiting http://localhost:3000/products/new + +If you enter the correct username and password, it will allow you through. Your browser will also store these credentials for future requests so you don't have to type it in every page view. + + +### Adding Log Out + +To log out of the application, we can add a button to the top of `app/views/layouts/application.html.erb`. This layout is where you put HTML that you want to include in every page like a header or footer. + +Add a small `