From 536a7e5bddce9bb583b32f598d8d0c7b7b3f0ed9 Mon Sep 17 00:00:00 2001 From: Vipul A M Date: Sun, 24 May 2020 03:01:12 +0530 Subject: [PATCH] Changes over AR Querying guide [ci skip] - User bookstore models for all examples - Add diagram changes - Add Output changes - Expansion of Enum documentation - Lots of Grammar fixes Co-authored-by: Ashley Engelund (weedySeaDragon @ github) --- guides/CHANGELOG.md | 3 + .../bookstore_models.png | Bin 0 -> 99065 bytes guides/source/active_record_querying.md | 1141 +++++++++-------- 3 files changed, 617 insertions(+), 527 deletions(-) create mode 100644 guides/assets/images/active_record_querying/bookstore_models.png diff --git a/guides/CHANGELOG.md b/guides/CHANGELOG.md index 37823047bc..d5e509aef5 100644 --- a/guides/CHANGELOG.md +++ b/guides/CHANGELOG.md @@ -1,3 +1,6 @@ +* Use Bookstore as a unified use-case for all examples in Active Record Query Interface Guide. + + *Ashley Engelund*, *Vipul A M* Please check [6-0-stable](https://github.com/rails/rails/blob/6-0-stable/guides/CHANGELOG.md) for previous changes. diff --git a/guides/assets/images/active_record_querying/bookstore_models.png b/guides/assets/images/active_record_querying/bookstore_models.png new file mode 100644 index 0000000000000000000000000000000000000000..ed5cf45135d8a965ef4af0b76a9de5e4978e928e GIT binary patch literal 99065 zcmdpe`9IYA`}as`5hW**tx{38kbRF*3E3NC$-a(#$zG|DB4LE0NHmPy*td`}St8rm z30Y=r*|+<8pU>yM??2%C(|tRS^Eju;yyvxCuj_hV&*$?s;deFF7>=Aif{&C|1|E<=S%a7jzctYO1I`pdXP4F((Q>sKr8vT|$HiwE7=eMl3|H2dSp z#rtgcu328TW4SSOXLKY(_9LU@qFSWi@+VpzTFsy$Z6>!;haXaIN}XAg^R++tb)LsP zzI5`4=|M}SMy1~*%KFIGK_9~E(A2?DvoCc$%Zm%6qi=UC3B_xs{xxex{@9Gk$w`~z zOvsOR)LL-4ERE{FKUUmyuK)Qz#>X@q|Neb+$bFRBzpwp1bE@{=mzbmv{Xh0=?Os0n zIKsbMUGwrP-mY*NuKv@Lo3dR&B(l7G`EqGqIfTy3=e2+A*RMBzb$2({Y^}_ORtIi* z`xIoxm{fRc1a))qs8Sj?LpZ`${c1ZqnPoVI%fd1}q*Sq~3~!yLo8NyfFF!3@_4oC* z|5!u#O%08>;9PVeomwPEba0Ba$0zpNSx;Ag|Ln51j2dr>vfNmjSelQG{YP$ZdwDSN zBIWuA!9kOt$S>U@9i}}wCMsTD;&?p%_^DIhB79{3#`+Z&{%zO{`F5JJQX9%0^yhdJ z81Ylshv?$=oJItfuCP zFD@>glJ$0qkLRH|aNxjtJ@@WIc1h=Z?`&Ym_o@S=L!{}+IxdW-uAp=?G_Sk5UU_zm zyA`*O!TQP%+&|~e%{W&rv6T)C82?BVr)TEXZ0IYrmBp3?Zml*pgfman)YjHgTk+SY z+EPl^ck(Tp$^!;}{`~o1Iz2wmrsZ_uaAjqs-o(_zMAzW;E}Mjegs{lS8=jt?8C1;Q zG&0%fM}pWvm+FmAEh7x5NDk@8wkd%up~twNeYcvSew59Ik)9C~V=lC5`SOBItXx)F zzBR|BVxdD_cr-oUw4g#iCM+y7>+ei|n56SSZ8o~_ReXHUC4%zK=E`g=^}Y!uHdwS zG@O}t4CZ6A=kDRb#Lev@fqX7jC>P=lwpYyGCOfOEyPLeT9ajY(rN2Yv)qKmRI9%#h z?=Wj@TgoaJfc=?_CF25TzrJLb3fg`U|1qs_X}E7I9+z$_C~GnP^`%W+|LY6tu44^{ zIi%h9v$3(c``cCI=a^QNla}d=JTof1DTyk?bu_tTd1*;+Wk0HNxm6Z>IU;h7cFjVO z^3BiD(NWvLpnGycQbJ-VX6GaSS`#(10@k#9boSX=kseow=wRi<70 zGJ0k|^vo{qtPS%>N*V^4X=|HHP>LOU?q5Bv-+37J`sU4>R~*r0-V~!yI=d?gccg-M z<(@x(p64;8+4Xb=f@E>7u+8Gf8xc;2pBvlDZTi8y=g*IHq^mFOJFloXoFh96t5g4o z_TY~WHcrl$@n%72uf*eM+PXLJY4yZbH$3aJ1C31ZZYA1CHZii$w(XpvqM821 zF}C#2x*0}T4T*$1-Z%1XI1mtLdlDp)ofZ#n0_g2u63Jgm{~*1YBJ_6DAk52LrTvdY|b#@;_m zxr(D>&x^HQwu@8gj1JQOcv@+B=w;Rp(EpFOcQ*H`#P+!41eDqer2hG1S+jEQ*FKn2e20;g_R4Dc58Wt-^bRKjtU{O++$Gv)W-y`oCB(Tc8-7On0 z`t|~Dwxe`Uo<1#><}NNSwjq=>53yd1Ih+!>%BHY2C#FwsbldpbsWm>Gk%H^j#thRp z&lhcV!gl7ljo&ETyz3+uQg=V!Nr1xNU*e+X<|fkmRyyPW3yp>|gt=_~pT2en}B)?8+EDPFNrPx@Dxs9m`m{yjWR0TL;n=+F9jzDU- zm{M~7T!f!>%W*EQobuz9p3|>qd}6*oDJ_-C8r*@S7M+yTZ*L;}bbC7>i`FLWAw-v| z7Y*59dU|?k`5_JM6}$E`cM?ReCtG=VcwiXL6tVurHQRiDec@fViAF`0!uEnn#G{82 zQu@R&IG$rKC5Q4JWNgC`boYmP>1~%({kKCM|4`YVwS;FH;``?9+XW96d6TutWgZ!g zzK~H~!8UlMCpHF%xNi&x?^VtCQ}S&| zkSX+nwcbkooNS?crx`lDJt>K&xNZKmmk!gkmG)m181kDHjIhQS7(6Q|5DnOvh`*_> zUg(?ewK|8c+?Y&Snpfiv{0CV=2Y*aOosOQ~$4yx|xysI^gQ#P23lC@JX=v%7Vs$#n zV7zD-EahzkYm4-8wNYEzn1dN233qI^CUAlny)9n@0~S#+v6jh`P1erN&Y}A(u~*W% zaOKP7Vn>7cF7#HKf&Wa7p{einlkLAJCld!p)NkF=7UaKh;rhwSK5~ZhKZU9|KPkK8MP%ex*f70eK@2gwv%?AwHe{8J64P9@+qaMC=;-M2zU$1; zvV>rPWJAnq`}J9u2E}~w&RvZRh9|K^mtkjb(XQE*Lx&G*2bTD)I7o-7-M{}H&6*9% z(JO!N`@(3wesDyDt6 zb>d;)bz4k%@<~?Ktlh|n2uH7EDAjK!^2I8~6Yg9rjL81;$L{0DkC~C-$b<&Vy(HqT zKV!y_0j_8|JLfbC=`}l#z)`v)Ya^4IpU;Fwqw({fG~YRR|GZ~lV6%9wUuQ5ODJh~p z^jH$XwmTc`P00Y@Q3r8Lxm_dWT~wDOdOU|f&^HN)7}u%bgxJ@o;!DA*Z{`W$dx%=HF=>lUpf%GnYPo`gBfQytqp!N#OHZc(nQC`TVg0?ya(GVIV~OK)hp>ImWmno1_vA!HMfJ9@5DP8i z#O&-l&PhmOfx_@iGb2XQXg|dRPp*3;aKLcYi0G6sGyGQCV*x3NGpbdlF1U*^->q0G z8=IQOA!ZAxdfnzI_xPWvsIj{jQu9v@>g#g)0u&a7UNW;6jdTOF)v7avOVs-dSyxo$r+^ ziH(ahhXVsE-w1d$OvLgl>d(NA_B0eWyd@^LCu^o14)(WUNE#pTih$5E$V3FTm3z&$ z_x6SSz7?rBjwU*> zB?oQV>$6^XKcV_@uDdm1c%m#i_aMvuVYGk|dmc_*q!)|L$3>L11nxfZu+!J;%d$Oup{W9JP0Y1mJkNF{3$(pCBe^%58<2Sx_U zJoE=&oH%hpkC14XYZ_N$?{ZVXtlC(7K8%THVRJS&>r=jj&gVRbuFEe@iR^>;k0Et1 z_KHFUy_2F~GU?V3#&oaL^(=@yiFSMJ z-6WskK-LgChA;1xL#(PknmqF!?qOv7I~E~KlvPY6y;;xT{3)iOFl1qvH#6f<$j%Kg z3b$3GnNbgjxWy95VMuRr*aEZ^@W zGtu%0$q~g;Irs9fb=N=$VUN60+}rjjoQjXN?ksUJE4&&P7uP3O@@K^_`(FM&zbY;m z+iTLy+>UlT)dNm8Ui6fR#goe;KQ*C`YRh^&0?kQb(?5XB?7U6IP90s6dkcAy_8{Z8 zK$nG)I=p?&_L6#%?;9z%b2e?s3{Y?L@~(7=IVq8(MFa&khT4C1>k|%yrE2asS~XPP zN~r3gCu?iSI?A5-xl?~u)&K$MV;4u3{%pc>uC;;?C{TWqcdn3}<2E4i)|M}}D zPj~dxndMhWNm+gumHq49Q&{kx0FVkJG^$L6YYMNvdU}7p`Xo2^umfui@uLcRrQFgt z9()ayKSvQ091ONiNPe}~lF-Wg@%EP~DJgwE@{|KpJ6mh>?b~EZ5BX_qx-#|& ztbat)gPNe7<0np>3;7Hy)-`gVIR`!VxH`+q#%Ao#&$KtPJ~<9b$FNzS(sLdusgo)S zeyCPCOn>RtQ7AG|5oA!h5U@TPibq|AayfQgyj_R;vWsU}fM$Z|0Uou;Cs9#a6|=+5 z&9@6h^Q<5X;4RmeCP)S)6S=`VjuFaMw$!NG0!7ey!XswBfi3E8uB4iWDTQ@Y* z-Ub68W0|G1-eqTJ*M=UWIP^(+Pbi=L!P|$A9@UFm)76qszpP8yXz8w|d(-pb;f92u zkPvZp-D%ca4uU?EF6>jDvkhM9S{p z48mk17mgS~ppnaN;aT6@G}_USJw@7Bo)WQWIAjb2gTAd@#d2{Rk^L$P&bykps2Li~ zHWaX|k&P+QB%1PL0%%B*KB#5X9peWMv{H^V@4d^2&P2PlQT2EsG-s{N%&*TjX{HXd zX6ih1WR;3Nw7-J3f4$27lGRw+_KmHy*TlD&tO@~!%AatsoCZpNW>@&!ycyQwq2Wk7 z-m8wWY4$^1IkrGXFdNGnzq(|;QD-vcrs~IuS2S-;b0sTY{ba0MSm`mt9DM@l{fFii zVsE)RnMLLuBNS>RaSK$Jqt^5NV~U-Xy59h>Va}1S3pw;E+s(_1;#%E*jlGsr*88Ym z)zDnu{*bzMYx}$+7lKKJfmfZyyh*pevcGvYWuL%yr{Ye`AgRdPEhim&!D-g7sK}>q zexS^}uI?X_k(qgj=f;aG^ofmycPZA`8bEx&aAf11?`Ubo z)4cWe)TT7LIRceJjx%*k(>F?zxNxD-Likb8&Zc==vi!|kxAMtfgpyzC(B)9fvF#VJ zs9c8Un9jbfG~yh&dFZF9%TVQEsGo~pU#AOi=w4BqA_Fif{;PA*yyqx~8@o+9h#k|O_bMNVAp5GP zr#G_Prq~3HU<>s;Ot4U6e$jrORCfL0`Fp1mcTK&+ncFUgDQzR?QQn`3Ui+Sr5@fVe z;?7m;N1q@M`pX4{lelfZw^~vo z&VKT;u&~xj$!gt-DlnOr1v#9Tup1;pu$XgVn5^-~LbDLc4 z_Q=ehpPqIlFIOW=zgxYYs0LQ6Prkcj*%{Yt>L2Yuk>53QsjB98uMqGyGk{0LAfB&x>n(B-Pu# z*_V8-YN}klsB4RbW-Rz`B8u_Kyqc+j!6%0*dnc!>6<1al7o$@!{%v>s!_WTd$wr4* zlW+-y3}8`hcr5z*mu&6r)&MB~yS}UM6q?Ua7Mr*Y1$kj#P96=5Cq2(ehG=`5l$6xy ziT01jIGXxRU+5YpM-&1p)91uRDP$mz^gwIXk|-{^z%=jcKu@Xm0?b@E;d!2u>|0n+ zpvU?F2(8x>$w^84A-ry4O4HEr;cK8mEXLPtH`*9cKA1$U(mjMWZ2d)CRRdqfZQ!F= zN(?P6(;C+%?J=k#R=vGN?!B{X>TsS{;!ZcO@Y@SJH7*xHXZm%tK2*QS8&kpdyzb8a z3IB1Ktw9%Dv##tB!3E2m%)nBrz6f&CZJeFU&|s+> zEeua5T^+sW;Ntpswx`WWwtuENi<3>aA_~q$gGFDj%vx_2&sLfsc@6D=!4-MMuWmR{ z_zi%*jmF_VktbcMR&Xbd9oO(?eeFQuOqK|&D}ASW$hfIw_>Z>~}zpsUo6XoVhdafu|%15LHXEXMZ5 z?%!J4W0^PZ$a+j3H6R#W5Pq((`G=<~?+bvd(a}+FLCltpv$~+(ZTJIzdunQmL{%8O zc4J$}lw1w~bD%n~f6l_E3$Ks&vadM)>Ep-y9UpH!*j{X+DCDnD`VG6@srf2r28urx z)oPyl1(b~2ni;6OoPg@IB+*a<|A@WSOxBA15 z1N%N!QnN$oyHAghN5J&zO=MT*VW6qpU5c-{Ki!d@iN}ljKXZXPDO7! z0+`@zJju%9-(3h*ESg21PJi-bf8qFDPqUM((?G8k^op`VQ`0SfOLlVx`UhlL#-W}y z`OjDNIQTKpg*YS$npJZE6Z=Z=K=5W~ir!WxQ9OeX8KB&SVE-J35$p*ycc{!O)V>OOFzrN9J+9P!8S05i~Ym9EKQM|V*bvCxjp@Z zac)3+;8*x7+Sg`#w7iZQmwO(D{aaXEtlL}L+X;ruo8eGlZ#F~-WO*>v`fg!mCC)^% z@#VpZO*1$(4NXm!KvuAj5Y~z^!`P48(XP~O;nGsvDVph3aQ}Ts$fW#9x_bN<5Gih? z{d5@A$6&H#rhwE}icG~1Od?%Y>h*|M(a|&`O(Ul09O&vADg7laGXix(k6xBPE>LJ| zYm?;fCwjV7_pBUYT2ZLF{E*gYNG*-C-Kwv^dg;R#79sCB<77g7WevD<$bHmoHFEwgGFNKY#ukv(~k1`<8%|W}U`$ z09o3SynISAs1iSLy$|TXe2rZ0E2oaK2=f)si|XR$5&p2t!vPvRmQL*Sg__mp5fQpC zt$fD6G60OxR_Om3?RhV84H^)Zg9GSN*AFw-l#7)3gijC%tafGg=8jTWjA`(Wm%frG zyrru#X%(GFJbl&fIE-N7E4#~^1NOvnNnvzB!`Mw9^3>()iPyQHja+D8*1E^}w@GJ2 zXw{Jb2-4H;mwN4zU9nQr(2k7@+hw%hbbe{@h9npP^WCfnttP zaWi!%zsf5f&Of_L<%dlZbPe=%*YqXdDFmK|l7*-bEs-tIJ7sn<3!Czyz#JfpMco zNRHpTm*Q}gf$)`>?dmq=PrC|~rvSP@RX^U_y6T#0l~(S|wM%wd+dyOEOcX%iabEdd z1w0D$Wb8n(+GH?>G8P%%gvR{mq{lHBn-UbmL7Hb$ANZzRC}l^WP9poyf4`ww%5B7G} zpzj}^aXSUwKae2plUF8?Kr4La_-HB&zbYQXn3`3TTSf*9FapPIzlv>zEhg-7rS-=3Tj7{7YTu?}!Kp9OVvLH_!5l7rpzv zxml+x7^;xX3JGw8KFPSsuXxTP@%3wqe1#K)P(Z@jtrW&6h0V;aa&$sB@eR<%@>zyq znQLwiz`#lKC&kAbX2m5gO$^{Bp&~a-r36O;`*Ti8>J)H(hVS&C4C=St ze&FXf25d`S0-|ovoApK>jh3xF0sD9u%N5JYmKdj z+B^yaEPb$eZeVb5u`^}&pGS`#5lrvdr|kXdFA)ZM#ujIaA$8!=v$C>G20sI3htJA_ z2fkRIYO`El7%ia3+QxlY-8iPcTqMqU+hUK8pFd`~yu94v_qS+*_s^fLv$M1G+}zwq z*J?mA@(<{%c-dGzIy;+{sD@GCq&zmpU?N~8ZhDoRw(Gy?doBu?j;9qBvKQ~XIaBh~ zn>+97mrSBlu4&bCq$bKaQ%rKYCtH&A1qC2^QZB1tIlV!mDKyY9G&J0hIi#hfRY4m8 z2-3LB{eZr{K9E!UlLJ@L4}5*ife7FvH(@CmtG~EcIog2LTL%(l5lX@MOjp)}&FKsZ zJ{Uov$+N|=-LNGIw6_4S16hsI^|_9Y}( zTi20V8l=H9j$e9vu9cS%hj!djckDUY{3sb|@i9%%JpI;ai{?@L0(sUVnKJmvq?j{2 z)5jwW6&Yp+rNpLf=~9aF90P&|>E1{~N1NNs6V1~e`HS>nH$0Pf+N-UWNqgREdZPk_ zkB!Y|(99%MIW{we;ogmnSrpo0$H&LnDnY1^K0+&X&ixRuR?qt)=HpBXjr?~rGmHvt zUUofFicNcY0)B_4%JVq$xtTyu8P1N54i1UCj`Si|_F;y@K*@}N4~nj5qSX#l-e+uL z!nsPk!VulNW;mt(vMl8+_EiKL_iCV*dvY0+ZCwpASQFx}mG9 z>%*H)g6RE_ojq4=Qgfjv*KFRazlIlA$TL;(`SbVD)hPdBvxt>mcYynh*!ZCbFNRz= zCgl2m1{RC;c{r`;!E?TXa5mp31FJ58(+>=krwk9zg#9vsY_GVzyEMU?>E?9^9q z05WUICMGa2FbUW9>({S|gO7PigLeGlIXG5UR(i|59Ft1aF~DoCgkjge^QMf%XeqdT zTk+{ytpVmD-evFg>(~B%qaB0&Wfeu@!!XtoU-#VHTs0_@lWi$c9#gGeKC9%9Ra;(; z))rWKqSIh`vC5XEEoWNfsj#(weCCHJ?b36mZ*65&)YiIMt=jUtxHL30OsY9S<9iO$ z-*BN-6zM_hB6&36-04N{EY_!#gCj zsC(;8iYSyiPCyyNT_4-|WIMtgj=tgDRmo8L2LIK zT;8E0N6Kw|6BPnwbaZtmh7EI!X;2`_(*YUhw7i6LS`|7h4?*zeA~ctEu0sKKLPa#n!cA57ykec_ww?} z%*nw`6Q?Yy;a3rSv3#ys`QwL=9~;I*u{$;dzv|+@ zX6D~xZqKbtWrz8zX=_JkhhmvlqF3Jv1TpuQz#5GOna^t&}bcPxwGLSgE7PQ!ORm~IL8YU8+n8&9g(W^@$PN~ zD=imJ^_4Wys%F2oj>_SnErOgxEGbWqx*DtHNLX!lFDhbm+$!n%;<5f^X>&jPg7$FKHN&nnp(MBbF4{0dQfIWOE=@Y$hugDL#KXGmb$(2+ebW z)%xfgudc4|^U!*Uc+cJ2Sc8Xa!?9^-P}M&Ty1=C#SdplZgU80kqZFS( z6`GluInKfH0^+B>rA4E)5abhI_v1zO21AFngIa(5h)toOhaq_L!RR3UGAW52YE7Rv zb{qx?O4hxVckm35$OZQle5h}ox`#_#QUK$2kSZ;POL&@jUR4%E8S@mp=oV> zp__o=B{dEXaw774KtKQr1$1*0Aj98&jf*b!31mu!fbQZTvcRyUKvra~R)9*(9LmV~ zdSYUJJ_-y4`|P9{cXoF4mr;TQeE_eJ3a>jj;QJ2Tv9L(}5o!QT=!+X^cvE&Uo1+&s z68FDPKQIBJ^nlZE8BG`{|CdlG4Ms-UFp>?x!ad@I}FUK^%%f#-G$H z-omqfq$rvd2ir%zobAcAL>k|n^9l-FepRM9QvjNPWqM`pK%jmm5aPBn3+~*$eOqrU zr`VcrmR-q&wOD+ zj*WqVLG&e+O1*jKPVBN>NjQX>>4VV&FF0kp$MWxg&SA37xavbS?(*)UMm5V^M=M5% zf)A_?+)1Mo6Bm}x5j9;-uZ1UZ>5>J0~VCCrli%I%5OHUFOIwY z{iE&cbZM;z%z59h(nBvCV}D}fIIJ*YdWu;2ytK`eGhE!XmMHE>4s03y#(_Ve>9hcL zH1x|#dV2Z^Wd)DPJ6&?a0m}>kVMEJ2rkba(ckaSGqG6hlW7gN#&w#@Qjvv%_{$>(M zHkJyz4CW?QE%}vy01v%-^X3p74iF#lREhm`bo)>c@S%`zAe$tLCvYnUaX=u6K#9u- zj{#EOKoHUuAB+doQdIQM?(QzQHB^j@PNPN&+Z1%u`N6?fS66q4k?{sB>UM@z+a{F4 z1)$4#U?B6A%Z!sh5<qrhEi z7>La%j~arh5UELc|Zzq1oT)ZVZHbS z1Y$dna!B2EcEt4W0Y!~K=zQQqvrVf`L-p^442Qpi6f|F*Z$ExCc(lof!nlk0_Yk96 z1-R5sLu15u`LYh@d^05_G4XOImj40s$r>rTcmed|@^Tp@xy$;&lw!7AVf~Upt^XG> zwXKC=W1^!!7)>G1gj0dc9DoAei*JglDaWoD*kCnl>+}KQSn4!rF$SHlUh@k7>$sy1woKUQIQ+iLKX;6WLvVPs$qUvzAo53`TrV*V5h!$^==vCGKCxR zUBc}|7T4C0-WT36GCpqE?NtHM2|KVv_boxp7KuMgNPn>DjqvGE3atv0j7&|Fv$uvI zw#st(W)<>tkn=MP*0hk04jou@w0<|C3-RC7e~acp!W60}QdNB$B$&J+Gj)OcgOu;G zc(470;CJvVBBuZX5Gn8AF8%zelNtrv=B_C1L*+m`g0NQT(2BA$b0Eu*s{J@5WU-Oe z?3*g{hTdQ^GiJ4gt>zsDdoHcS4%L+)I|2Jf4in$IF4=)oy9A+6@mfH5^iDl4+f zh;9zq0fP-zzkZ2hrwuE%};L5Qd?^uj1RX|~nr&VxfkLwM`*va;`-@D5Io z$sbU;P}@5@G~4&kfF!=Z&lCJv{pXsP*Nh(2je4kV&>b&5?1pZ=o+MoJ0-Ti=;E2Fi zu$u)e9sq8bcODhqoddQPBh9p)S!5Ssk-zlyvB8=xtgRUq-zJliSHhijEt?{*Lw>*F zI-(@E@jHe>{t8CiYc@6)zeaLKAaEVT0Mz}$eTAWgg<`NcNIHb{`W{fpDY*w%P;cau zmH_S5!=nQ?|0>(r@j*_p@_7bX_{p z1i~pxQg~)|R@=}p6u~+`X}NlP<2oNirdG_8tK^IK?c1j}#bax0dlU>UM1@Nw z^SMDQ{OBBnDks!JN2hx8<{=2fmPy^`FJ8oYnZd@5Oit24yPT>;L6ha5ZEGCXYNtOEQ{S9yc?i796ID0KBImA`qM)9!PyAm==o zpep-z`BuZBUg0~WAqpXCn%ULTZoGVL!gw8BxjB=CcMb!5r~R(8F`sXSFE}{3ur^aX z=Ay>ysXp`iXEYDHrc|%@$ZD%0h>B}}vm-4<;V(k^K!C&un!7>z~kJI?FJl>ON{ii(n33VW+H*Fa?C$Ulnp5q*CljOnDmKUcXTSgraV2{TGv za9g-Udl9>fw8v<)eSX0|Cex}uA;Q(`sVa}6s%9&|A&WjRK0Vz>_2QCyaCgXl`VPQ; zLoc!XuSPxh+?X}%Hzy&Z|>C)=dMeRMLGxPD_BeFrz2+^ zVrS|6=YLL}vRl4J>Xx}4-_m+s^DAOF8b-2D%vadVmPl<8tiAnb{FX6heQS$k{e17b zx-1HPkZ*RMTTJr_CZ%9tCf+CpD?qn^_=9-(_`YQ;&;(V6>3V->iB&EwHP?q;4Av4; zbsugp09L|nJ%P^gOMShFQ}2}uVW%R6-F3@CxrijElP6CWMCdt69%yLv4hX31b3rOH z+7PWCJD3^Ue-lG&PI`C^q`E<0XJ{jD+_;fNSR32NweA*N=|1rsU2>D@Ara@J9_!20 zR1cZYh~n4BLe6=QlLIe}JNJpUIwRRNfZc-*@>s{MBOg9|SSrlyDFM%QcOaqIqa6%b zKbFqCmGL?P0308fL6~ryU(Bf-(k}O3%W*>Mo!F{}>@_+y#Y@sVCGCD*VSR)aSm2Y2 zTYYVJ4GnpPvx_(MCbI;AbN+3s-W1aTGM2f4T9~EY-^3Hs9aI%0?j5QT06acmu)~+L zHK$>TAq{p&TLVtKSK`ds(b;Kj*b2@7^5nQo^DrI5=_kl<*9g92E?N$gnS>Sv%k{>p zIrftK6%k2ENoZ_1;0!r0mnor%=UQ+xoxnsQUQ_&X8p;W>HT{SzVGC^sK5F%=y$XlVy**#kx@^o@?R4Ib;Qc&2qX#cLS&ZTmjfS# zcU&~lVtP)gkU+|T=7{rPYVHPDMz7ema+Crzfc6V(TGGqStCcJ;KSNd@IP1vIbRT+V zLxY)jBDa&Isx>agG(I2d5CPp?Z<+YDqvb=+pLoTa4C z!0FegFLCBfZFUgTtfPa&Oo#1d)dd{%3Xr(2ii@Kw!-^U@cKJFS_~)@dhuB?l5JM&k zduvbu`@IxSOxcS>;g)W^(7AQ%Fv4rhW+Q$Sq?}DT5|WuE@dEk!l40Gk#MX>-xgo80 zGEWJZ65?iM3C>Q0wLv-?YAvMGkf^9Ar^Vxv?+|PZgKO?)x32ACBfHm8;BJ@q%d<26 z-7|-6kszb@s(s2dfC9D1W_amQNfO#u#?LY!k+5x4&_>JCt%U-w;0W{Zdg>2(wK2~i> zKD10a21icVjPD zSZTnCMJ5PTEC|Hf^2~h(0F6ZYgBxa*Rp$@;heaHQh)ZaL@cEu|4H&hhF(1GQ?h9w4 zg{^(TPopkWlAWDxGJm=lr272`-o6q7%W5#^m&mHnHxi@5NtNg4;xpBt$>K? zd-;+$bm+&oZx5j%8XFt4+Bu6er^sDSZYl7jYIn=uhEM4wV%eeGGqJISBYOmn8hlPE zZW5sD8HfrDVTyd)iJ9{4Axe*cCfY@9 zK)lSzE^!n=q#*4ArWzm!av-iQM*>Szrk(Py6DCKB2W{iFa(mk_S={~g$?k?ak$*q; z%pwCo0M^3|vA3M*b3&J9PB~84sUtWQ_6JFIWmV zek+DG+G2kevAR=@<5ZRb;Tn)rtMkKAaHhr~vtg@A{F`^@hikY2K=;-JD?%LSI+!yJ zvNLkZJVE^Zi(`!iPjAR(XG6<)TV37y%4g1th85L-uM-GbdN;<-O@Zu*r5-!B-J6O~ z`LAD}0CnOz9nZmITzV$&SHU#ExS!`)a5m0cxaYs1$mzWt=Vkf@SgiN^efi@+8_giPd_Uy&8DEB z04=#_h54ECGcX4;I=^Q3AY`M!!%^Lmg0t4bS#y;G__bCJ8<^&<+L`ZUm|vBpa7b6I-U< zu$?@(M+MSx+(dfkG}2hlvgW@W92}ewIn#`=G(aX7a;k1aH5?}ATIrfKF8Uyfc~>TOunI=p@sa+mK{PY=nKO5^F^WAlFE({rgC-t$8Y z?{})MtCIR*Gk%n3ITRCXN^F1}fNP~?0`YkKWq6MzT>N`yq!9UKwEXT7Xo`@_6n)?A zd-KeZ9(W0Ec+`7F1QjTel((QzHw*En7LOozGT|cVC%;Ks$RM0SN*&PLgolUo@$tQ? zAKhACuf2bzs0 zxq9KT(8QIp)$BGBi5^%GsRs*Z5H$@jXHwOXIxeI`_4K@oTqpyg5U8<+#>P>|a?tyK zMU<~Ttm^(nOo_9;S4fe>338r!y;a`pw{KqM(*vV1$93KkuFQe!=@=+@ zmqYgx#rM7EOK}_j3bY+790-J}aM;s*9u=hqfZ2E#_$ED?C#Sjt;WWEH@W3|m-^d}C zxNrj#0uC|cb{O!%kcRH)-{}0LQ4f||y{!Ns$_Ipb&Yc^Xp8hP;4Ow~O?p&(V^%5Hq zoe@Y#Ey=w0FAEC`jWylfMQwooM&KSy1+k$*O8C9%1D$Exv);m;N8C{^6C!+cWyjOj z0^!gXkUsr+>=0hS?*k_dloEn-XX{_2I|eV64nZy9m^zD)aTs*aS;@*L2zMdh73kkK zKEFL4acdP3wc=s07b4!$zkL@vT1Q3S;h=tb^xJUolt7?-nPGNc-& z2K4vu8=aiIcrbNieLMWT+!JLXnB1L2^H|7vXPR2ptOKUUmhS`W-z9?%{84<57}{qoe^N;^MLFgz^mVMIg(hY_cp^bIJZe_ ziCe%zLH5)0dhTwP2oMS&m~%0EFF*!lIrKSsO|M%BW2G;fWu519ITF^^OtJgiwPt!^ zYXVM{6`I^xyf+bV2J}+|$D>X7WEHs4HZyS5v0ET(#S()MoFFPJN#$h>#0?A#90!XG z@S|M}t|G6rZ4H^YG9)z@y6-64bSR>kh-q7sk)nod09Akl39 z=2f)!c;&f9H33VW_buJL(%E*lodf`0iz6Y&jN}vSx&N~G{161*|H11@XU z$HSwr%#3Kz+P%bEE?;z`Y^Qj-2xsP&O-#?yb!@tinPI>*=^38bAZA3o^-gZh>}e=4 z>bE81<(uLzAUa~o-UfGswdK4-VeyOBcAUo}g0id({~&S5wvVkpUC=kLXqWhwgz=ZO zb)25N*>g)>#&5`_Z80c!$vd}rZJ?j*Tf?8bMK+?2z3uDkzdex0A}J&5{+5q~1yKHA z=5vP3)zfa6p1zY)+-jX!v{Ex{hy{LV!}1n}tC@-|qA_-Hhuazzrsl4SF2U!{rRIEO z7?HUx?cnKB={-HoL8QMR%~)kROf~QQzW9<;cC|WCiyppt!NmhG1+weEmLx>s3D}~P z{QLU$-wegZy$wadmkAbZiaXObfoK_?<)%!E%*eEU z%8wmqdh)=(5;6+_NH?I+yXGZbhEx%R5VT6zF`oi#7sv@xPY>5g`CX zzHFo7BQPjXVWBA%MXm||bMoZbD_4%AT;XKG1q}xHTZFCxuKVY&Ur)SdyAkaGXzhBa z(*N@3u!n~Z9YVEt6c>Vi59#p1e~}a7#6TWgy*@^daHq(nI3P;$T}QuAg^d37%($On zWI}ezeChZ1uoo}(w|BS`A)^4@_HFDhoYkvg*0Pj8k9moi<+8`q~ zK<#+nGb;k*5tt1V1TzW>bWXm7F^?iQkN^<;_5pOz2s=12j6#^l|0s*~z|8>*^|fR; zC<16p=@!T{u5je5ixQ9s37hL4r!56Vj20o8Cw#ysAu*Kz7t;gA0{}T9@(xM~Zo;FG zQJ@~4Re1(qmjVk~TdM@immuf}rG2T;G`H#1S(UqB@Pa3e10j(Kt{6~g9Jqj^gSeta z{mY;#+1X`o4}xnI1;0uv>HT+aqX+#0P(B+=)+j}~YbYyMD-E#lK&d_otO3H&*yOSv zPcyuXsp0bd%@jfEAie_V^k3x-+P}i}nSBtUh4WyD?jO8<>)(UJnA3F76|g1hA(Zg% z-FVpktj=MGP*@?vat4=+M*yY*GmU6a_Q1{jnA`#;)JTH_`~aXR{ER^YRC}6X?Y(LKpfe4_h+B|Eq(oyaFa;H=Etc%oi#|?`%oY-(x3o-)1mZxnm_|7 zPQ)Q#5{^m%CcR+myxBR;f1hPSXey~)0lFUsiBvjJ2vI1|6+Hjr{%FN?PV)F^zUCAV-A6IV&j0P_y1Bya0;IeF!q- zv9DudqF=x6$dP-lEg>c*R#8#`a>4i-p}W4+De_?9{vS`7w~E1B01v^5Bm-fGeDwmt{rqYF zkVzmk!ri&c2XB@EeFP>n5zjw&k$i&EoA&bdCYB2Q$ATt^e7nsgRkNY08_p^?6X@V0 zo*5mt%jF>@mTW;|Dm3LMK{ZT zj&#qAiOGx6`vX!KVQr5e9r-ssg>mfvzpg`YPa;3xl&}2nE%+b&+J{-{eox$dj;V5^ zBDVW6OI^VNVu+8@_Wlx6-(= zq%)AcanMFsf;AMB$-duthDQo{<#~jk zd{xs>nH#w9!1IqQT-5uAv#qHkIN@=U_+D*6k6B}i`?0Z#EJN4b9~r2JdEqhEcYS7W zFg)L#&FouKA|2?-*f^-|GkcMXo+JDN&y8jS;_K9FFv_Enj=MA{sez8@VV8d5 zJx$9WaPczEKaCnrTku<-I&_GOuqp52BZL}Z+8aH!O&@H$y>u17yF0w(0?iFZ(qUmS zRCY09RN0^+^tpXyu)_X2%P;z!SBLDC>Y@Whqe`5O)sX#IBYmh;(egYN;InchHD%E- zP*T(>dxlQ0xl+n_yWr4r%rJ%e8!(Xeu}QeR2v@c0Fu_qNXWholf`7cy47L5L7um4vxb2&m^~lPk;MuQUWiODA@7|-9xNkrxmk;&lYQ?3Dvj{ddd=?v_$ex0| zZ(60q3T{*kk!mAE+>o6rWEZwJd#BPhyvJUj;&+3qQ_ zP)yCVrwrSf@bMibO_oXCn*N!W@nC?AlZ-^wzF8DXelTgs()kKkdb68fNu6^Mj>^4c z+=UcogQ3{d+~OKRHVAh9RVZbJ@+te7d;$fMBEKNF2) zWU|}8eauA@t_-y?pkA$|NI$UIUbd_3Jkyz2y~Vfhu^N19$oKp6aCB{GI8YqBlYa3E ztT$B^@#~YR+j5gn;{`RvAS(!H3Wsgpx9Yw3Kz`c;Li_pAp-unoqkF{3Ha?l;j3XUS z)LGj|^IP!R_dRpfF_)uAN-&K&^mVaDGVy;g_vg`AuHpMQd`oE{k}@TvG$T?}#xgV^ zGKWgZJfzHpQkjZms*F*{Jj<-83<;SNB_y**i1-~>?a%kKe($i}wchuicdy;rJHzwb z&wXFld7bBR9>;OkUO^!GIX%M>$u1dJ=Ew=Y4LoMB+f>N)j`UQv#qC17ScQFNM%Xe{rJdY^+e(?ZS!kmB zl&|9}&kHU_{`q-pK77i`Cw%eA4el2@lT3JJMY}v6lYHvpxWd&juZFEOLt^1U`j3Le z?g#m4Hq{uPd7YI--T%>j*>R5X@2*PO&|0g|CwbXX$UUT`wANGVwsQQD2{(3OhiXSk zK;YKL)TXa*m{pB^{r$&Ax2&-X1Jq9%L`bpAz|kl?bfC8kAJ3&?@vRmuB3NL`8GMT8 z{2nQHzuOllekeM1^DEvbhOLpDFlWh`Y->-btp0WiCvxg>P;t}#l_h> zto15*jZTiu)Sw1%Z&pA+B(VqU$Ol+7%-bqy4qT+fOvcyD&bF24vyQDZ-cov7f6VAh zaBKg$Umq?uTel|d>|5q5GOp^nSWhtxFJvC}Q!{<%$VQ$kHg5FOgnF|R;dx`{+STkA z-WeyRJ_ueP+G}hS`urCs$9_%wSZATM%E3cpR-G@payH!ZN>=N#S=$+vBk1a+>zaFY zucAwjywk}%`@rJ|z63rqYW4D9%{NB1&?LEqObVCjc8CoQl7+qIfx~aKf7HtV zyk0MF$DA`K^axf;GdQ-K95^X*98kIi+n(_YzB=0y_bPwP17Y`G@YqODqmJ2#T5hlR z4Gq81&Ko$KY3fCE!)^7MR)3#4cG4+;zu*|)gn1*&oj8CK3v`%Or`f;EUhw_cP<%)| z#qwsR@@)P>Xo}$J1GOm|_Dy6>Yz-K%9=g)oUvbZE-<3?=v}M6`M@X$AvM#}zfEHX9qU51amONDt4 zbf2ZDIw>Ch(Di@rNcNO2@fv5bWtW*>I_}f5Cv<;w7qEk=>-Ecc)&)No zyn73=Sn*R#UuCtBTIaEaynSwOHZOM6jw7s(lYo2SllA^Z>9@t3LU%>F6(S@Y_!zdL z(k=Nej8q);n<&6NJ4j} zuQ55Qv2++0m70#cGkBbw=z_Z^t6pD6VncHwPvBy?iPli)*yhsvQK!GX-h|bqDl45g zGg{4DC$URfQM$(;x1fld`7Sw}u#pmtzXR>)x@>R%hM;=bf%Xn+l`qBjp)_=G;Ir@s z0BiB}w`M$xL%ayy4k|lADead*On6cYZSGQ*Kfz;vNW#B3ek6x@_%+xrG#d&Qe zuQt=u{liRxDY8QaJ3Y65Da`WviBYkU)sNmBTHQ9U95XB^*fsRaS#A%-JKd<(Okc`m z`T0)ivy=y$+s#@Zg^vqOTnc=qMZd-_D7S>DK`vaVDPdjvA){Y#tgG0!PTHEIEy-2Qln3>tmC}#e`smGMxch?Fk)i@)sWUZLSaDI2wV=SI8pQdFCt>3j3 zbjvjb%Mn$8KR~7E>}>UgmH_9I3@cQpOJj@Z0CR#Z_4kGOw{5!sY5{`s_tB^KJ-!4E zBFT8D96yxJo0vkw>mfF%3=9IW`RL5eg;r4S?}ydU6jo*stCAPCeUK+*hLscfker;~ zv&_tAhZYe`gmI~iLStyV0dP?SQyBPn;UO2f5fN0L>KYn$4kDE|(wC8Rf7Dx%+i59S zo#0oE1_exm9Zt;PN*Eq_jqdteBz%wjJ}k?5?V7mHPUcRxw%ZTzTmPSn1lg1Lq3@9< z;EVuoprQ*3bY0zmKg~L!;rk!FPtDIfus-p*xwSR+`SWA0tc$*V2f(?jtxYM+Qj%+W z{?{tCLGtqGrvo6aHV@AslvLI6iDw}p>o7XCjdbk$mi^DowB(CXe*M3~DFI@_dQ}Cz zU*0WSo;DAFoM6;qdua^2wK(F)Qk(WF`hr6%|DpwQo$og-d6E0mb6Y_a)WHroWyr;I z+Y&ulrE2WGdl|@Rlz&-p7#(!P@I1HIU0p~oJNyhw`|%(1Jy`GNXf*6t=b3 zY`QNZB;>q#K9YH0)caeLME|mv9HnM^S?+S~tNtbmKGBNRt1Y#hP=$TJI@p;hw!BU* zWh09rC%XDs7h==X(`zSxR!Dq2{T95j1S_s!XbIxdoDcTm3913(buE5l2h%PkO z&pf3hH%PF>8yhgspz-#Hs;ZU8jvbrG%B6qyoi#8rlF{1QdbYU1_b}yqos#XQkBj2X^k9ztmZdEf~^&ob!1$5-ILZ`!7Gw$^(1aVl^|sE+^s z$;bcCB1+T>3k#D!6An}@^%%aopY=|i;w^=rs~H^lOuEv~AKB~v4Pe?gh>oAkA}_{| zF>#64<2G1o9n8$i>J!1z4MW2y3^LU)8qq~as}oI^(9)8x^*PhSc<)1j;cK67Xi%p>WcFx>+hVsTPp?FKdwVVUV_;zL_Yc6xkj6AP zc)i~o$Hc&3fs69k?T}%vOq-j=S-Re&LfbGBH_0H(~|Dh!8v9gvp>uqQGepuzWsf+3Z_eE6tr& zjY|8P&js;4d(!jq8XEmO;D1jQWJkE!spf;QVL*`P1ZUia%Eh-TK?QQUWj0cI7d!i{yg%?jjr~L?t@M% zgk>s&cWJHRa+!kH3e)7OYNT_K1 zaFXe-@$oXB^}Fr|1>GWD>tHQ|m&o5PO*;8vr9$kUt9z;1)(uW%8l~8L5A8O>fq)YV zhl52My_{p9n5Csav+8yD*z@l7>t&GLRMym#gB_!azy!n8RWc_WOi$FEB(_D=9+zJh z!0mr)YipadW}S}E5!%_$?BT3e)kh<^H%6Bm9d0_U57sGlk@v#A zm*^yHS~kg(Y^UG$NUFr-OjarDf5LVCfr_(F+w(fmKV- zo;?ep)Yr3%Q)de=UdO=#J2%`R(ga6)Sa+jjEjN+hsj01nN1C+V_t9jyh{PU+V3Qk} zMP*ghOIRiqTJ&b&foF*j5kHk}sxHo_xJmbeklqVk|6L=(M6w3IU)+XDRed-GSqO^l z+_`ENB{Vjwr(Z;Fi*LvhMH$JjG3@))+SfJ*13{)Jc*SIYq?OJ7x$E<@OCy)siqSoz zp{$F3&|wMB_yP0J{9a;y6dcy2%wol%RwmBF z%E}6-PTlX^T#&0No@j&{dyB2G&V7D z72X!4A)oMbcN?ft;O&7O11qYo~~ z*RO;92$6fXWgICfJFsKNN@NK^V%j(z&x>+`mF)?KvU7;%@YuPXmL`NJKMwLpN9Ro| zmE!N{R<*ThFI~3m{Lk+V6FMoov{EbkaAiFm)}`d9(crSRwH*fq#ZGtDbaDYF>*hUs zPPJ^N((Q&c(Zez*dC!j&1S=)ePWk5@NAQfhM@H@?By4ZKVvN`}UN%2F84wiIWIFW~ z9oju^3)ghs;Es@GkM?>^m@Z(;!h%y5roC3Ra)kd2g>coSrU`T6-%r%t((``oibSeO}7p8dp}>JwtJPQrWs z!mC>=No9s!q!FiFF^&}rzx32F|F@ z*2`b}7DAv_`z><a*<5A-|C2{0SH^K;~3@h{gsgYOH@M2nbpd_tIkA}56eTcyJ-NKBXWZj5f z55YhbL1Bm4fcC33~LCEw0~fzz&3$B12y)^iB$yH7)QEX)mmd}+cXK-PGv z%rRQJ4X%B%Cr>i`BME-AqzQ+kE7m~zg}029fCo*{k2f1K!vfhw`L}Mp6RWn?&Rx>3 z4Wz8L&Z^-CIwtPB*p4K~Kq)yILMiKP^GSV-@^$eXWxNe#l;=R4q0o zW19nFhb-;19oD4j3@9hFczcanPfuW>f_it8alyaHCaID0-WyG!CIx@)d&9%S$|pYKd8ry0balR#yIPkMzknkB z#WuSZq<6iSAE9D}saavaBA)S89g4#C#Iv!&zj zIn?yzs?Ot5)SAsxA5WyUme`_$L?GZdN zU3}ZNy&be}8N}f_@A4oQ5~`}64>$)zMVm&ia!Wcl4cfv`)S{WE+I09it-hWfqW^Lx zCVv;TO9%oH&$O1?pynI@_3IAmCnQZ+y-S+C|A2Z!zJ^j8=P-(-Q>gT!pPe}cx&-#g z$f&{Q0ygM>%1lHL<65DxB8B7e(C>nzbR@MTVm`h^jzAR^PuRWPym|AY?u`(7p4bL2 z1p5~S1&I@eX`oFbbdv)DXImmGg<3Z}oQoX05_nd)K;8ofbmDZ{Z>t4)iU<*-{CB?g z#fZLuGq&&8G0gP{VkCO7aH6keJX`-Tg$U*GiWvHuf|0@6!l|^xs zbKb-0BTvdtw;;PKN=mCi%YZKuJxWW|y-7=s3Kt)}U_GV}V5C_43e$Ah=lM6IrZl2J z2moJ#NXo`0M*FvLA|>Jb_wTLf>WlEO2m^wYg|Z8g5fPmbD|QZJ@(TGgU(O^g$^)4x zhs-2S&c`)1@VInya?02HM*d2QMN(=qMCTV2d`!LSeGR5b``%Vmh?2L$e-l3Fo;p=e zUWtpH9ir<-Qp|m(5oKGqu1&)L^+qjDe380pqoB}GF?uD#6QQ~LUci7EGqw@N2Zp1eZ-fbbK(4QH@MD@w-Ch6S&MQzZAlo{*D5!E2-s zC2kkF>c2&xzJ4bchDanTW=~ZhYq+Shy>a(o6$V%6XzfrrAJgUK?Hw?1ss*+t>P=eJ zW6y^&O87E|;W>#E-gWmqe)%%)oA^iz$XfJ)B?+Jdw%T7KM2FwSsr$&O#xAt-1iW%I?oU8F+T z$~3ZNiTKFs^%;6bf1X~nrs7Y=&nR>3*)hayq`wt;OHxT=ixJ=0$AUL%`ddC2J}*&o zRnhk+RV~ApgcM!3@hVv-N5?YkIG5?s;5w@auyCNMo|r~(fBNhhn630)K0b$`ERUD4 zhAXVxtWIH3%Lo3Gr2MnLa)p+!>8C9`Fk5L zF9l~Xd1vQ>L2)4g0ZWI2tOB~Xo<7}zba2zwt#o@Yy(@!-$(OHRKRlTSiAli~p(?x9 z6byZORN5Mnn@Hyk)fS~2*{Tr|6&f0vEGDmCfUyJ?a1+eA3zvc~s9^59r_8R;CH+>^4}o$sd731ZL6z#~<( z@!84VHn~4N-QAmpqL$IKYDX*}aFP`A2kHXoeKVWODmnYEoEH=nd~tnl|9*=$g5?#> zeDy_uv7DUD(zRSk)kR5L|B6^)6HEwp&7Z5CCt;a($%)n-k2LG|+en%)AjQs^1}jIpD{=T~=i3_P#U%-F!`?1G9Qi-v@@U9< zwM$(kTaaf0=-a~2-wk$_TIb8rlXa5QT&Tp=KLh@^t19qvUTgFx%d%rXc`?)rtV2<_ zrG&5yn^E!-RNKajJN&nyUO_#iVbhE10a9s0B#;TupWna%LJZYm%+MKV>)3M-UFD#Q zcBhzyA6B86qdvP`P*4HjgL_C9!8~VRV0X*i-QDGZ6>65z59VjS1f@}HV#QXG3>O^# z?%v)lV{OO?LF=zFt=QQ6o^dO=@nHjIyCRgABg`S98A59~Vc3tm4ks%$fzKhJu{yfA zBz+kFXrA^w?gv)pv(kOP80rLcIbXj%10~;n%a2Xnwp$0QDptIx{=IGI&f^QIDQ?Ky zE;`5xpIqQsoKd6%>>@Jzj_DXLqoae<8_UFcZk*Joo*lWnWtVQdq=dx9Q%%7@b+@+e zD7fQ$Q08tf2ds|pU~#{ZR@0LOh-?%7dkgdZ(zNcw=XnPM2=TYbN7d=|iibGYisjQ* ze?bj;2UsLN#w+Y+IKY$fJrpk#5zrK`fRL7yTV7r($T^4{lvG%qGVt1FU6IpdI)WlTV5Q)# zn1vsclVG*;f{BMi&=>IhW+3|Tk(NPzz`9$Tl9raPsHixHddAlAPhOYviT6GDImF~d z1HED-@w0_5&kzZ&2_wefXyCymP1x~aRTHo7KBZ_l z53V54{u=l|ti|op>N^tOh(mNWN#D60ztE7clh5(1_^3%O>ru-P`w_er^hd-vLE$cYL!7ZH*@(OKUaUb(1XG(#96-N=KZ--u zzV0Vp41THzN*XowaVNsqesp5d(qu1nLduV~l!HqXD4cy)WHxR*dLtno9^`OuQE!&U zP19U$n28#v`NtGYfO>Gc&0ubo@cCCu!NNp~VpUr4ug6@KCQx_-It!fi3Q0!YhDB3` zY80xXTl1YrCUF8nHTDWVB6}{pVSoxEZ=kQGx<|)lp7z_677Qw2xJjtCq>iqK0Xnw3 zW~!bylp{4`!A8s|H}R3XAwI?|ho$ZzG}=KPlC?n77JsKL`hX_ejnb0PK49iz^vCFV z31I*p?UZD=WG+5z(<;lv{`hQ>iy1(GT7pHVNuOfe+jaWGN0uKm-*sRhlm3(QhRb>K zZL-YQQ-LO%4HbntIc8>$(+J-$M-aK(R~v9Esp&KIXU7Mj60hkP)M^`m- zt_kc?Z(xg9@s(D(a!r!JuGg@z5Cax;54*Cc_jnGVhZ9^Ac}W9~-sWhb5kkzsBJKh9 zXx%uZi8m4k-HjH+KIwnOKnh_U3vLmo-V*w~~G z=I@V*bpKaJcZQPrg@vwpqWb2OYwgzm&ueqyl40J`kvcFO5t@)SHK@9MH61Mjok#QM zi12U(%Z}n}Xu)mUwoUxNfp$HWGj^i-$FZ12?xEEe5u(61Xh7%j|q-Xe1^E8Y4Vmd`<6Xtf&Z9_8q~Yp%)NR?3IQ9eBf9O zYrJS|OmM4+2f1LCv%@_=JHt>uzCX;%c|Tgk-`o$M@wX2_4S=e7KC(~p(4jLLiw@6# zq@l#vEFpoff$uhY79H7L2yK5hB8=A8pPF^RR3`lzRBk4X&N#8YbayulUQJnMb@Syp z_o%3|VU3pt#Bku_O!+?bdw-v|7nkp|9`GjqSU%?7lcq>8myZ!8I0qgzoBwLc0e%gN zS?v%8%Ow8(+~R!ZjH3Ch{pHIUa}wwuHmMeGb-!Y3dstbS5y0TZU7_C}Y%Un5WGpO_ zo2?|UuF>=SKh@ubFhMRH<0$qt^i3J)Xi3Kq=MyoSDT7)FvNO=ZuJo$2!xp|lJ%oIC1Ob!44A7-`mJ+G*ozX=D zvB^R~6+Z<1+=TsKp4*eeqO_Go8Q3#20G+yL@$oh8Z`34LQS_|4-BFt2TtohBVTH&$yaROKj|IZ!K8*REd=2oJYlb&Qa3QftY>Tasq(yi$9F>eb8YEwM2M z0y}oRgjR+$V@ai*Gmiu@>ssCv9S@?4@z6e+@b8aVFtrqIdI{L1PvnsYk2;E?FzM%& zp;#m-;OXc9LM+GWUk1U;ntvt3$A~*P6EN0mB?2uHzdN3u%W=C&{{~&BDyzGAkyCd0 zdf6t-1Zc;~I-_TW zu~;g>eHTPInE_%WU)L%1ynVYHC%b0ruE7Stg_|Wv9`Sa!)t}B7r6FYu!Bg^8=wVH%I$xflKUJ3ok=^3eWfJKRy6O}EYsB!ukp=?iX=M`XV@cm7irHmpRArJ%lP^BrM8V{BL#A!OoiXM=|0tkcAk!*b9CKQCe ztOBn{=c6V&k4(SCrcZp2A#CW|$A^DIQbXx3IUeAL>4~I5fy@-igbaoqA`Lb&K%SUk z+PIa7&vFVz@7`r-&v9*tkq0FG3bIZBmg#W!MfpooGL$={X+mfxQ1#$A1s1jr9T{Sq z2C-_P{qjvQbH@=rW{)p13p8LUvui@VMMIGxS)Vnuv}h^8vn9KQu39`o_KOz(IOb~c zAwfo050$mqz8ROHlssMiUr2QNjG0$JyAlm08MOzhnAyV2sQ@o$Zah6C1H4f4K+Gzm zuD%XwUy9V+si*P0a>ugVZ5=++L~-{S1Lpmbxb!;g^7qSXo#17L5sw8jw1(^G9LXSy=~v0F7nuK8ia2 zFF-IWh1ss4yL=3`j)~kVB}GIVoWRDR?I4bS!=qcYUsI!Q-shACkcYg5>6IT`{R%_S z{W*nMmhgY!+qLUNvwWhYLt;)%ZZA=gQ9L|Rn(_QGk>tRI-EbKcHo~Z^--U@Wp*qR? z$Z4Ho{_NQXIXLiSid$-E1nWe9h0j#ztQv=R&UY62g9{rcHy1&?*b!YGycCJsKZ zMy>n)gE^fr4TY_IZK_#ofBcSJyRv5f0AHbzp|df}&P2Q~q>Vvf$MJl)YsWNNQ1C?P z8)K6ExrT~{9K3O2x1c-zdkWhUwKz-@-V5pR4H9+@ctd%~XW$Z!pw&CZcdo721u*yT z?}%f?uZ=7_a+Mf87T-!QFqipH-ON?K>+5?28<8s|h=LTdl zRQx{l-=_r+jRZ>sSk|*4BWU#?PFj4-uKH@e6y?!v^dC2)x&;7?SSpWW8LqrKg=62a z3%SrA{#z67VPCUu9RVGHYf&>lIP$@{Qr`Gg`p}^b06`H#wt>6j4Ly5xs)Ah8q*tmF zWOm4?ao{h-<|Z9PJc?z2VOB6Pp?qEoI2x_w6gnH~3s77%!14SN6tbFIyipwjRg}f_ zp5%UVh|o%pNVqM|eMWP|I{IIqlt96V9~!Yzxr#VyNEHk=6<*|1k<;m^wHAOT$RIp4 zh#xqieB;JZ-8Wf=?v02L-x!wA!~K@J@fNXAmxlG7k(g#{a1sQ`g2QiqEl&`k5!E_{qK*Wv-<_?j>2mjmw-5j z!v}Tji)*tY5Wq7=8}afRT@MTm^~4Ke#Z@x%3N&LH3k_+;qo?+cPD!n6J4$Bg>FMt( zY(}^9E{PtnUmDIAvy>zbMHsb*`dcn%9_d7TSecsSC11b*2#>!;J9iD~SSD$LMoP#M z#HtHcEzq;ucD`~op^xTvHUo*UEG6T^U<}$et?KKlV)Av8PgH| zcYl3pBH(eScRD`;-dn#V<&Ot$I zcgLXl_JqL(API(shUu`e!NcXmZtHviOrB{S8{1|fAtoZ0#YYUTU%9w7RCIA%2dG`)Pj$CT#&1?F+^=N8G2EViB+k-9;|oJfd&S zH#j@$c`#vH1U)9QPha@odguSKmYmwoFMm7X_naFo#TO^%;9$GktfxN;@}s z$b0+ZwCuLt8dLtn^>W~~-wEyYxcWy3!!#CZi z*6XV4GdOR0@?xiT-)Db@jeb8n z6@R=x>yR{&@$+n4TZASit3LUm?9tiiAnInhL#4p&KN(}NC!J$ihr^zt>&tsUq0gAD zkN-aQV=q+(8G^tPI%m%n&Qw zb$TJRkhy<7M#5fteB5W1@VD*~TW53D4Saq5xS)Vwty}J28i;(UvU=C6o6_^g7D{F# zfb{P^1lb#AxFK4dq_GD-;M*zi*{<@5pK#kH3d%HC33YWUK2Sx^bo`P;l zl8>zyMVo4TYt4URWT{s)<)nf?ejso(sF&pU;^NBfs=}#*u@ym{B^P|re4{e~xH~LzCOw65l z93n&=Ri0tvn0Zz*eWNo)g^8)*%pc22Ap)e4|*>A!<}|k%bu)|*_u`)MFek(kcC1aww&tyW9%3Hw!A~pRg;cm?(P^3^~R6^>i0fBwEKWpsbrb`S0oFGKcy-Dnx&rM1qaQD7o<_}lg_#dxKpi7ZHBjVG?) zaU{oT>wke=8qXk@^J?H5}2fjNU#03n6>xtHw8pl@4JA9Qjp_C$Q{ ze)OZRr~mia>d$U7QCm44?kME`=#zK$OmiR)X zn&#=|S4Z#HJ@ZoX#e8X}3X#doX!W$Oa)`P>{}(XJIX*kAQM$OAH}bmFaaV_tBUTwUv%mHc|!r(Z6oDN7B#9tPD5L@w8OW|z4Z+i+w~_+0em ziXM$SHoYykjNGSyYY=9t~yUSFK+p(8iS6yCV-V~1D{d!Gp=Wszcz7D?ojv4 z^~`s_Ji5RXdreln{sSlL{>z!Kf6ATo4v9SyhyB`CQFTec{MCbIj!fTP2vF09iW%-g zfQntK-YE>>7lqCouKP%&(X-bTc!0Y&phP%Z`6$%^Nk1mSYwL&Bo<3kX7?`OAwQ~} zgotU+JDn}G(FhyoUh*v5Dt(qy^gyPFRp#VeM=&x&BPxBOIKs>I4G)#>T*Z~uy+|Sq z5&96>cDw%R*>@Z2Kd6Pg_>pGMe~UXcn5k@@WJ4=tM+zVa^`CN9VgPritx)^-sHQ^f!LB?Rm-MuZ?#d778}x4O!8+ zzwYk{YP6F2xx>e=zo&3fFx10Wf<A+Ulz$VPCcSO zqO{cC+oWoGF;2C;b^;sX!gT2RLHY_l!>3Qh;tb|Rnw(vS;zvfNM`5eWuSkyVtFmToOcT0i55|e#n*LkkK zgkCwzh5L-ty1F+yM4iXM&SEO;KC~1WF}uVI%i4B;n;>GE+7+My3a-k>BA`d48?+w) zRp;QuF!TELw~@sX)Esb*{uxKPCP8GC0H21NjB?`z&HYgZT67h0aC1*zT9-pQWQPvT zMJCgmJpT+ztUDN!KO$2C0f+Jw?{_x`_PI|cQ}kZSEK)_-npm{hMN6g_{OSrQ2C$LT zh?2JLq(_Pbv}sxM_zNj!oQtQ}&aG`IzA(T}Xx;E->1^!SdoRTq=++yvGNb3*8LtTi zkP-Y95b@0Rb>@CJPX7TNhcSVa)?I==7?W5Gjrv7&{9?Pyi=bI z5GG;Dpp_eXVGld3w*j9rL&c-3hzAe45op0g^3v=O(-Ekx0A3A#AH+|*{tZCR8aj}! z)y#{EidN1-@#*dS8cZR4@6uGT?ZJPl69_wM#!!b0CvF>bM`;435}p?yWGHAOi-s9+ z4aNAWx6oG?oNNk>@tgQxzE3>cR1o-~C1rwA4Td+fvL6#}Lif48@e7rbcV_mpwI zoQ^xj>>`H@a=20g$ z#iTY9HBBNQeO^I9Ml@p4Jif_r&^=`I9cd^1Y*Z0Odl|ToirwTQ43c5MZs>q>=?RNZx8+Ejo=x4i(rX1V33pPai0jf@O$${_?`S6QE;W+z=E) zu)lrKJoVE07;JI`Tnlo~sWNm?)f-D-H-u&ZsU#8R@bm~GOKkGNhoIasSeP;y z7`y@wwYrv856CL8PTCI0ujQNXI&-;?uD}#F1M|Z!Lu(eijc17)VzE0DJ5J%%K-RF% zp$`^O;j2X%N%0C%@&}4AV<0mb`=B%b%5sV{_Q_Xp1%z5c6n7e075$MejObC0PfnJD z2}DD|&Py|t&iHXN^ zPeP}0Zef0w_&uU0t*Wiv_AOH!B08X<7YpEQ&kQ-i<4agmnO0)#44Hx*cIaGm#;a;+IjdzK5)XE@!j^rGnXC5@zhb{);JNX zLJ%TyUlF^oR`bXkg&1~75eKD2D|dpu<(RXrWnt-a2%(#+m&)S3$wgQo%a@bt7@{9o z>#hbvBuV+iG?|@>RxlPJ;UNGM0qOW;Qr(TVy^c3TREQsaR4IJ1vTPmUcYRPmMebr4 zIut0r??cT95dhhQ0DLb^)n}VF_8PlHCVgn3$G(BM;nrZ`Sq!;F>gCTwMW?6N4^F&= z@u&2zAqG{&okOg+B!J<#k=3J_0x#x`vC zXhLU2dqL3ZP5Wt0+@?#jrwML;9+QFzxdDcm_s}AJvmS?*KbJEe2X#cQy|{kFi$3O1j2b~CjLlzu!DV7u zc;LK*11K%^QS`pg18AgbMJWNX4xIj^?M>L$FdG}uCFBgnsmHA1mqWz`1yfHi|Ei5n z5F!VrRqh`9fa$7%b;^B80RgMoblHva&{w=i7om9GNb zfO*}zr=0mIDS~>KACQogM5z4ua^J=t;ERm$9e)KHhZjbWv?)fa55A*L=+%SJ#*Bjl zDFDIvJ56i0@pXWZx&p^y`)Yi$O*?lcme+LVn1?3ss>sr*7WAXi5x5{m#lHuRD!LS$ z8oGLraG~(*5)u+HT>Dkj;=D2FeH%vhIsY^vTkB_}gwtBs#-2{uwsq^9-PS-`Ga0$T z!GsS>@7o{|QWJD8S4Xj7yGS!L>UpQOmO#mM)QDe6kVU zu750};_?~>dU_9>q<-9zK14Dl19p-pZ4w>vXZJ7Srq~I!APfX zkCe@Invj$&JqYS4+FMDnI%`$?!~XG&{+ISdWFMr->{+lEl*0`;*)kWv!4Rfra6^-T zuplAo#O$V_`h%a*UHjXVC3?g2A6+g3U`wD;@UxG6qvFrdzk`BW9zIm*)neVaw7hNlU2i35B_D&55nb`7y zA8lnYJ69w4y(0F3+pl7nRA(`Vx3eUyn!rf`u}P;S^CDJ8rfbI4a9WcoasRt_D~S90 zSFiwIffTu$@uUbQkAY=(9<~H%aj7k{4jB~Tw=94x&j!+qaCDDzZwlyUCn(W`dwP26 zy+O0lpUuFL2FsRxpIMNeKQK<5DxOH$(E51XJW}Rw_n2K(>i~O@h|tI1A9S5|^)uJ) zBy+fkWn^D8Kal0b)`KQB$fpu^{ogDeE?v6hur0O`9}HV2`jBtuMUieK2p$N}I6Utl zuAPUAFj~rQ;B?(5@84h|XJKKH7Z+?XWck)Jb|`fOtK_eS9D>+ElLz{$(_m=FVvy25 z)HiqtWYF}3BC!+l-Nlb2J9=Hqeb&E>KQ<5-3-} z9>Y{@Le4{KVcTZi5PbYopC;oL896_t1G`;~ozQ?H`gC2ZbLY+_O?`yR@Uqy3gu&+e zFr2-Nq}8u~hvt*y)`F4$5n<#`FTZ`~P7HKvpdAovPq@?H1_)1?a73srwf@L9xK_2U z2T>3Wld!r>GH^b^Rl>#5F&e*ulik*x>4%zyd5|4(%5*V;Y*c#Ihjp-uw8gG%f-oV2 zo`1O-M@Gk22YXrxvoHuY-uB10);u#G#__w>)lo zxdq6SHWA%}6RM8?>P}I#xqA>tIs-S=sX1M%|=lOogiJLw;39Va+1Xbg$`JW?W5T~vq7Ixp_D^ge^CS% zo~bm;=F}wk8GnI*9r_1o6aq@qpiQFEal~q9DXaS7mF_p5*-5Clq^_~sQS%xr91oB+ z@Ag9j3tv{oyx~J&Lt#LuS`>-1 z2y&D=2sc^Am8keYTE0glsgUr2l8VwUDRF8y5^8(~!cit2^}-KlAhQLP0{a$r?_Q|A z$d^e229dS0y4vhChnK|8(8XUJ)ZgF8G3_&;dmqH)xgcM1V^r)7kBef+&V2nQ# z$J{jg(NEq;Yv+IXyQwQVphf|eh6&%$>mOxk+WcP!!50q<@|xRdVjhk+=Qv0>9%$?! zgA6tM_<3<*dSVi{m?B;t6NTucCB)TLSCedy++&j9kOTtCkY4B8fF2+Z$~nYDsI%aB zs1Fdy|MWz~UQ6`nL!(|V#x$#($i6QCs;bP1(03pR(oo6~2bWQ#exoXj3Se6b9k;5$ zHc%KygAYT1P$kpAauX7Tu+^0lXognKtBsWl5W_k-R=_1R6a*S^zYXr1B|POy`GF#j zaRJ!E(&N`mOB&JvK{`+qG^9|e*&|f?9&b_|l$4g!wQ~l&P@ec(Xr6*(q z)8A#5?cu%EJbF#_>Y0x9dL@FH+48+-B1S!hpNo{g9r)1VuO@%zsi6I}?az<4rfhY6 z`tjHrJ(Ka5@!gk8-LBRNiz#*dG=4ncEX93YG(R8weJf{`vAM`Abu>6U;&jet-+Wa2 zO=`~4((*N$b#qUJ)nmepH8n#n&4NtPJ>{%;)dIe?T+;=7grhb*+5QCjI+S%_9x-Sp zX-w)|ERolNu+xz7=vt1S3ZbD}+5W+!swZKPPD5R_w>Jwx51y&bhyP|+h|C4t~)lqQs%Sem5x>qDHR=$dyfC;z5-Wc+aiL zr?D@fYN_4sgIjZ5L6d7G7|TrBSzyqg-9t>f0-H`)k8m``P9E*jN6ftI^lj z&m8Wy(U#)bR=qZ9$n5m##)m3?$}BbeSjspU=)@!5zZg>%k5G1M-NDY_SH0{QJczGJ zMV%xL6(}Qw8LVT-(7^u5mxdWz4(^gWro`}8vIW*I-IKG}rxJpF&Jf#lUp@HN=q$)N zck22B>)x$tXhW^cpKH;+B&?R?pd?F3N}>nDz@fC`)73^Lijr3)ixuAnCBOC{%zJq$ z-o1NIw|M)+m_$C?u-f6%<>l~%2nY=Hzzbk9o8Nbi7mi*-wLlrx35L=Gyk+z}a)k>r z^>cY0KVYOyQJDFVcz_#~;qaBt0<-3wj>QJ#oiPO+n+(k1iZj;5`{MeZc=tVC`30h5 zi~VOl1@8E0v43{WbMXv?!H_dn0j3`X^mso%)nnJW2DLjJfwh>P*9h7iLx9*}na(8Xm8KD;p)@p6zKk0-FI!dTMl zO&bDG-=ram{4$6VnO7|BwS#2i%DEifeN%Tap3lKS4EL1GMj<&Rh*s&kDG&D=GrD*2 z#upZDz{D_AsE1%EW(t~Sjr(#cSu?5V3Ad1NrB9m*D7l}k#N4|I@?v9YG3M%8RDUfU zU-fpmIG+5Iy&7q)HFs!*#j9`$MpAetHxXt<_wTRnc?jy)=%^K`Z3F}aOiWG5PzupG z?UHK;mfWBSS3g<5kQKaBf*8O<>77u zLdxjPaPE^;UXixfLiu_&7+-6cVdUC1p_^=%yBj+@Q}S&FM@Ka`Y}jCQzi-O9@Aa+n zZm&mqjL*zHGhYZSYdh{Ocd7irEcL%D&F8@jZhytw{f`3d$-f z{)yXt@NXO=q#0ul(hW4>M^7(386f6aaX`pwm3P?D7{4{MUPaMs?1H!ZCca_(5;L?Z z<2pXV?!4`a`1Lqf);X^t_w!U}xb|74-BmN!y$Y<;qZX_Zm~J0Ti&FRx#wRFkj zAraM5g_fRjgqe%_HO?YZEPv)7j%$56Qq1dPA2^IoyzuVJY*zCLgl-pwQRD zJ8UB=`+~Gtif`t(F!arNCDW?sOwX?qm_Ph8LhJH-@X$D#7_L59`^W#R-Ws{JQ+E=~ zV}@2|s9g3q-o(uJWoTePH7POCF!#NpbyDKzX8nLE{fq9n=dpMYN=avvf8VFkn>c@c zU&fZ6X=bO>Cecw13G{r{Hupr`IejX5m7AhAuRf|c*QQ6m_4ll>>9-quADuk4Zkfxk z;g>l1m>E5Z{s6=sDHaZ_P;C8JprnXz6|jqNpBpQMdxPjh!u1Ib#_$d%&7}3nAz#7n zt;e+_Rr+hU2IcWDr<-A9Ric{Cbx|1ClHGz^L|D+wA29Y=*%^2u41u!WfZkP`O zYDSC^eS4%tF8{xAHf@Ct zLKt-81LJ&0iAYyP&$~}shccWdFOhE5%X4cGleKfq)*~IJHLw$ZBeiy z<9>QGZNwSUT6cNAF9!Op3W_KYXTi}HMLy2!-8Ss|!&U>&E}iQf0~&w9uLM;*0of5^ zxiGLA--QpzJr`-8nCRkS{jA*+fBap4Zl;RG%72?{oWZ+7{;_7&?TZxB2j*3`d$(jY z@C`kw6nR@94D$RfFjJ9Ew6EH3Rf#OPVT<{V#-XWn?cIcu2{)At&>J5P<>cg?hw~F3~F-Pak1-8XMbdg_bg9rR=7oGz&>?WdyFz+9q z`?JEc{4eiWrt276Cm;HB@&}S%)bu^L;)~n^L@)8{zq79i6|6CUW(*%0?>DhmST<>e z2?1%zoHIzPj|_gDBrp`l!nR!J_FqnvHCI$U!4N42%Gd3Qi5*V{B*}3hImeGBlh?qLS4o+zv%HhbkQLeAL{fs7if8aM%}rz#0t<7 z7Cw2RsNi0s&u~7kgN$H;bt3;a`Yi$aosIlE=iM;9_%*&COYC3Hr&h^-?Vrcey z=icwTkLNk|e*f9e(R1J3vDUiQb)Cca{7hd5SSaG%u0Q7xqey$W15bF+Pdd9}e2HKF zDv5tt-TT|nskGJN+__y^TI^Xy8=)ALsBuxg8&$fz!CFBLaZfuTIX{O0JPc%v)f zrp2cqmBrxBk^f-_34iR%mz=;=6K96TJ-UAWoO{{3cbeKRo#>xHTEO8SgZLyzHWRTirbCsp?ZYIbJIGcRDotHA% zT|Xl}Exho+noXPc`wyGS-nM9Bf9LkQhxT2Lx%4Fcu1Jltmk4b-x~sGJ;W^*v5g>=*ki$(W`Y>Np!i-ym#ai+uQRDA*&Y{cQpW0>V7_PkMGV^EwvM(ol{OhSycAFHeVG4tOC zMQXK>x0~+w4%0Jo6Jr#ukcVqKYP>?mC@YX)ENG%AhBYBPt%_VCro9A9tH{hi00B=t z)IC$GPJ`7-yDF}O-wNqie}hS5r7gvM`Eh_7Qd zu$A-`L9@^s#m}&8r)+xHiasDaK;S#~ew#=APW7HtAZ|YL9f;qO#(xtVQ9KipUB!Jt z?GgM*eY{jhQ!E?dH*=kFu(lP@_^k>_r`>Thy^hg5a)b{ZO@kt1icLYE%61Xvk6amf z#q3PeY;+eL3Q&eT>~gx-GQax^qCJi>y|Y1-fEeh+whQnHJ!I>8w0mhgr~A01vr$xC zWIwGt!mE~nwM ztK$traP4nWQ(K{(_oRn_0-@E+C$>C0G}kO@nksKPfh$exO{seY#EcN{KmhhQR^z*O ztsuFrT~TWv>o0$fcTuu$#|rY=x*>zce~H|i)t0`bMFrif)^aO$;mr+No0ba=dcm8> zerMA8m1kgOQKjomPibrm$F5*gq?re3LeE4$cQLZyCnV2!re|}@#G?`WR94fUJ+_i; z6tR_^H9rPdi>}y@u0L3{hVizNyeo#M7o{v79LKA$j>yG%{+^y$z&!xJ_D%SL3G zva+%*@GL417T9%FJo%L9P@K-Qk~OdTfRn>TM(At>cRG3jjumF;rbLCc7eF3I!~`W>Q9{R&|X{lIB?y@z2P&bN8Ujt}wuuVvyre z1FNpRr$nyaI*JX@~-1LACB>zt3~4Z0u9{>ZF6wiK{7*E!Ev4huZPX60L1%3 z4P9Hj;LP#QEvjl5uk>%McY{ZRjop;sRao1ke^wijR4>(4LRimev-l5J+gg5OdP2H`uFEG@sIv?V^?^L2Gdvh?la zHU1`W0;BHH^75Jqyt0^^k^B@TJ6?xnAJ3cg+OoyzqOGHYj$IDtOrDRJ+xgb11^r`a z^)N>&0r1C`j|WC-H<0P3C;>g0)4H0qp9oI0KX2*}sOixFY;|j+F&71Hbva`*5k*kV zq%8OCCUXiUu&v@{1eOY{4VEP_(*UDTVGyGR@LHm8%50FSd0p3wLWa0;;LS0R;U_-l zdpnnM9cZx~M{4I<{3~h7j*-Yp zk?ol3y|0TWf#l1N1iQuYz^rBm>N6P3fKXlBxjNktUyxUWNVxokvo!i#&??Wqw!x)I zm2SGq+Sa;0?R8}$(6x8|;F(iV&xz?O=Ih z!i9?*tYSQW-TwL52oIXVvlDS15dS53d+~jjQ|}m?ZkTrimSY-mUKh$Yp5@jy80w{V z;D*?d6&PEe-=uMl>|d?D^6$#Yq>(xjc%QSYSM%$aFOS9z-aOxMERn{hLVh)_s=Atu zk`=x`HXe^c6}ObrjkRRA=&FSQ1@_0x%{$XX6hexa@#pa2$th|0j8g0EIjCm1-)_~> zI+PNnQS5kr!bzz=Eh1=X7?XiIIBm^;?F`Q0zuUr^`fK)IxV?YMEBCQ9t#tpC>X=?c z(c~D+u(HoPhh0A>n25+k?7Ov5pl2;Ts0GK^s4+Zhj0je$-g|bM%Hb%XO1iCRFQN;+ zlO9*Gds>mJaFAD{ijMx~E670%$8G0K!|NRf>)m@^Qa*^gDKQg44qTehixZn9jiE8Y z`Scji4L6mQ*duU&_<%$nhpa-optVr0@IsN)gYAG1bspa79R2%i4`eT3Cj3q(V+S*lGOn)8nP)&)z*A= z5mpKy?R$5G@GjO-9p&|DG-ukt$MF{f#;3qdwr9(VH>Ia8U_#@Bfa^bq&qrfWEPl;^ zJ4}tlqg=(UD1|%MRqt7e|G0aF@m?>^k)EkNA|WNkfzlpl5Q4B)s4qPy5^l&&2P~`B zu)u$4{NR=Fxk$#SH)}S3D%||hZJFEH)=yw*{Ij%Gtkc_XsSV2)Iph?c@6$-CweCnrC1)04 z5d#bejqMeLN|3Dx+x3mpB7=bj-biMU_1q!NQ+W@`5m@GyL#AwQfm2_8#;+iu6_6hRVKglc{{n+zoO zxpk_3^6{5{AYM#4d)3wU!gFZjx+=V2-u)wo4?hFe+w#gi+i2WKmzTUOG&FSAdT#)+ zX97Lv10V+Ecm$ubaJ&49(6jKGegl+Qs&`5Wnu?c4UPxyuieu@|*}5XsUhwt)DcX}S zO5D#!9|W6t`DXIUb}t7+nWar!05Qgki%;)K#U9T>)eyb zcyLEV?ib)o(b+~OK+*}72lzlv=_+uO%iI^N@c5`GB`YCa*nQR?TiP1pJ)J(~TSU z3^#YkbJdc-7b*nIyc5WgS(5ip7Jq(t$*xm5Eeo1yANkzsG0eUIXihhdsjTS~eVIF5 z_zjqtXO7_O#diCP)C$K$-}4K!1uZ;a*=nnJDyn5UM>u~$c$4o_-q>NjY!7;L*Rl`E z;*I^g4Avf%<`c-$Xrv9eXAkx3-_d%V5;QeA>YnX6)4f>2=O12;YIcl~#_dbTmnfu} z+>O3a5|DOgnP+3s!e+Y7+rVf`JX^MBqA~P7fPzt0qGhF&b;}$mS~UhH}-Eg zV8Qo;hetC*hDx@jiz5I@tvGwBV7ihQQ1_M9dOZ7NJw@pE zmUo%URoLj(&}w$H?l5SjHGW(XR1$qIZqnbH9wT!7^h(9e@2@U*+P#EFTaznkz2-jV zoM?|jmzi0&b9Jv5QuA1j;)*DW%b0zA>JuW13(qFMpZVaPy%NKT$X9>_ktB&`EEf?y z@7B0;@!UNiF&WI#q#Z1<=OLl}eoZbqE*)M>`%pAguF!P(y~~`$VPwChZCEygtvgMCaqbhMT%c{dl#QbRBbztrYAIW-mB>|NlO*+j&x;MyW7&;5H?KZ;q>PJrvJhIuY$mB zky1l?DBRlS9_T<2mb3V#`yyoKeLT;uIAq<69gs~(Z30{S4n$X>7OIzVr)vci_@W;v zXHQ?)@=!a#UZ|P3X-kEpqR=gV1#Bx14$dc(?w7MgxI+25K!yr`YXiCdO@#k(F`cN063U!?xt$`y=Q)R_V3%8!1KW~gaSiaL>$>2~jCdJc0nS4Yd+gH@dAfuv0Jx^sj0UveV!nH}g!-T@I$aWZ;y3v>1JlJd z4slsY(=*%h{n){$pkjW7mapZ?P#r7=4Dk_JLH8BrC)Qe`0PY&Q@C>Jrq|@aWid)0W z`k)D<`xFHU_r_I1Tn!L7PepJG@DWQggB9R9MB3;?x+`_I!yI8Hr zc@Hp~qR=~hNBN#W_h%_7DV6E#ex)Oi%+1=r;AH*z{_IWB0K7dnT?O;8JBRBJeBL%$ zqKqb)7P3)t=%%LvGy?nul$yJs`U3h(4toX$_5E8>dZE_EDHdrIv?E83#B@FY)s8~l z7j@77jzmvnv!H4TMco7S4MFq|)IXKnTVMGYYytQyQ!pNp%1-N{z%f(O$cZ~5h0i&a z1FXW|{GOZU z0SJJDDH%rJ3ktwBzPf#)-hCJ5pD1fTM9I%J?k@O_QxQL}#tuMV@Lc!;yLEJO%D3FQ zSPES5VW@|9;U~abfm|*sznB<4dT1nl9djPCwcv&u#~B*e2(y&`pIC3L_ODim#spOc zu@yIU6{)Utr9psA;#ssXx@_e4%gk&1Z(dnB)Yv&(WU#Vl%af{F^THCs78G51LFk(OH(p4^{p%||3$H3+F{uCB9}X)Fwn{*&=5Rx&9XYp$5OX6Zs`hD z&HVCRKHL%dPOWg@he@#C(c;Qp=NBj*db_*Bciq?GY4QUl+!mXgyWq;sJq4R@^zYiw zvSfNpqN1imSAhMS=9HDztYJv*=+V)`ZSH}aJHx-q9J$MVo8Qhl_zb7XBas)<;ciQN zXu-+3UG|HT>xC}K4*n^I0w)w0m0`SxwLjW!v-|eyBn$hQ-OOMdY?be}Dz}7}wZpl< zg8Z^qh#Mq63G{<1d)ijeZVv=N!cy+J>)=%6(QCbvV0*H9yuuUaO(`qgdp`eu1xkFq^y@6KezI+2kX#zS)F8D(5{%ts$$NPO@MJ7l z%|mRqm>EO>hHXK9xQ?5kwwi*0eINj{L;0;hb#WJ;IfeYE5)PTSo=3AjazKNpI%_J8>|HX z11N{fV4;ZX0Ebx~C|3&h_M?L}?c6yj2&#QymJ!cz<^Ag;<<_sR)s@6oumnlszX5x((1X z1OG?)kiCuqBz*~-Z_9&#m53PfqKEADJMqU$%V2dzlla>Azdr6-{2d_TE;OD;zi_p= zXdPjf)`#Rtt6i;`8vR_g!hek}e9QpdeH_vFtV`QYR~H1!P%uaAw5R{#qW2@D0P zQoecfHP-+9I&|jF|5oV(TO^(y5_wbv@c8?nD;de~_K9aaKLv9BUGYEeW+wjkc=oN)1Sx$Zn2=5$BWVcm@mC6ze3_~OJ?y`CCwjN2T4QtG{^3x zUc=E%6eTMwyUFZ5FO@x<*s~XCVk~-a{1(~b=0TjNaHOJWLJ=G6u{a(oKE_wwqW}K4iZ~Q<-v|KAsySc z_1W=eU@;&OHN`eywY_nJ7q~W!|J-hXYxu!CGm#c8p%)D0U#+F5lS=KsTBZO)51jc& z#i{m0r&XN$dVxxZaF@Ydw3>K?5h8z#;0!bj3GgIx`GOPyya~2{_2x~n)F30xQmaGr zY(H;H2&n27itv=*y7et|YbeFK9ZgEFiP_oc8yc#vmU#$+07pzElyld*;+BN24=diX zy5jZs6)!Uo$teV!g%mwi-G#{Z78*+d(99!xA_`?FU=^^WJa?CgibhfKK{7Y6h`48~ zF>03|+_d~+{)y#w%GfByub=r^d{VM_PE0nrqHE{oBT=Yh3)J{9c9k3Qyfg4Jv^4XJ zH%H|8V#cWk`NW8uBXkD5#lP__L11>u=jzWK$#=gLxPg5*FoMopUrRj@iFv+ z0MNoT`-}qtq8fYm24~F=-cQ;OQtlfmJP_c`1PF~Tf4oQEarJYg)x;f3YAez@bq|cS z<<@9vJ;&78gOY1gbx_fy+_2rA`=+d4Q;18g3SeG|Fr^rNiPNw#}svCMW>htzhL^B5k z+VD6I_g`=-#owJXTi=J&_4XxsL55Zz0~7{_Os1!RS#bFOqjV#GdHy^~1qqNZaQwct z56DZebD0O!E9F}+KucA39(atJy9mT!P~ANTq)-efP~voL;ntR~fu>?tvhhpB-%ttQ z<-ld07nv~PY@lF^seJNlWQ2T@tO&lx(APlwpnko!EkmwBPB>Jm7y)&8`TY5{{@5^7 zTYur44G~&>QpXmxPu+j5fcoJMlyrxOY@l;{7w7$Na=)8+1$RKf13*N$32xTx%uLX! zl{mzqfVn5qlEv#pXIR{rT+$ixRG~->9o^z~@oF$Ed5(oC-92!7akx&nO?ZuK&&RHe z{cW+YU+X$$HrT7P+)WK1U(z4j^M2-sn%$zJu_bPcTA!JUhUtwco7{dnS=8AwdF6vX zB<3;grNN`ibS;59|9kH2*`;?m_lok&sOQ_VC~|+LEuEbrXbG?ax52e(4c0)t_5xLk z;&}Ri%?1v^5n-+X1MwN%5bt;4`FEasxja~t!aW{pp!IMy^QObSoCdflr=v?$XhwNY z0ZHr{QKN?yY;O;h@CnKJ&CBm5dLlSyz+O=ZH#mHUDKMmtmO}=L8Ylw)dAR0E=vS?{ ztiZ7rSB>~$>Wb!iKrZzkf>abHUwB{a8s5)(DBQcO#Xs=*ujy#MPOG+10XwcBIXVmH z>&m0ufmXDujHa!O(c@X7J+gK)TGe~P6}l&QbSVB~V?oNapIV>!88M6hErz*8`yNeI z<=`<|hG>GQ4Z}$Ryj5uslMv^Zhn{SGh%N&uA7y58H58ch!HQTr#OJ-QL>GYY=x7cq zaa-9v)$<3B9b53EYMpvW0QyS&IiT{+5=~sVh{foY?+h~R21bypqE-9paGhnV1P)rwZ`Vi5Tgh` zy85Ql@wQw2Y>6R?beQ^%q4XtsN@0X%VB2{j=nHU(B!AEB}<8y2WfFv8@z zv!;g1C?80~%FO8JzruPVJsc~!FvczR;skX4Ocs^pM@MxYc6H|8^PX|TQDuiNFKJIh z7$L+OPI~q|2h##6vKoKq1iZ**s=S6a%-<$<%)hh~e~2RIdqDuBu~9}HM_%}*BTr9< z?-!D)@D4Yo(z+iD{VRsKC`_oK=f*l4i4@V=)Q-Q!Zr zGIo-oR4OLU@wHcClCZLJLVS&DIC6&FaR(3=y)AJQz70n;P4f6(16)qj^CjQg19d1@#%TiQIFf#_(ZgbAh?BMd&If9-iov<(?~iF7l!FKT zPn0wr7y*oPghhk~Xfh5C8%@YF&s4mwRGZv?J?6pE4LvMhOj$S=ElcP!V%^?^TS7wP z^iwSJaeikPI++ML*>Dc*L^{Xrx&L^^J!-p3lzy(}p_MOD-vJC0;@p6zg=|!uDp7a% zfchvh*?}X@l$Sc5I1JkJlWODAo0sfr*E@U|JFs<0tS>R9fnF}eM270z?8NE|x1hXt zi}>~*rQ7slbmH-alip3C5gu5ifOKR4H-ru_CFMv`7*53~2*g)6?Q7G}IpwqA!bV2& z=l-!+oSsRI5l|3`6g}!hQ;uh1Pc?8t`7SWuo;|sDtqugdQeylPsreAfN5)A3fw88p#!CC|T&*(vVGFgYGT{L- z*RfQ=rxrf?xVSYRm&6uV?7!DBqCUqnQzbRFa`yGA9ClsrVC4?j`MXlDygn+e=<)lN zkfFlRHs+kn*AhQuDSO_^5{4@DI}=;Zu%tcO7x5M|0K^q+0>BswQ2h^l+PAUP3HpE~ z^I1=9jc(tv@W>1Kj{055obWF)@olgcIkag2k@L(H*wD%StE6N~3P09kE&xXOnr3I3 z+l_-hH_q5xk77{sKovQD$M6j>4mSK-(qhz}1Ljh94oE=BhK9uR{$LTn452n9`ajF9 zMA}L^4B(iFKln|8k-*-J`!=d{4}~W1T+%)n*hBcuo0mxLLX3KDQ~F@w(st6raMhCS z?(WJ!F7)&w;=E}-df>qEZ1M6l%!=7YVMKohFb15UZ~5u#4i~{XQPfKBDLddBs{y~j zVu3Z+9{>otjyyhqwtapTdRwpJ(826;XNH*hLH)B-o9=pqm8NI{4$CzucL6ULL`0timl_9Ws`pkicYW`fwy@uHyah$2*L1CWp z>|pM&*e9=oX8NGW>&Wa%SlNmGT-SND9z1-oSNHH?ZO0`_S2+c@0vTors$kQwol{=` z>u9Y`T*ejf8Y&`Ci*fp?T*Sc#_#W^TqoF~y#TtY#-^jY@*fG{*iD^p5g z5qeNaVUTQYRt_Itro9#7CEqV+HO`;k_N}h2xVO(4`wpfHu+@(YhA)xmt?@eD2D=o} zoH#lvV>Wg!SWAC>Jv@AK*YG*KSm35-kWw#XglL7gNrveTR?3>5B5daEBa7Au%l!Z_ zE$bR0uY(wEL;dA>oV(gCX4cSYw-8vmt>bD-L9*L$hi+z-4$Tj${MPDGWnDu$CtJbO z#;-^b-&2!~jxX;j9t!^#2$T3X$nbvyVg5n@v^^L8I(T)j{k7KO*RGkY@Wq2)tIPqt z+DT7DUD8a~X&gQ*&$xZRjU>wmDcY`L)FNS4G>W(Je7`FAWsCzE#<2%$^Z%>C`5w`A z83zy9|A4p@LUt}HZM-8I8?HKiq)>Q0l{Q?l4G-{Umney5nPzfxT% zdsF4h0tE96GaMI?tZ3Zlc3BPGKN3Vyx*aJ<8%)Dg{jn05KmZBNT}$An41f_cziejt zC`~a{T$?EU)I0NLi_eA#Lng4pJ+Fyw?k7&|@0U@epWdK?_pm~c%nI`Px;ue2Xd1$> z1RO(C>?N?7?;^8$^fuJ@3(CcF0yFFh9USP?8kvoT#bqfmbmB2Oua%c zMl!(=1l^S+E&Fux$BTqh?pUZFyA*ILoInGKAepV?`WcZx+F}o|o(w0qY6yK1T8Pq3 z>Sf7&nNL8x2+sB&P0)L8=CZBk2pZ;b|2Edp}^ zS(NDi*XRCUz2>j6{_!4*($_+MM?NS}a?f9zs_5ROe=(%M{~vy>Yw2C4s5}1$DC69t zT!5AhE1dohP{z6YpN7o6Q00g+G2;Z_a-cf3t_U$vIJA*QAa;)&fJ`F&ilP7za#1AM zg#d&k3LRj4kj(l&?_q-Xcn+4V^hgT%QEG#;ATVg#CeJY@3Yb}MS{0~`G=RRFps6Ko ziz(Z|77bWEA{r!7GX!=;72?_thZ_p6@r#3gWqbl73VFfb?w^4dx&?SnA(2|LC{jmL ztql$85FX3Tt-aOKH&z!}NXDf#V3>l_D-PZ>Mwzo5BMIJlUyLPY3YwU|DV~Pcfw{&0 zK-h@zmk}(c7l69)l3a$(4juck%96thP^ABSCo(R7JdU=$mY@}TQyM}`$ZfCpi$H2j z;}4%394EhCy7q3r_djg)wgsF)6@TXAAq2O^u9sDWgpx>*VT8TdG}WO%`2hKZ_Icj+ zeh+I9D+MT#sF4GLOR_JM_X^A@khvlE9O~!`nSB`?Bygw@U%v$>}}BAk-Pc=|u!jB~%SCu9TWK6z_MC0E3F3@w1}-xTR1q$~94 zq+`x8tgD{PBas_(>Gz(u$j#+uxGc|OM8bcsfpY*XOOuA3n3z3D+9R@LK@pwc`tKGF zh-t;c;m;urPbv2&j)5K>wu*>55PjkpspN2)9-?rz*c=i2cwb>V zz(d>;Gt(W@_B4h(L|xlKEYRN{MHpS{N7wkPU7}odxM!rfOL^OnfPesrFhUyCo7GPw z9%n_Wc`747yOzd}UKUu;_fHy|P3cdZZh(`0WVP9Dlc$gJ z99PTL{WWW>@%+#<1lqf>YH~s7v(IreMkXZ9d_;~4o;YjzR~`A7nGu~?Ug$X2?Cr;C zo5=1pHQA_<1s>NzD?6(`Xrz(E{)w7gP4EDiEAwM21LET1(mqWP{{%Gb8aXyrRvP#r z)r>^)!B@wJ$@BL&dCP8#?rggq`6L(SUJo+EC6Eb2+D~Q8&P=Z1YW8VVny}97-AekX z?Gee##=j0FLrQmJ-9N9a8urRN-alme+WKjxtJlPA# zoKMJ|asMkQ4}53=C3w{QL;w6k{CKx+@u7LE>$ud z1d>=fQ9*dzld@M!BSgvFMluz*WS5wg^5zi+01em)cnh6`e7-(V=MT^3kx?PO9$#yZ z48(@y<3G9M;Jz!jjL`!6!HF>p!M;OIdYZ_6IoT@m^3x@IGw|GIBQ-j@-LUKSJKR}Z@gmwI>Q|#t6|d4J!`vJ()1&wdmR{6)!#0Zk2P^pu9L=IFV0+= z{AE8sA=iD0mO<{@PwnbOeu6V>-u8EmtjE5oi=Y#Ycic_~1(k^;2gAEV*|9Am@;h3d zn5(@Fnl%iY@RR@g@5JJgT$@WjD~=5gR8rKu*@ob{{+T^7ZzlC~Jp1=~R6tsO+uN37 zVW0QzoXT+-8g|9v}VBhp$ewol%Pww7_YYIMpv zedA<@G1oWM;phavr_$5XB@oqnXZDnBvrn&i2X53XhB58gyFwT5f*L858>;ce*4#lEE*VoEh_OvdrN!lV(+Iz1tk*!is#b5i;?5d48$F_+6O!ih=6@d|YP{zB- zbCJy#k+<5ze4AX?L|v3|F)kc$7GWCsB_6n_aYnD#t9GYE)?x4YX@2^R-M@q9lJWK zR59gkH%^H+$T3^J+~E4tyh(L_ly?&gzJL^WR)d@k({tnV9E+z$#i?w5jhNZ<{oB_n zOm{Z?z;lufwb>YOY9QrtTd9}dv&BuStNl_lL-bm1y3}pP;JdSUMpuo+A+y~mU1#;s zDDDctAIhgyJC}u$Hk_%Vi5wAKF;C=;kXBKF`gL{s6N<82Kg~X_4Em~B`?Fp`R-H^t zrss>9B{|tb%pcEJX?b&gcGWV|^e`6fPmN5eC3Rp02& z4SAj^x$nr(@AUI7R4`^o`Z`;Aip$i)w5F^#E7ccLM8JN?jPYEFiLx?DwzIi2Y+7~YWbO3tpC7amWkN>m zzsR_m{tz+bT-0b`Fn2yWwrX#nP0lzQQM0K{4eY`(zcoIy?=3QAKnvfXnTO)rv)fMB zPK;H4-#BBRZMlIbe;GC9luuXd@g52AF7h@`;&z!XS}0%})Y<9v&p!+9E0p}`ce`e* zDD9U&)=)g~vDd-$XRD#Mp7^YU`A5$|K`Q&DrNHCp!6VbF?@ZM2MD-?ibgrGS{lrQd zSz|wL;Z+J;@NwJKQtp}r+uwSXtpf@7iW9D+v~;*}*#0aznO+f7vAL|q|Cqp#Q_o;* z+H_w`b6}4i5>-wFce*jKM<0*GSS?dzyN%DwK{$q*~hls(H!=(m_j>o3HGuBcvz8Ver78=_s4@h z_>L}V$gE_a4K(*o4_b5LQ`!lf=H={B_ZWJ|?zqCIB;~->Xys+-{Nl3EODo1F=GvWn zs-N`V%)V~)CHFjMa`_eE@%Zzf4832|XIY+AmFE4Nxb4rh(SPAg<}xzz+52aVoUdQL z<7&WqnY!vy=^3O}NRvCD<~lY;xyLjt8$CSm;=r+D4d+Zgr=x>EMxQoJ%T?fl@K}4e zxtp2tvh*i^DciaoX#K)6JGYL#oY-X-dXz{8f)u*hHU|l5^v+J#8JDn6dpk}F+mGjG z&-!4t5A^BJ3XV0MeH)9lg;8S^9-|dM=~0CBPX8_2dCiB-x%x~aS7w|?lJy|Q8_YdB zX4?HzeFr9Z!4uJ=lZ>6|_jc`|b~lTzQt7(6K^ zTMWhv3#HO~bPfAKRQdFvbD!qf)-+k-^j0zP3wgrJWg#q&U`<$ z(|38-?$1_>8`Sg~S-)XUp6PgVc|@Y7yS*!dtlqOjJu6VfFbcPJGb>VagO=rCL9V+| zipR~q$r#u62IshSZcDfK`!Ju|dyi)2a4x3vLt%r}NyeeF`;*hxdq!M)S=6y!?_=}f z7#?~(+8Sa}e)VCukCEMD=H#pGb+{jYcCu0;u7Hi+!#*05{8sFL+-zL|Y z8Ec~OLuXpVa(c3J6P9rDwe)9als?BysJr&v6xVjI%>H!ISlH^zXDgi}Us}Aq?c@6uI^f*U#xpEwco8dqtxn4}resxf|q*{7t4`pRxgEGA_2oeSN=!NqkAQ9&I`# zJ^|1jGU)cq6{44?roQXkCI|MZnX%krRG16@7qp6pJ9NR;esXKl{ueLr;UteVraAW` zwzJrzq<>`o;f2X|t>}s^b9jeEy$Zs~h%@wW<$p!;B{}D(w`;CZ1x&SRT@0~w(u2Ce z@ws+A2pCvAJFs}ofgb3X$YF`}!vU)%mSI7owO*KWOvzc#42sNhBSu&7<{Rs@?`$g5 zBUPwIi=aAMYA6Sg><{|#uH0gt4Mn6mI5vwhG(<&L==~Ms{11R&B}wdOTwKE@wg+zq zILa3H!XV9dN7CiMuH%Jsn9jR$oDFKsYYChX(S+P26k?C>G+Dva4roCb38o*-Ye8k% z!YNK|hu@+bw(oIeRN!;iz*cU?(?6(WYX^>|Ka%3eJu}X9-f+6&j)2~AP+Y;cjc~X- zuCLET8k-Sp^7_I#U}=Q{Oq(kPhLHteQJo1;5tgrLA&X<)%d@x{r~dRZeOC?@k$-X{ zF23csCQYtZOoQr-`x@0Hy@b2y(&#dL!O%w!=QpEk?N$7n_U`{WGWJN(zaB+T@KCM_ zmN{k7|8S4~8_@Ls7P$Qf02E!<;VUQ;R?I?`l$VE&rICdH z(|zVoHKP)C|AC!{vdd2k-c*@`MH#=N|J{I`iTU{_4(2q$@d)}#qKVya2fTUXjn=7U zl%*nk_sE$dJq<}#RlF6rwzuC0e0na+8HYpjcNy;3pWl)>oHITD{RCe+Mp4mTp1g8%n;hSM--K4OYz7kdvVKo{X`eHe zJT>W60rSJ|%u6X(D;yzoV%LxtY;IQSx=`9uaR70TI zF~I?pWJvI8Fj1uOq2<=GS~aMpkwH%sy3*1YYb`aQ|H1TcsUT8i2x;(Y`~fq4^^X#F zV!|CFU1o|1gLCi`#PKais=b_*rE-jKu>KnqJtVvW(jl14-dd-wflLTCIQv}U1OAgZ z0ZkZ`W~XX{EKE!y`g6Xv1AVoy#U=l9co_xC6JgJfL+G%dRd%vah$u<7eu%6?5<5kNImk6Ala;}g6KE969@J1`6H=?`Oz;@%J6spb__7DOc8V5T z)NW7Gas1*XGCX2b&pYRh{(D$B*}9uv2KiZ?W_CSB!S$kt3z{{5z;=9rE~)4~A_;>s zY266D5D2@XhGm1fGc~gdDMhpr^l4;l%tgeuT4jrdgQ5^7h44UnDjIzuLS2;ef;)qg&yg62xgafRG z8ox}EN>W1rL24CYXfkMWg9ztDR0^cs_t6?JVx#db&{CL-AepH=02`RCAI&EZN?!ac zEQ4d3s1?vsI{zOPjO?GXIWSPv_GUu$4=@5W@us;Tb70AfG1cHKAbRx3^3%0Nn-e+O z5s-8c5|)UvOUS^0mJTfKx#KG|Tn3$5tW?d&?lf9kb)h{5N$1jy@^CJ!wCU;x6c zIH5fW`zo-wjRVku5VJVsHbY6ca`;Cm`1;6?C<=awpqagRPS@C2(q@T-0-O_UtAcaN z#sx$fiRH;CAxw&Mvm|0Sd=OrP94heH7vOOSJCu62b)xWb_OSa}WmWm@#Aiz-_2h;!j7US1{RXN7zur z3{Ek`Ei%chf) zu~tnvcmgFSegNSm(adNeu(&ys<1t3Eu3(GtT%-{F;X|4l2f`H>ftH*xrno~xV2^nK z>uy#1dCX$Uzq-8+mU|HLFn^HM#>r{)smu74wHY28k0qY^=mh9hGG))QyljY$Frk8# z?4ywB2ZJvNNo7nfspuqf#%6clY2)~-SSDM>HJuq2BHJeu$u%AEulR_UVe>4&V+==x+AIQ`gaFgU>=9_Kf;DvXYOud&bC-pBV?hV(S)IQG@WnNrQubq99NH`iVSDzbXh`EOCFq48vTE zUEx+0-x5aKA*577mU#ldlqhA2ji|jWqBZAx+t{R;a55UD5GyxHv^aL`_Tzg(+F0aB zmJYo6)N;otW^A0FqOfl=7O@rh?1)_M?ESQkQ}am`(k3{g{Eu!_UL0PAX3VD#Y`d>; zBE*C35#%Q@6p-W|j3)$waNHjg4GR>19PT(Svwl7E$GUbhyP;+PPI-`5tWTy19XM%N zm=XVgS71*f3J;_#cqkC1n5yMUXruOCAE^NkPSoWM$; z8}qe~(aQ8P7pzB65Ws|S3Hp;=c&zb7tS+;V}T_0 z4!)O4C^3Vn!Hm#8e+bsS_sKd7TSV-&YE<8(RRs7T@rgrH3x`i#zp5j z*Wd%=Kcwzbi#Hb(s6U9fra{lQePiMoT{NC+Nhas$Nt;$VKbR{&|KB)Z+a)}tNM#L{ zKi&qWs%;2d{Azjm!&ijT|FYqP_k-{J!7=AQ2NNc0fUuV%kh|{h_ZPI@?HC?p!TP5j z7rQ$Dt!Q#;YFRt(RFPF`1E1b;u<3!f8~=+HUw(7EBT&*#{4mvt9J5oUOC$F`*43_o zfDA_Wt1uTJw#SomiU9iHW-R&09fTn02a^E=A3vQZ zU$KyXK?)hxKGIPJtS3_0gui$|FNREg0eDRRI&L87!pdJs&Quo6Auey5yGF?gs76*a z%0Mx&RIwJlhB4hSOHtBtB-N@s?}qCRb#3Dwc8Fk6E92!cU0|ZfnN2e44HF9TfTsTb zF$WwwU?6DjWYh%|oW$P4>X#om8Ir7W94jA&RQuKk2Q5C5x(GkR_8mEik^D3v0a-$q z(^rzWgK_n`j89Kj7xq#=#KFxa!d0;z0S`#j{Fp9SBLaZ)KY-5Ot8Z+QXy{QF*Mq4> zJqo2KML~l*_dk)x>WMB$s93U4|M_(!phkdgkD^dYqr#@PJ2@qb8$;!W(u|oR_Xwd> z+m0j{ZKyhP_Ur|e={O9M;6aG}V8a73)QUM5G_wK~LEt+B{c{{wa(*qu|L=`419%)% zv62xP>BwXN_b3?jk5yHEFvyV@W<)Tue-yNU^5i69ZBQ0#Vw*Y=ZxBQT`?p@f?YL^m zeALyg=oY5+kxZ)O*q6!kfaM=mp44fp zjJlxl+cA;WBPz*C3b>=vw@!x1tQ#5y|FZ_+ zj;NzHW{)hTnz3B6Qe-bgv<#Uui05qg3JMe#jVO~pp)h(-k`*ckn|quoUTYE0B+%gU zhSs?*3Zo9$59%h|KwR>?=>A;60PoSP)8hpHCx~`Wf3|RwhwAS& zYl0kSjji&)5~R&0Ky4&NGc^HBhB%dwj%loljQaXTlq-x;C*@Yi``*ewK~Y&~q>TFu z$3J35K0PGG#rSa)OJ|eM9v{VaMs%qsJMj`@Hi}p*umkVjvuDT2ldGv_98s?I^dd%y zBF`nl$oYq?kV|E{$e%vDzkV4rd4 z@oC)N=fXalt6QrFG6Yne-OI_|_Jj2=y}^0iZ=MObnr%@UIJQrYTRVf2`G$bO4aRU$ zlLM4N|6GV?WsJJ|i6|m;Bj#XuNC+d3v`h$~RywcLSkgT_J~4&BWZifd2+VHJDm1#T zVFcU?wD^pnCjRnlfgPfInA8)VJ@dsGzVy^J!|$!ho|Dc>CbptTUAY~*>>rUP58*;Q zB+NhcDj!e>9Mo?Bd0+p{;2;2-$V}(C-`ziASSU=h;GZVr1SoaPnP_oHH&BV@%eKZO zoGGSDPqndc8LT|-Y{C3IIKW#RcF%A2ovq%|@81HX6_7*Tub#@(mhvH;N{@^0KP|1d ziV`0aY=~NZyMN}>>b-CF@aJVi8#WhugBe5FR;rGbm|f_aut(}X4vD*TZ>YKa)AQqp zTt5~-jEq5?4@dwCe2y0nC`_#z#cW<@jE)dz$dwg*--L1te>E|Bg=q@(qI86wITO_9iSmUjST3@}`Xb03l#!kp^WXh=E1ALn z?uXac>gsA6fr)M6;SD_OD-8AGYK&jpQdDDc3b|aNudiQs^~y~}weZ;a#qW_3`xdrF zKb%*`O8-Gj+pBGlf6EkZoen)JG_Anv>4EeBabfyy0u6&_qq?llOnM)gzO={|=L&F& z^HZCqc?`T`Ptqo9XKJKgjn8DZPZF|ELOoKzp+SCL$N+)7eL$$TbZf@B8hStjz|8b=u(0s2;if)Shr5TK1w7R<2qkaup+Lu<1;IW zam3l06a_DfGDAIxg*34a6F{Emh_TP$KnR2djp=u?t-upO=>!jRrgQI3B=!KUk;@N2 zmMu!xlUt+BQh(+LTKhuc4ROV4l-_mbw5Fqm%QeQ{miV+o5sKaEXo}@+>0RCm_e3)f zrX(k$XB;OE;l%VmrwKkAv#Pw^el^m4aM`&2n#+seM(XPPe4F(({kD$=NS(3xMu8~vEwTZ zW%%)RN$4$v9Dux#ovc$e`Wko@4hg`kZmf~kVynfW^6Q&3JaDbSxT3f^!|qezNLNMn z9Ir{R{(A2h3$UZaDnB=!em3jZ^v1a){x&7|()GePdup@7OJT4rrYDVTLIyG0R}7t4 zpOE`)+odXy16f1X+YIhiMV|bUwW&upo(Qp$d(G*e&w_ zSdH~HVEO*x@H&wMR^dE>lkky5SNwDlmNYyEOnhWubx7Ig-2l=Be`P`IAc8uUBB;Hu z47UY4E}}%REum^#4aOK&}DX)%GmSm#!{I(a#DA1-=q_5m;gU?Z^|>C!Eq*`};a zAdZogMZCPEk31CnY!t$$NHFZ*%r}Uth>!>Rm4eerq=OUj@#7{F{2yo$vYJjjgsk7L zSlFc*%?K%q1Sk-62#yZaf@EI2XF;D_Ye*&cH!E$(ORtg3J zs7#;VKE&3=>00*wJr0`fs|-a~;e7IFYPMt49Rl!62n(<+nN8IU`E7kY96q=jzEW!> z2Tpt4%C=ZQ$oPcmVWwJUCx|nIX1)v)IRatZ(^Q@eO4M-9DtXjBURoPGre3}6)QhiL z-md)Mk}ax}ZDz#&mvQVtKKYk`zdxKoRRtI8|No4xFA>}h3klO4q5atTn zo#;N)>Rx)snb6wTu8;zF8vV!kipqGzJzLx&n}-jhf6a+aPj4@5W>^2spQdPr4R$h{v0%G8%Nd;m)+ z*F;=&*Uf2)6DFIrZ^pyuF z`v@v)W_x31XqlA7b8Pi6Nt`+JOr@Hn|26OryMvi87@GZps2O>~$uL042-;GL#~85G;lWvZZtuqt7kaHKub5b!tq3I^_K7jyc>KaojeV~mfB8Mt zRA=|YSmy-9JPYqDM1r(toj0Z~_j7vV??D&CRSCI+neo@n)hv3i$GdWEbk97RZ{)4! zF?P{o`pEQyNW(R`C?B-l!4L{$F)qr(0D|5K3vAg0AK1Oh3=6LdS;y|vXsuvgafft( zfKv}!d7|zHt+pJ|;T2N3(;Fn{LE4B&1{Ou&Swv{qL1Ksl0=$?I$95g;FVcw1oDSdd z88|&=mQr($s1`#wxXbr85Ozx74T>hsn%{lo zexK*}$MdfBuJx|mk_mLr^_9}Taw-0FWr+A zVtN8M#9p7WW+kTM-dEKl`?+)#o z%v4pw_>P%*rG**Ir(J!9)sgD#Ej(9R%?^}7do^Y>S#@KmvfgwqcIZ=E(v-YIbRV=H zmaTMV`ak%pD((sT%0Pyfk)e0^j$q`z?*4$DH$JUXJByXoR~<%SD8fqn#&zdOyr}Pwk3*A{gTJvylwt<;6@nU&Y(x%MO?qR@NorAi&O?tso1+Db9k%I0qQ#k@?jrPT6UUKHX~?BfgV; z9)PFsN)KlDtj8L1PD5h#=*QG(M%Lmv@ATc5Bt81iOm-4>-8EAJt_)i`iHyrbw#NTd z-DU9)?hAu@P?WXDq*Sx+a7xdOOs!Soa2e93jLRtg>*zoMwETmzq|j@J+Jxx_%r{G;?-Fjc@Jc&$9xp_ zF0mM~L0H$b4 zD1qRb1bfjiaeHWthi+hb3wJCd^P`gcc(*7+jo02B$7I+egF60B?PO0^9#&T z#ek*zSD6|aHTHU!>tRX9n%nht8F6YN4q$SRpFRcd3LB=%hc+R`T6Qemv;6X z`C;a-m1m=hU*qpz`TstQ`c?ko+wI+rXqsKzDtUE(PlUd_zA<`$=ut$Y$XA(dN4dUWbE9a*TBfPcaVu(cV7q6vD&vr1i>S zf!GL+L8H{=i5V9@6wc;AbG61h+k``P&Zur>S07|3ds>wyzY|dt%a@NtLwdSpX5k#Y zZDw<#wy9X4#{pZcNs5#EI0~kA`L(T^U9ca1?{H?qzdxd zZT`i;pJNZuRnER$H9l>(^hRPobbXq*aqoUQ4{n4^G8Bq0gq@@`eaj-ar!GAlQ{UQV zcKIb5T+9tfC3ytch6-jYC>t8QcL|LH1khqTv}4O}Yi3jwuy>h`+=1>g#Sv#ceHSg1#Y)4qj*pnSuO4!xH~vlwie(_v3ZU`45XFPVJ44Z!>0MIILm+ zWQby%+&a$zF2(%EnOt)aVKVXUmU3+AJX5IKe*&{eFV7| zD3#-$KiB@m#+?{As}(Zt0&BYfVV6L@lcVq=FGl6nai9-yk_#%nZW)qguBoYcxO%6i z8s|y^*LNlXj6~BgGL<=;)ehPih!M}2rc-`DA`rf>7m-YB zpHkOlna&Dfg@L2UM-Ru&@9+W(d)MSS*>VF6R5SX8yVafJ#08xUv>`W4QnXM-`68x2+GuImZ9I*A(?g?P?J zAAWdQt(#1`a<{G4+P-XMjRO0#Kc2+c+%<1>So~xTCkO;Gt!;IPBm{!*cwNos9qHma z*?hyG)rRTex_rl1&i>CN7eBWc+sS69T_>BXZFIk0k!06RsTX`gYO*(<54i)K=(v=0|3`V(n>7;Pp$OZW6^W)<(E>Wv4ul*n{rkoG8DM+rmw`I zAF~U9=kLimV2lm39OQK1;xXUzV^mOp_u!%tbNp3|?0vqOc}L1>IsE&XQKaL}qpYqH(OU;L1* znK`i*#Ms|h(%?X>OvegdVz?X^0SfZX(Zuhc>hiEflNR4^!gHPn0Ic7lm?xMR_E%w) z2Lo{0uw%_V2zT+5mu@IRw*YXJ_U+pikzXO{|5E}5jnTAXBFQ&^xvuc8^J;3^R}u~p zP&bR5G_0ivG~gmP4!P)HUvT<9ri)3S0fk8?*|FUJ@nR>@kj1{Wd05S891E2dEXx6G z_wqugMnp@rZGZWl9L9BI^3rI--J3EO8X#R_@%x|C2F1{sYy;y4*Yc2`%A{Rpd@zK)LupmH5}_BY>fa>{<+7RExk+0|h@tG)(td6k*7K^orYz_E=fbhGQ^|1ib-L zxdF&=<5H3#NNDy0(G4l7P&MFrAmy><{x{KAvMl$|5|~7SLx>)NVa%H?!MFa325>m< zyB(34l$FWK=!=F4Ozwp}Ax1)|p#i+KxW+5R*9kL`a zbP&C;P{@yGAfXr3P1mZ*%Dq6fpB%Ukeq%XL8Prqo$Ht+O{S>wbIHqf0i??w{mTrZ& zj}|$SqPlv<94XJ_Raoi5Usq`7QkeoWO?}SRUvg(4Q<+JGi`Cr9F_^xS*bWWmUQQ zqjEjsTU9quPrvwM^CxLzKq)i+F!Otkv7t)9_j>!S-QrAZ?>8w3RuL8y6SI}+Gs&Dd zdW>XsqN3&Dp?si*a@G1t@_M8`x({YEQTDLVxQYNJ%rC?00=yhF1BHKzB{d$45j?rv zatS47lE+Q`1_)nt!hqo{E_uk7t{r$9vAc^1*~T)213Kv`SjIvkBoz!F5HM7vJV9I) zJSs494l5#jZ&y!yA5&=EfJp6SrWefprB^ZG(AUACgFGoB5KDjRH1aK2Y`&?(wh zApV`7<{pjQ_3QSnQ@)-0BJ}JCVSTLcutQ8oys*N<|HX!L`{R#Oj}`1%6{n_2I%G=m z27w)?t8P{TSrJ=Z=~o$k>(*m!4xj!C?g7Nl(o@(dD_=i(`jozxwb`e&t{hG%=QH3F z=kMW3l&|?GwbXe@r0$~FEbYgg96+sX(V@M@r!$+nvw5m>5?W5mzfR2PRbF2$YP0W9rc(M+A>gGo?BA>2q>%vC6fSTet0AeL4Gv`)IJ=GS%jB44x3u5-#+w zY{#ZNK*}YA%i=ccJHTkq8LC~sWDTN4K?$(KH|kOxiPhC~=I`hl?Cq`T&x94tiAv)` zhaQ$k0NcUNOs6{k>3>D6zB}a8y1WR$5Juah&cg8wbD)h%k$xJQPLna{D!}^5hYzYI zlxc)YDKwO&@7YZM1mxY=6Gx}{)#lu}U$UlQ37r$N%O%)0U56O5 zCqd4@;(lqogx@$Qsjn=4Ff)+uap}@(rA@Eh?*7OsKx|FT+Pr*dUnw7AMuU|Si(p;3 zKwD~s_{e$yo}S$MEwcqfnQ1rwm$&-The!>aDuAuT(uYe9!I=|rmRn$Yh2LC0#xSNv zd}ZR_?93MkOZ^F!67@ClU8miECHZrzYZ^w?PkJup9)rs|8r?6wk||-a$CLH5`NO;{ zjp8#Ma$B}g#H5OqVF@h2918*0{@5F-^jZw~6P&R~oMx~_!V@vzlCQ;JzWrl=zidwb z+&T_k{=M=l*8QxR1?MhYh;+UsCxXX)LqudfN%Fi{2aN2Z!s}0dNuAU)NW!*H^7q~2 z>d7V6Txf{zR&%O{jbCw*oai2%GOx3W9}GU00sAs-v<%Pi-A2gPNk-83CHh-E8$Skn z^E{1JJ*ipqhiZp@`IW_%^>HKF(^zb^tGK{tza-iClwg5AreU~`OV6y+8FM~C(ji;f zRdBp@JHuSrDg`pxO4T9{cG2K!=5~N@2lTGzK0|%^+GI#FB~p$fzu@j~?@fZt?MKrm zEmw&gA-3}PD8Bp-;cEKA0{7z>?6KlGk_1us-49+oTvE|zzeLVqJ6CqF7D82z!^7#zY^q)-f|KxI=Eb+k6Km>s!YMY(b^l8?sz-3(`kn64g^lMyz zb)kE@(hYDZd3xk?-0dB&y}GcKCz-yX?PMBQc#3N996{9=cT6(>Ib?nh#aH2VfEGwJNt?_pj z-1jEW-|XB&#YBXRiif!lrYt=G5a+Iw*y=zQwWSWr$a(ZGy{v-(+UP8eF7!ClXfY-9t_ga z{8}0|{?xGKzt8i?i%}x{_7XEO1TJw9Yi3bVACLjSdZ@7#55q-M*C&m;D54gEMb%Ys zVkom{5Ff#ZM|e=$&w325H&EaaDJCN7UQ76MD!oqTp3)Dx`j55dOD^YDuqoUI>#^7` zW-8@>NrhOC5O%&RB5cx)nLL$YSNeFw*(v{Xb93{VJGToX{Deb#N2OF$Oh;Sc-3bWg zR)S$EZxE1+vgtjuAv%{aPrhh_S~<)BJYYH~s0Eg^{XO3y=tbBM$at~7rrw?a3n)Hx z3uFCrWQo|d2azm1iz;@4`57$bvU!qh2$pVW?CRSb$x7fg> zY%h9|st`2?0&h@nLXQDRlJVY$o#8)u{GLtnPocbm{taff!!Yk6JQ#=K(j`~CGS_KY z9+JIHqm& z8QYuYk;jymQ11I-c}O@B7{WyfFU;tBswQv?u1?d?Gy4r7T!dpE8w+E1Z@3_r(dz9T zo7jp)P$gpj!1jaRpdLx04Jt{<5PcziUV^MFNyy;y&bQELzz>)CzT37%Fb%pK5VF`< zKz<1-D6ExjapHgkeHba46=5ElG5>j=SCT9rvkUGAR?&Ldl-#fkvofaG8I4- zW@M9iKB;gnhk(ktLC^w32(&dub(zFY4inyiaA$&2(o-iSG)rchBy>uJC~FJ8Tb-q6 z2@}v)Grs=opLi?6E|`d-{^1j>eE*!$@A}|GlU*t4zEN!h7AN{l$_FhLjLQ(r1&EvE zrvPG8jty0VrUL36IAHRz&)}k^exF>Ul|b_Y6jZ#k+$IkW5~v6MiTTJ#vc4=rTC)7Z zeS2TzdoOrFmjv9dthz46RHc8~d~SN22>ihaLmdfXm2j{yQCXpof+9;3Clv2X5%z`9 zH!bhfWy@foyyDw6MHAF?K$sw=Jy2+#Y6#`u^4kYeTw5hVB$ zTicf>s$r2oT*_Yx&VkgQC^$pUMf1Z+4P{F|B;vTAapw{o7T0ID?i1o!14#=0?e3W& z4Rc85;s1oBPia_hTt&MMZ`%jlrFK4k8t49o6Qb75f)9cNBn_P?bW#1{K6!o2^O+ZN z9N`K}^bB|tg2U)s%z`496)79B>}@ggtwx zm*yb9yu0fyVxf5|@TkKx|d7LVDst{m>Qv(!vYdG~8q zs>xV^V6^k)%ahMzi(dD>hQW8R;U_&&kr00q)6~MXY%~TarlqUh#?B<=p6nZ+cIf*y z01`bqnn_*n6-G@^F_Q50!npyvQmX|u=`6$X!$}gK=$+j|#*LhE=xeVGe+}NtX6M<| z<`!0IUVDdDxqSI@;xGsqD#=3PFfVf4XF~eVm#5+bZ@zfJ+*ruHP8N&B3Wb-V0sYE% zq3`V73lM?G!7+OC3eHJa`xg*L7VY~OSSB5XItB9uxMNdcD4`B zmBG>TIW`9#DYEO0uq9X?WSFbXh0ExUC6Y9=zfw$yG5?3>(*I$6=>KHRBo|Dfal`Qu z2`1bkSn2;NMbB4Vp=V;hzf^!%`2V%eGQt6PL02EBYY(XSNm3nDV(jeKzBNCM>|ImlpQY_767zvMHmH+#6 zX~+7b)R|O6X>qOLL!4G+O+~Nq?PHn%eI@?;>;>;+2Ec{WX|Bzo2GU`ZWYP^{F!HPZ z5%fT4;$bYaC}G@Ye{wT zp{0UfIA+*>O7GnHu5-A|1Fu8G;i`59Rb^3GXxLDz<7egLGYabklHn77bakqi93K=e zF*?L~0+tRIaBoIWjg9OB=9%HTM@D`yRY7!$P?=CZkoS;AN7b%996upj2PUop;y1vq z?z_FqTN|`(0Mh?DUYeyfTE5~AEt7~u*^ZkQF97NOI~Y707v_dzbnIWh=A+kXVpeZERN%d)L5S9xf>>z*HhuxMCRvIDZL+s;UF46V@^^ zj01nqQ`@-BZAUS!fWa2BN`VA~NDzu30H$X$a9u!15bpo+PdgObJxk9Js%0>9J#}5cZ?N*FicW zHEnZt`L#XRL`q1uB(@yf$54BRR$O!>!8BF~{$@jKTwq9stSA!wK}=@xAQCf7{l<;} zI0Pmq=!~!{(^Dvn;t)BHPG${R!>o(|L@YyNbbKwT;}~JM%~zl7zLD^6-fmW3f=d&n)<&q%(U;dU z$fc6Ilp=)*YQ3@n+yY?FaMr;QP8l;e&bf95%Tg2`duwWy>zY^$TmsHCNNYh1*ap+U z4co9R9*Kh13Nl!ov=;HqW~t?EtJs1gdmWV`C7SkfDV?(w`@o}pc>}hQwWAW>%=!6r z5yBDP^5`zpr_*UfFDvIdB1H0b2n_^lUf?T7tHxpQM|AW5we+~Cv2)Emi46;*qrjDx zLenzifu1z(OYq#rIgHe3GBV%*-M_O533EXUqM~&Rwb3N|gxr2)xtq(&eTx;&= zJlPm*rf>qF)7pC1^`&W>bb_8BT7@i=dWY|Vy~P`0(ZJr0>k6|Y>d+tMnb=jyeG8 z$z>Ma`3!&Mgwrj^`HSvT3%R$}XueDek0--ZWqB}tKW$Xx0DcfcXWlRR`=E4JL&!<9 zNUX;aXrZyF@KoIgW)1-MznLb4(k(iu7Uof#ktuTI2nWc`-+wwo_agrtxEcfWD!%Cr_;bVR>sYjzQ=aA^5LX}Gr+*=^kn1oeI|d%up)l?PnN=97CmYY zl%>#2p)##`BLQ&?T1-;sfC?hH*3hYejx#~7xtjNVwTQdD%d@fMK-g{uu^y@J4;%4+H1bBlcIu}KR&|oTw9x&Ah8#F4nVVb zUgKkMo_r=otbl)F{LKs`tRts6POY}){dTYU(b&>_TXf;)q1d?A22s16 z>Bu*11H^%boJ7IVJtJnSGWgp$0jGLe0cr`n2A8@XNv<26`U-qy=g8I}M1_%w8!I-e&`&Xj2mECtM}137 z^4c`f04w$&QWekpo8I;eej~?@6)F%sqsPq26DrBB9&%`KK>td{#d6|E_pC&7R)5r6 z4fk*1mGL1#N%X{*+)i+GV})>qz-KP!FhB+`ChGj-Z8@yo7>b?4`v~WwWFnTL>kIJ* z-3RfR$fB1D4V&?+;t1x)6uer3#ruUx-UHY8moa<=Dv?#sUy(zb@h>%7qa(Oc;md`8;`W zm{XnOraDsAqMH0XkaVK>_Yglj#RZ`;nZCbkLsVSS+Wc6xFF&2OkD8u5$%>l?*gEQj z3oSWZ)bFoH4(c|%UUl;PU{>dfk24rsAtKeNaoOcby~?){%Mgv~|1WMgrDNBnl?EJ= z>~e8?Z*)|cf(t;$3d%ak=s|3(q-o}xHxz{|iROdoGswR7=l3bwjLj+p%J|67kt!!~@dY&3Jpc7{*!Gzy zpvPn!_R`(Ig;3rK9@?WdN{26u&0Jx*^hG^L8g34$mgV86x`%T5UeebnH0V7lquYZJ zTY3t4@%^$5CtjJ+fwEFsedExNr++ha3ujt}P~D<5eYXL8lJH~g#wD^euxwL19tbL8 zIGhhX02mwqB7*B*U=wb<`+60vEbl-4^{a#Xk9nh!gyxMbFvufViA@t#)fT~zxL`#C z1dcaeEpwIhM*JonRRB2BYjPABub6zTcy|nFH3l|zojbxsoCzp5CO38^cC|j3nvyAf zTRt_DuR--k>Ho#nS8mCBOCmhpUB7E7FB@wtDgn&2A<2yleBf+vOpn5RA29zFC~wrU z0OFg_Av0R%(#_%ThQ;*5`m+h+6mje`ug?jLy*S+xPe4mgXuMV4 zN1+Dtog6xTJP@Q1bS>|!rQ-pZ4wfPeq-XRLc3!Nf5mVgzjF^Zv77?AKR)av4C=KuE zpio4eM66;<$J{YZ#d~lw77zbVMXI0!edw812<=*{#=%}4>TiVzz(wO-5hP^v8_R{S zo;KSiXSRRFEVi`5;E~IyT!Yu1xSCf?A-q+GKK~6?zTLjCM1*VcPV*%m6U;KaCli_e z`K0kJcvq|9ij3IQ<*g;VY_P8S|4U3F!mfj1LSDzc7iLn!i?lh-iEa~IQiTb-yN|+^ zzOlzNi0{-TMa_@vB+aEy;;EL!pf*M6gnwzhd zft(UJeQYEoY0Y1Mhq1s#hL^Up%p^y4kvzS}b)4h=HM+la_5V{Jrc8gC6(5VJJ3pT8 zySxt2JqCmkGoIO2eGN?$zMPd=^J-knuN^Wqm26w-q_g-5gqOqiZ8sW-ZQMFeGS`za zMF#JMx6Y{k0rJVV_Yl!Krbcl4DfUj&aGM~zBYx%5|LxUqzQ$q?X?~>jw)w367bCx? z`s)0ZwtQ5*AhQ+&_zt^J6#0Vw#FR6FHuSUK5vA1WG2G$UAw+fHTiWU0*Wfo0A8L@~Sp;!*`JCnx{=^IGLp3w% zSe;mA5P{MqlUl*v$E_Ky#JT+JXZYQbYb0We4_*N(5;EO|{Tw%SUfD`UUknxBmAGkc zgi$)`AxeY4%J-sy%(fL+c_yK)ab`AzE?j6+-qr1wn7c9ayd9-C zR0rnB14Dzz?G&pi85db;ci}=2q)lo~1XiO+rvs@Pr8>5eLeuEyRSo_MjS|;8BTUl~ z?^f{hpw4i4CBrHqGhQ6j+im%a!UzHUMn{dS$C$Glrgy$#wVmGvcakb|_eG}UljNPD4Y%dEFB$n_%Z(w;%p`pvmQzREH zNK#~1!UYsN5P?Otwf)V7KiOtl074_mF5D()bBISID&08TNeG-rQQDzI$l2;1k}e?1 ziR1fvWGj{RYnE zDC^1CHDX-)kEvufeSEZu+`p0G!a9_Qr2laU4!@q;Db33YGg=6@Q{h&fHAJ(P4k*Qd z9bsJ2|J**AW}J-V5a;GeZ_?+vy%az4{OF;?TDU7PQ1_q-y&$3;UiMj($&@_lz*FK} zmjh?SsSzu3@ef?e8?QSa-;APDEBsN}-DMOBFaSYt^u;?7j$3^Oa9Y2$Z|SXl*Lohf zOKgrUCOvSvG3%m_0o9H^-yu^8necCn#Q`nic{p6``kc;hng#;qzX6GiXOUM;!8`)$ z^rKsD-F|o8)>i*QE_Oz}6>Q_W2jn<&#T4(vN6Ux=e))N|_8eDbuBJKvT)LHX`2b(= z1h)M@+fOaYg10Cg1uqlf2l#k%jB<`QH*OUdzb2$jBcH@MJ zw7k|*Vi-)lMdP>baue(~!1UAVpoKtB5wNS`it7b$^~v1k)%hlp6<8RS_?Yard(krJ z^GrqXRxP>&HpoM-Ls*YaQZs3OgC>X1Ii{xZXqx9`QO$5)vz@@El)2?{}V99{?ey)ly! z0o!Kg)}K$MyY!~ls}Nr=ML~%Y&>m(TuYiUfcXLGeNtthU-ZRIo(~aTGxAN?D6+OTc zHI;Xe<3)s=_qC&=bLhFpjZ$esBEGyU?|7ay&-v!Xe8$UtkD^KTkEepK`AeA!ORD$R zVgAo_{)?e(jCFI&wjK74K3p;5HI`DFA4Ht_MRmVcp3=Dl%wHn`BM}((T0+fM7S0AX zNbKs}x@8Og;V{E7NAnQaTU%=l)L2W`oJ-I0n%e~_^Pe!&{dLisU7s9&DM`8Hw?2wg z;#Qd|KS+%+oOe9lZKM;dAvg_h5tBR+DuLCE6vUwtUZDeNmLIpU2Xo3dUFc_i?6Q9$ zKCNUTx^^d<<5X(eFi3n+k%Kt%3 zKu2KMJ?*3O;m5EZL&1oSJ1qYQ7G|m1b328!-@|XXC^`iKGDJX<{3F3u;rKXwaI<+cRcIOl_d#dR zK2@@8;|B6$DHe{c}=-H`{Y6`uOEnmy%*M zB1eReQm}?u3JV!{%mjMLwJ1HgrKmUNU_;RKXKk%OB4 z(L^*XoPfL#+}G4J6v?@43qj~Qp7id;str*zeAxxS=6dXg0z#ln(dG9B^l1}AZA485 zJuEW@F9RH7?%w_V?R<)VAGfD6KO}~?ZNf{E;Sr%{`OyY-?T7C^Ew*bKPcyMgCY~3- zlO?4ZGTyxL>#lUnFn}teEKA{bgdV2%&{?e`KHyN^p^|bC(0M|$p8m*i9HVqlb9Dma z!}*}xN~mBB-*4X@NO}R)&JX8d?E-~LRBObA{m2oFmtrv*O;*X>yRUG!VdqD& zCu-|0b3PXR! zj3yd>M(NE!dA3fNNlyn^%XJyW^d@D$y>InRP<@4%`HqHG5Si4r{-B5vaXm}uuf#?p zi}x?%(p4_78h-s}g}RezWv7m(Xa2G$7ApT_Kfvn${I+_KZupol|12dFlU!fW)t87c zGp@#~$nl9+f#*_jA6NhkvKghk#+oJYEM&kUKI}JtPqiQQqP;vl>r4*g===`r^RHNK z7+t#W#&)zFXqM+0#fL1?H5|D(>UMNCjVqqg6Gy*4o|}fP8~Dap-m#F?M@>qZ= zw1}?vKa|{Tid~4cMt}6y$qkcnR)(m64E4T7__ha*o+%osGCjTkj8WB#ZoYDV%J&C9 zd6Q0z9%(L2>bW~z@G{jsf10Y|I#%XsZL}nP18?io&A%rsV~rI%u8d( zhfbgYsSw78ZF0&QDF5_n{i|v}jSBdG(v2}3pw+*;ujH;Tq#JG*$~FL{#@afsV_Uc| z?FzP&vr_ut14ZU!eT9CrjhC`5`w#b6^6cBNRWh1k(2~O>)qLsEEnLW)Bl#4?6C=r- z@jE(YcJADKfUogFM%F-tN?SI&Q!5iU=j%81eRo}l z?1OGhESL-k!M=6q2N}&kUxz}9*wnZnhw6~NK6Yf5hi8IzaF@D{%Y5Dn|ZM-w$Z5M}UZ~k(w9xIxUjlC4=n4#SHU?`w1cS%Kr z;z&Q@>fUXNL?RPOzq9;!ONbeeF;tExIv?pLVw3d+9lASQ2n@m=_%R;0Pd^)@1khsx7Qi@B(XW_guFPjlcNh-8{z`s6cHG-) zMP`cmjF)1k%gzEt#CwXi7HAGP$L9R|r|;ShYV3r?YK6)PETQdo2FeVSz>co^`Z-bK z5sa}#ZJ9T%bMtW6Eq_tO&K)hE*R)pkoLPNzLCC_$=kt_L!q8kygYvFa?XgkEOXUl$ zW-1f2lhNwgOAjgsTNHY&vD2TGb;4$oS1d1Q|3)ONW~6_J>Sb25NIS_sUV1py`9!F} zJwThh7WAH1_@A4J)>i0Vzw&RITGZK<8J=^u z*xS0}q%YHm`=E9s_D1jAk&Ux?miaCc->T#|N~ZVmBel7y9`8Rx9hc>2ER*7UWR@H{eZ6@qWkIqI=R@aX{%x!$*hxy;7A$5T zDS9u2+HHxl@;XxyiDSS*>e2hNsC81A^IdyDymP}$>y1A(U&<{vjU~UuHNQEiwDgZu zR4fdxFxk+5Ip*DId)6Btm|NB!NUR0FBfM$v1*w_c$@&L-rDtiGj$~D>({0e)J2pIA zE)t9J!S5)}NmJ$Z=L3I7%KWbsdkA<~bDDu564rzAS(r9m=rfePSZ3RjbIp5U(!11q zw$z*I%|1H=Ejlm-*hP7bCz|>vC6oD8DXGB|YggMbZ(m*_#W2l8o2&Bf8N8|!~% z%k)7Ff+V359LOQ`TGt^!IO1Se<}%5xa}>}WbOwGv!2Eh(ceGg@ZoJO}_mco9>!Tz_ zUcd$HI`2F*kn1sbL|?;ER@~ZLph70~+U8o?{2Mb7_{oA|)F800PFL3uBPJ&9 z13Ss{Z^H!ZRpogbn_}RM4iKz8iqur=T>000nG0lb$K*g$rUj5k_`dwzo^He-R9lr7)+w1&etWq!!ZvVc#j)2 zygEfs9P@}hMpDdPHR<@QtAxEM`HIq3=Fw#Lj~}E4rnZ4m+ijI9B(U!oxqSuVw{zVe ze1L3E#Mj~B8pVkJDIUU_8sJUW?vB(Rp9fVz^)F9;z(=x2>8%_)Ba`ji7iC@O5p&hf zJ$9AnaR*#!aHkHyB^1zxe7Xg;gUVBnI-ay*SEQ#?D3w zFti~8Ab0>WOj=BJZj2J9~Vz!#nxDE;X~ zvX#J??frIRHmAx`DtKR$CSd>FU=JxXREMAl*7lLuHP5YvTQWkNx%{k`k$xYIdtk9t z{O%yA*(SVDqA|sMtUE)=)+l$?BX}5~sE+WLhkcxXyP05(9p;|5{3Hu!*+I}DZJ8T= z?BbOZ98P9rElw_tcle1MO8&krW9RoQwSSI0SNvX)dkPNi4#SKdPd{N-oRj4?=Q6g?>K;b_xoi#Z#j%Kr9kS9<|tTJnj^ zhPUON!w6~>;AYvAtiLiRAK2IW(5D7d@D3loJL?7!2|NV1PVltI&ZTdH3pUVAcyw2L zMP>Te7miGziom>vo+M#s1b($${iprF14Vx_J^S-%tOgJa5C-wf*a;^KV!#|%1i24{ z4?YM2k}{kFA8=e)1Qmkv5A_Fz<{!109SRpBECteznQI*&G!~VRSRD$t6R^sFiLPM? zsT1O1U)#&T*|VZ_&>l;?eDj7AJN5C*i6mz@!4e!Es%>CN_-2XsvDBSy;670CmD*g! z`2Yj%i;_(%{3;IPzlw{a2|btDNLM+E9yK7v8-pC5ZBA!+iS9Nb{&I?Eh2;z1aP7xK2r{~+HW+ZqatNWJ_yK%R zOpx%4ObNgxAh&eCdLCnWRA6PV#XMK;)ihfY*Dr9Yea5&&o_TZ+9s_M|c#~ptD z@bL5BRXeIFpDd%(xK;qZi-KeSe#^iF_OJ$GO#QvRZvn-H>^SoY8!4+ik6&IJk-2TT zwH!b;wv_%?(?m~&DLWH4<}JsntwPAe8`X{q*`aL|l5yfyj(QlkDx?nTIG?DBfom>% z3x)T}bmQdS+=dJi(uRA}7kC;BIyqCyA)%K3!F<%OWUK4ui(?}{QiV)2w@Q|VWU?q8 zQ_Hl<8EzaBpj+d{j6gcCaF+ZQmR!K)T<-QcNbxi_5peiih#Jp1W5h(bpM}+ zXEe@3>_EAlzD2<+d*#+uQ!v)%9H)FB2bbLJx&5}-9Y_B11&x`pF9(Z9vpU%vo-gkhl(@4}cxsK*3D71*pYJ(2v=AyQ7N9d1{iQK@iArIaeQVPnFX zGQ6K%ufu6q%flU&A=0kpJ8L48E&;U1y}nosyH^bz+gvU1YkYcpG84|@A>FJ^kr>1a z>qx~EF3p;OLi*?dMUjYT&X>ru##aqp8r;rOHWAeG0W+eOtg$xYNpaU-o1tnmdd>u?7voYcvg<3gcc00m$qvLXRP=Egg>YjsO z!0B}3PGODk*rD^xkMAYROg98(Cg+_T%Mv>LuB+bNy$SZ7|y=NC{8^h+^w-%oYHpzGMR?1_Z*igxHrc;0NJC z>S&FMzT2w_X$Me#SNFu>C;rf}i&jAD7)tq}!zuRalJZnGWl$}`ZY0GBm6M=jeI>+z zWM1*x#-W6ijGPwR;S*yMs{=Q&&(JEW%ZI{dO1d4@mt0XT>UP}xO}u_@Vyy=LhSyem zV{F5qN|I*Df#Ch|cg!7XkYnOpKK{#eN=YI*(V)p^us1D=3t2<*kw#m!02F zw*#<7+N2TaiRCv|tRt}G4m+$RXwpD&63`sf6%i0(kxlh*fNc>lW6)*5-2qo3&tNw= z=;pbz(!BTv-r8n%uEFT;!@fkCb-TSBMPYy)9<-|(*J^$y0mKi_{Imp$4biT;l92Wu zC=P(89JnX9F-ipnfN@}gVf9BAYLp0=T~WJ(s~vj*j)W`I0o))o8@?UiT^{+P^u@!_f`^IIaQAXZS1{v=TEa^v@f$!v)+2=I3U>iR&R^`hfykZeTO> zaP+|;V@ZsMNg_yeFxxoyA9m5_qAW(o9RK*SdeElQP<~DDttPJBT$GY_JSn0f3kF(( zo8LbuJzOXbv_wyJ?3y|37HS&p-qTOL+3VL=l1ytD@1rSK-2mFq5nIvrP7wvnS3_@D zrlq6iD=2aWR|G1g2DqAGG2D?P1uk$}CrL&R?=Gxpk>Sv(QG9zHOpn~vE{$12pynWX zKaO)-9b1>`bzVA5uXXQJ)`Zjk{O;r{hF55t}-Sj*{gDws6H4DU4O94t#Uw`H#lC! ze6sISZn1KFY3w0Uq0@cweXE-;-@CGQEniAW^t&YcfCrD68J4i&3Jz(*146Vjo07Q= znr$x^4P4q90#jQ~uqfs_`^IwhH>^6um*$yo@Di5jKb?a#>v!TVBzJwcqkn1KA+4Z@ z;j9xc{XTrxdRE;Lyd86vW7>HG-CW!w7xCw$@bHvQx+aSTcd+}5MjfiK z`PHX6@5o`7)U_rm_1*9}51tcyjE$IpFZ zp&pK#CtRoGr_}DB&^NV6nN^&>Se`BBwmGNyfa`4V3Qqa;6Edx(iOiW-9mOEAhBjw> zCZcMsLEpF={v2#P1LWJ2AVC*ox_Wv(^o_qaPi5IEo&~Z4_6f~RYgXx6!Xpv!C)Y|l z*(ya~QtDUknyT~FSKgK?A;`*6v*CNm~o(jG26y%Kl{glDCKVoX-8T~-GQ&s z3KUPc2OwI9P9w;;3HQrN%G%nxw|2d2c8-4Hz<5C~%R7jd87Pc;l@6>C*cYNDv;?zw zsLybL_DtRerUs=4aR4I{c#If85WVCI)od#Z!Vjb_sJ)`?zb@r6-H*gW*Ad%B_%=Y2 z-hV0AaQ-A%L=Yc12GnO*%)oA&?B`gyZr4?G0r)3;DUdOfCz=~&>n9dtq=p(3I=)3r z?5J2hqnm9?crSYmb`#W!IA73j;y0c{x;zy3mg_RGM6(ou5JS`0C+j-|$N+99{#ru+ zVj=^=^Hkt0^K;O6<3nRpd56{rCYUnkycMJ!g_~^{NUixum|nCyFkd~w(dFL z&SXT<@};<_=v$QKoI_YKU;R0mKvPYu?LetIH}MZ!a0m=C{Eu%br>ljsmm1^ifsjJG zu^O5dp^&Ma=j(_X2Wux8J|Gqz;KX(L7C=U!`aq)=@b)6opW}l_#yTWOu)<$uVUN-c z`Z{R5vm_>q{5d+5L=N0Tpor#?BS-dFoQCcf${2(-(LX#hyHDC4Gupo{D>h|0Bs}{7 zPf6eZO?{y}+K#0xYh?)UP(z}B+JD-CU&+<1OzGW zj$*(f43uGk2QD^}=R{1iD3UdXCTn7_P*|6}@- zIS4Bc3j;o2ysFjFTQHFsQO@x63p4&3Tm4k@GJquF`itX${0}V{`n*m59uweIO3h@) z{fJ)U_Aciwa9+Oi=IA)48e{*QEa>Tn>PGxQA`o7P&WbBUQtF!$S*93_-}JiAX~4L2 zl4>>@C)PQGf;{&XU#MWiwaXu0jjjOuFLC0BnP+C(JQn6^qS}eko+?bRh?F6c$W=YQ1YfN0|FquTY3Y-L7cf=}V>Ha)zLNVBm9;LBZ9mBTjXYQ2g3VirPm z^$oP8u%GSFNrmbcq|>4iHQXeiySf`c3*LfE$dN)tNE^2vhC{1lw51ul%=TqqdIAj` ziWaKZ$WiXIhyj{<;!2B)8lEeYyMFHAB&LbfjSsY*@FBG%(8(AzhUmZr{O2J!nnp#G zM)YbAG{dYip zyjkeZ!cXDOU3FpSA^?Fvgau_I^!BNk9R!#}oZtmpCDo)Ap}10ozy~535bT%@CP@j} z;SaHnW~CvA<`ILDYPI&pWc`1j=ENlp6B$fQ0gJ-E1DJs2`H3mFCK8(g%{NE_Oh&8W z)o|m)9O5CB)&6?S7~*A>7iKR~Z%MQ{rIaWF^kOOQDtozmk;+E!k~W|696LNVylvu3 z4KlR_;^`U})u^nyESe9&?gEf%Rv1^Tseb6d$-;h*c4>5g^M2YV0e>L*)89CHL9Ze*XPGl1N=5zK0lNB__7+ z;_6SoMy}@R>D&Lnd){?+k+3?qDtJHPRQ#u1)nYgD4;bYlS_Ugiy7B!;qm14R48-j? z-Yt6fS*hj=-m0FBk{=>M-}RQ#cq}bKMW)g%`3gr2SLO3(&-OZyNEdP%o9Bv~lufc} zjc%R!-VYfZ7a1C~$lk6jyw8FfltYsZOy>rNUEG4dj06$f{YQ0dDAvC)t~Z4_UNOOw z!vPByA>J-58BrPU?bd(Zx;p+S*IE{Jm8cySrC-|4`*PWd`56@Kd;DvMVNj-Lp5snl z-Dat9+5@#Ve!B(Q_^(6>TJ-=7gHib7JFzR65+>vH*s0-FcG=M8VQMrxUGr$Qib~zF z4SvzCniHzDMO zqEN;a17GJF(AorFl-X9(*j2%s5^F08t9Sb;zl_)^!744WLVrxIqFQF$o-xF)_c;8G znIS7cg+lqDA%gq1o!aEj$r)&`Fw*;kuCF$lD4~ERV#kLRdayP`jSPBgzDf4SbaN@T zX2yPw_|(Y$!s=ni+2Ba>A{g7nqkDh-;&X(?#P zae5rC8+(1~9QK>6SePnlTCbWiKnBdlQ;n1JGhpb5d7z*f6KwX-1d=gD85vSpo(#-l+k}eM-Sf&>8=K&!=+rsj%LHQ1C?1+D^;FIoaJ=t- z_|~j!Gs5sDoeSIA&R>XVgEtxtDF}VsKlj6B3BbA>A7jl9ld<$RC@h;a3x)Zcpr!`) za*VHUF4$s3Zq4!2@c{eeCM+tG6B9Y+xRCTXIfLKoxfX4LDw6fJox@}DUJw@z8(&a4 zygYCf<~(QnHoIqv&z72PTt8W4(&3WETGfG$}rgUfvUng&)kRaP5`MUj6(Nj&_Z z*AdF6Vs}cd>hwhbB?0xDwAs7i?o;eVsRe0R5J+WD+@YciGJ!eeL$@X+?IeL{DCHD* z`rF*Vt66DXkt*NEn{S9nN1;xs- z%ci(VYikkfA$AQda^I$U!5s40x34G;Nr3ge!-@0h-?ZRW)G(wv$K~J5lMwISl`4{> z$%M9LrTzgKC(KoK%kSFEkSBtz*145sK3(@FI=d0_1 z7vVDtty<=%x^>kumMtVKGYk?Kw8S0qQ-RlJV{^diTVYQeE{-dtK9Nq>v7KY)+J3JK zK%K6rnjNx9)&U|it>{>)!|1ya5==q3L$T#mG?mSg&93%S&Svy~-_oJv_=oSsC{h^^E#+ ztPL01T$1^2HeWM#_U1vqvT;98vCxDO2RoS z8|Y~HKypnP zZGzP`*dCJgN7wfUemc9sL7z+C^YqK^1R8c!%Ew$j6h#P0qP=G|0O*_u?6SPzSB|N# z6}h1aP00iwVn@B-PT{MN+3NNWDk)ULKve;+V%cG$_*A_3D5y9j{oHJk@S=AB z@I3m@47;`!5E7Co0~m(dW^T>EG(0h<+gGes6X}MP2;S{jd>{q_ZG{R0^abX_pXkt0 zsH7NGs~en=O90N0MPY3xa9T3#P8nu9Zle4iN7BZ_OV$3;6Y9vT+8xA-eTt7V1BMR# zo|J-xUP`c=(Ru@<^!H%yP(bIjLE$tPnk3LhckJ_tuzcJtEG%qe$qclP0U2YDU%x*5 zb?bUy9p3~NX21u~Q(iMoZ2?{oI|B0mfD;w|dyQ%*$v%tMdjd3ln~*j;;;=B$9N?99 zb9}s5WS2uJz8yMM2b(hD3RgTqD26gwwG z4+52=tk9hYv7$7KuR;k1fx`dQ+?9q?xwh>m+8VZ!Aybqj5gAfxM71alNRkpu$dC+6 z(lXOvitMOO5oIWL?Ig*NrNLAwT9q*+79mrVIeh26?Drdfe1E><`|<9hWA9@(te*8e z_jBLZbzbK{as=ko!m6kV!-Dhp433{{?bN)>ah-68>G+XD%-qNh*l6fK5VLe>bEGDD zjmrXG+HONuFZ>pXp$z&I%c*+3E~UH+!)YCfKE0jU36Qf;;@|f$K{dh?0Irpp#H0rX zbI>n`m;1fBJg>~kMUhy8QaU)C8;6F{SS*WQD#N#*S%sPh>!s)`ERtq3Yr#LqDY$v4 z&ISkz08Lhz3S4ghA%gcyewL(3;QS6|^+iNPXwd<{^Va-MVZtk{GF&zF3rsT63u(3L zek~QF??&qdSIO!fGF&elUWtZRS_uddP5;5RuC;TLdCKogv8g6EJ845y2A=8 z0a+0B7N1qUB=+yv5#n&i_p7-}w(qS?W5351()8`t#WUbTp(`<9e9z?Zv-SJvrhgy) z?Qc^;O~OYZ7F#!Kk6ERMg6gu*+4fQ5O~Kb3*9k^#9tcggY?|mj?X!x$m>)+y>J*-i zE$h;&zis{Wh_<-4t$*^<>89TT1zA^~gQ_|g`Z}h8rI0}ewXbsPC~O-*KLd>xd_e+EV5-#-c2?tK)#=RU(x zIvO7`B89G>QiC}#W<)X8II9P5}$) zE`nk!kdA;(1-vgIrOQGEhxb@*F=n@W> zu|jTQ8cWFN=+DsBW3oxSC>W6kAQQv?0MrLX91>{h+`H(RP!I}0?nj1b5U#hJ0u~N> zhA29(=YZo$01;aB8C~g^7C@`k8)M}+h;c3^Uec&Bh=>FY z9Kl!sX7tyXyN142h;|5*@k96q^r(1%LG(Wh_F}Iw<{f6+O4Y*s08SS*kjn3@|-_g^=M4&O9+2 z(Y=(-b-tKThh*_x4M&rzYF+30k0xqmwXS(T;T#g@qd9oGDS7gRV*ZYIR^P;Y76sWt zSK$dreBX`@*O`SyC^qKghU5y~$Wn`|DhA9kw#o2kQB>Fo%|DD3KG;k1?a!?1X>(7B zIxuQF%ddLrB~C#mr@sBia+Bafi-&d46%H|{#9oZYxcUiA6uB1HX^3$A3R@Jnf>*QV z?FrN5Yztmz%>;=X>^y&`&2NlKACm%hd$b(D0u*u6vvRV}Fvde_3)wWGzPO#OlJ}p% z{ddWgU02+s$3EJ3JN3Wn>AF^3^i54XOkN{A*Wv_=@3;>rS91E$7%+dl(PSf(F1Dh& zJx({{JgFTN6qDb#4-aa5IJPGXHyENcFy-{poCl(fvBh985A`8{H}V*Ydo{cyR74$UXxCo}19Gy-=Xy|< z04|+yS#~>f$*PI8#@OZV{S#IVTG#BOf#pFPQMz{J%Q%bBUX9Oofkn=kJ_}1~8b|s{ ztGcz9eYGKm!gw6ERc|UQMej)WKAG|-BvG6^J729@?BGFgi(Om8luPw%2GVGeHIron ziN{}>=`D1AsJ$$?U#`CVJySi=?$MZ}>JM)Aqh?KY%F-4gc$kppSmYU()dUH*?zCAo zM81Mt5)Zge#xuE$pnrqkmR?rCgC1GxzqhH2Pmg;dJw33nurM#v{BP5KpMCoTk$SVA zcy~i5XYz6N5p-TKI%;A3Rh{G@6DCzbXvhY)7a5NZFnLJ#+H+F-?Ak_eT@J&*W*Wa* z36EnmY2~CF20H+RqQL^W2YqNtkAex~S7eDsmH22uG>Eut5~K{7#H<5R9h2fpBtIKH zDu8@Gi-6>Q1#$kB7klb#>XAwd8geuZ(CnKb2B`$CtNiCbqU1b zE_@?~6+gWv%ntJERMoeWURC2G(PN-0L`jH$V~qT~4@Q)nWbM|1C+JFTEEXoGVx0dR zl*CdSF!q3Br5^e8!F$C< z_N4#>>jFGj+yE`mkrEgv!k|q)-AGanrccW5AN;KMogx*_TXKFQ?mir?B`iNDr#lKE z4zg&f38xtl;2rS*eAapu99-yb$E8(UokP5RX;F^ofTcq4^ig0_fe@Xn{I+n@Mct@* z191y1a+x+)3h7>atG(~aNNNaT1&4E)0j?k4zC{cAuO?JKx}*!kdmfn<%p6ZbTd?^c zX8WJ*G3o~qyKsm9-sn^`r&uz_FsbSH{rLnfU6ufF#BZ^obz`rh+J+<|%kb-=;*n)q z71RRE*juvL4kF{RUB|d%{;)@+|5FeN2d9US)g>x4I}G=@sU`cz~g>q+BZr#tV|AeOcv(a><+jaZ_|+gPfUgL!b#5S=!Hm<2iKkDSiGi%vchtu>iIwq1K;m(_$S zu+-n__;K$@o6VIgZuM(fG62Om@Dn{Bsv98g2Qw`ZlgW$h^dt?tECP;!XGCqWzmlRl zK(xa+pfTHn2{`ZLPb!tBeJDnz#oCzPpycE_bRXJR1S>{tg$g2)#^VHNMdUnc(4iqu z#4+*1OrdV#5hP%cgokPU@Me*T96`B_<@2?H)8Z_{bOQ9Or47cWrt*~!@t4pa=0H4- zVIu4!bF7o#{x6Gy0Y^JJBos?HQyD)y*p`Esh|j4yER@DG+LwRhhK{o9pC~sm4(G|s z{!jt>Gh5J2XwQuPloLjBni5ku+i8LKEC~0Zw@b)$d<($Kxl(v=| zy_jJOmjKyNEcjv-^Z7f4vyhBL1r|3O&CGE(KL+1Zqe6LYIAxR21`7Ftw+?>UP2;V2 z`EskNscEC~m9uj~apTTHq(SfmGGD-(@|MjG0+~B%C+qg!cs`0Gdz8K&)0wf1?a<5* z+P{L+i4#PA3A~BU-`d7>cRK|f;HP&J#&?=`C;l>73G*Y%;jQGL!R+l_rHVbA@j9*! z#zcN)b+)P=VrQQkc(*Fw#oas-AX)*6TYeqGKUN@#L|QtTe^i50&rA*iyPJevZrWrn zr42IM@tng9*);8eXuW^n4WKIEk-|>NjMWZfHl2K$&y9~F3nE1cyq&VA$ny?RUcimS zDiPQSI$(S?ShMPWC&ME2B-VHiI2Z0GOL;jO+SqUcHSFDWpgZ@*42Vqja2((oFdr$q zwKqPxMhiv>`q4&5jwrtiUc?`qs(iCM%J=$f(%=A374&6+W8U(J7g;Mo=;3Q6U%fm4 zv;_kw5+scYg>F9BJV=@*bKB{8DH$w&c^V|>EWl<0@6I74Fwud) z_hbE+y&ip!`?M&+sQ{DeJLYm*Ap^&bL-+$&@x~wPFbVyqFPWx02p`heu?qkpmsu5- z*LlCHy%?BM7V>~V?toW?=!KEzxiHhkfrfTn6#E%@RNyJ4E97Emiqn`J+$giL+Uidj z9JUrs?l-99k?6Cl2PF(HA?xB-^{#fiZh+*y-(Uq`&<)N3=GAh|5_xFk;B~hQ{TnbD zOmZN7CaxK8g`Y}9>S5g|x~$ha0-inLXJP0FqhPnOpPiN~n4x7QW=-XRq^Ifjvzf+7 z`SUDfKk}FEyt3(opUhV+x0fI2^c_u3stacSELcIOf1H1AIK)`8Z}`C2BZ&)Dur33h z{5J68;OTB4ljmOT3(a(_sjz(X=8)UI2qH^pw~v7q_pu#ax7G&bR$=8g6tMPVZLo)^>mNf=v?N zb!227G|tm}-0CnS!KPt^DtGZ_Rzm5)3=<-9hWjF5{uhaJzOTOxmwE^t#!`(l^qlLz zn5h^_?b-DsD8epDa4+rU=EX4pN`G$5D&{1V331gQpyU4^Mpn6kGP1jp+mizP@7~d& zU0!hUAT2|26>$Et0wL}|jJ8HiIMLfiOF9p9F z0v;VWbPRgKpbxU(;18A1^dOODnM?kF|M*6gu`cucMw1lDuDJ_{{O2h&D(AuBBvwV< zEUlZvFX#pw=O*}-V$lC3Xx8GLho#XXz)6);vcn9rMP^Cgh z0Hvpt2lqNsGmFONCixD8F%w|*yJTvl+$h6)TO?TV3TL`#Q^Br{c|n}=D5XSmpfWg3 zf^pp(ICCP8D}m|!GI}{N#)QPz6F}$Lk3GHBx#;!4YwhUm)dR_>w`knuSH}+R=kTU4 zD^L=i4uWz>=<4k)gG@)wsYcls8b?1qTLAf;xe?GN?e@*M*p9n80?DjM8@a`R<4p@y zF=od!->5FCH|_Ck?c!AZ81EdLcd7cUZ*>PBr{rGJeq2{&GMUikj>1V~{NiW%Ay?wegnS)l z>!lqKgc5`VTM0Pbjd={@-7NwQ8TU^l`!Wv9&S6(U${zwQlHnXM8XFJ>>oFjcRoYWh zh-ajVCdZrzlWe9l@s@@w>Y0T~rdF>mD-ts+SWRG{z`g_g+SAt7_NkQ`MS(39LJB5A zjnVu0!+={8&}f_l%p~>n_~8HNhI}-Lixup=pi$5`Qpgm9K`m~#Xg1uFetg|q%Krj& zV-(WfEXPN>v#Z=X-z`^HVh}_S#`~av?69}L-BQs52;U8*uz+J(2lz|`i`#bdkMx+$ z%TA6DIg^J%vhmWL(Bc52RXq~~|JD{9flwLzjjNzN6=Dfc$^MAYorf6S?RU+xc&P8q zAxmVjGGkRNNXQcyjl{1Q4~N7ekVQ7zoyZCPN*Y9w*58L*gJetS>4}+;>%4qdI5RT2j(n2kE=@&|2#~V| z>k3N!@(2kHkqS20lPZ>lFdK>Glxt5Crk4NIc+-X2Oeh|~erv|MErG+t!rhg&M79Ut z9Cfr5n~~lWx7JA%uPiZ?!0Czar~X)Yeh35BZ~jlt(TjZZYYl%6?iN$EH2Kg8{>Cf{ zbSv`Uu@Z?Sh#RSOo7n`?78~+7n30jEaq!*|!L1cmc;gAr28jK7m}+1^J%b`^0Bgzr z7iS46GVrt{tE)CGm`&2vNa!djv7pE!ldG=X|o|Sag;$;J*+W8;BpDq0ZYro z*@tZrp=Sn>J|bk1hHOt-W<5Q<%wxgG3w;jmBu$OrBlYze>x2G)i88ESh-{R&s9^ec z$%sw{7J^0=Bv$UWp;!|A#1}2>3cvcJ0%ivK$8NW?wUr?lk^2sG!@w90Wu1tNy z{Xd@Mky}jCs7u89w5NXH*8KXa`Q+JPS1l6gm)RYltPt>RJ?9fvIBw0St_$DrOHiWn z<0ocmx_&O9|0<;u>^cd-X2^X^8lTMqy=V$ zEUj!jeeltaVk5#L+{hF3G_Z}^eek`G*A&1@6*=;Fz?%i$`PrN}JUcgH6sgvhOHPK{ER)e>9LEQO#uOISMs8a-~p%EU%fk1eqHO8=H?e+owbG{bB zfm*R4SWRS=5F%L>#4f*s$H&dIW%PUk=SF304I(^UD)3fI8p^onE5)gsa9pA|*o870d zPb2K%X6jNr^^s3~{xF{^@W@COfJ2p1sr#48%E}pM_fTN0w8UM72^7gchTJAEK$s)Q zg?5t}Kv8}tOB)8OAd536aS9O={gDB0vQ^RMgGF=J-~|c9!vzmZFyCNBkz{YY|9ErP zl2cFe(hvsetnlTD>}-R9*e>h>VO@zrgKspOTSq4rY zY|)(Yrm3PAW^o9{#oA24LnfP4nD7eHFWtnw0EKhTo5~Wqg9+HJu$7eN94BWQWNjrm zy9QPlBPb5noKl;!BWtWc>_`BD-Blo?-ur>%@R3Epw6HWDEfu4O4qyhDlBd5Ch(1Y_ z%=-BqMismBs_gOl;9?l&XD;RO-NRprTTD(c5|M#Bl6AzSsOCG-mO}=E!Ik@UbGTx{ zey(()4Ly*^_{`o$AA{bQp`jtISdB_N4m`d*kIrc84Nl6yR3tR)NKR1CpqC+rY7iz1 zCv~FCzKGXe(7^(`Y}J!_`8{}XN}x|)_KM{rJ>|IB6KEpho= zn34V-<2MKz_;41J1VQo|85mUlaprZ_Q;GFtg232`@u}aI_!BtpaS%O4l%Nl{7J?YE zA15VGpxL$!;yApm^{@KUSHtZl?hQtXBYuzWqhHvE8UJ^Dw&zRBReQzB~Dp#ED&_*rPaDAdx=t z!9_#ZNK*4AHY5}b4}X4k*#|WdrjyuNWE3@bM&NH9&sh^|iHkYGcQX&Ljpk ztQJpUXy2b4Qs@4~v!d0=Vxc+rjwj?jbAtFJ;;Ijs%ne=|%DVRFmj`4zy^; zQx9{UY-MGCO&J!fiuqi&ZIq&@*@dun;Z5{2r{`Wt?af>s { order(year_published: :desc) } end ``` ```ruby -class Address < ApplicationRecord - belongs_to :client +class Book < ApplicationRecord + belongs_to :supplier + belongs_to :author + has_many :reviews + has_and_belongs_to_many :orders, join_table: 'books_orders' + + scope :in_print, -> { where(out_of_print: false) } + scope :out_of_print, -> { where(out_of_print: true) } + scope :old, -> { where('year_published < ?', 50.years.ago )} + scope :out_of_print_and_expensive, -> { out_of_print.where('price > 500') } + scope :costs_more_than, ->(amount) { where('price > ?', amount) } +end +``` + +```ruby +class Customer < ApplicationRecord + has_many :orders + has_many :reviews end ``` ```ruby class Order < ApplicationRecord - belongs_to :client, counter_cache: true + belongs_to :customer + has_and_belongs_to_many :books, join_table: 'books_orders' + + enum status: [:shipped, :being_packed, :complete, :cancelled] + + scope :created_before, ->(time) { where('created_at < ?', time) } end ``` ```ruby -class Role < ApplicationRecord - has_and_belongs_to_many :clients +class Review < ApplicationRecord + belongs_to :customer + belongs_to :book + + enum state: [:not_reviewed, :published, :hidden] end ``` +```ruby +class Supplier < ApplicationRecord + has_many :books + has_many :authors, through: :books +end +``` + +![Diagram of all of the bookstore models](images/active_record_querying/bookstore_models.png) + Retrieving Objects from the Database ------------------------------------ @@ -108,15 +139,15 @@ Active Record provides several different ways of retrieving a single object. Using the `find` method, you can retrieve the object corresponding to the specified _primary key_ that matches any supplied options. For example: ```ruby -# Find the client with primary key (id) 10. -client = Client.find(10) -# => # +# Find the customer with primary key (id) 10. +customer = Customer.find(10) +# => # ``` The SQL equivalent of the above is: ```sql -SELECT * FROM clients WHERE (clients.id = 10) LIMIT 1 +SELECT * FROM customers WHERE (customers.id = 10) LIMIT 1 ``` The `find` method will raise an `ActiveRecord::RecordNotFound` exception if no matching record is found. @@ -124,15 +155,15 @@ The `find` method will raise an `ActiveRecord::RecordNotFound` exception if no m You can also use this method to query for multiple objects. Call the `find` method and pass in an array of primary keys. The return will be an array containing all of the matching records for the supplied _primary keys_. For example: ```ruby -# Find the clients with primary keys 1 and 10. -clients = Client.find([1, 10]) # Or even Client.find(1, 10) -# => [#, #] +# Find the customers with primary keys 1 and 10. +customers = Customer.find([1, 10]) # Or even Customer.find(1, 10) +# => [#, #] ``` The SQL equivalent of the above is: ```sql -SELECT * FROM clients WHERE (clients.id IN (1,10)) +SELECT * FROM customers WHERE (customers.id IN (1,10)) ``` WARNING: The `find` method will raise an `ActiveRecord::RecordNotFound` exception unless a matching record is found for **all** of the supplied primary keys. @@ -142,14 +173,14 @@ WARNING: The `find` method will raise an `ActiveRecord::RecordNotFound` exceptio The `take` method retrieves a record without any implicit ordering. For example: ```ruby -client = Client.take -# => # +customer = Customer.take +# => # ``` The SQL equivalent of the above is: ```sql -SELECT * FROM clients LIMIT 1 +SELECT * FROM customers LIMIT 1 ``` The `take` method returns `nil` if no record is found and no exception will be raised. @@ -157,17 +188,17 @@ The `take` method returns `nil` if no record is found and no exception will be r You can pass in a numerical argument to the `take` method to return up to that number of results. For example ```ruby -clients = Client.take(2) +customers = Customer.take(2) # => [ -# #, -# # +# #, +# # # ] ``` The SQL equivalent of the above is: ```sql -SELECT * FROM clients LIMIT 2 +SELECT * FROM customers LIMIT 2 ``` The `take!` method behaves exactly like `take`, except that it will raise `ActiveRecord::RecordNotFound` if no matching record is found. @@ -179,14 +210,14 @@ TIP: The retrieved record may vary depending on the database engine. The `first` method finds the first record ordered by primary key (default). For example: ```ruby -client = Client.first -# => # +customer = Customer.first +# => # ``` The SQL equivalent of the above is: ```sql -SELECT * FROM clients ORDER BY clients.id ASC LIMIT 1 +SELECT * FROM customers ORDER BY customers.id ASC LIMIT 1 ``` The `first` method returns `nil` if no matching record is found and no exception will be raised. @@ -196,31 +227,31 @@ If your [default scope](active_record_querying.html#applying-a-default-scope) co You can pass in a numerical argument to the `first` method to return up to that number of results. For example ```ruby -clients = Client.first(3) +customers = Customer.first(3) # => [ -# #, -# #, -# # +# #, +# #, +# # # ] ``` The SQL equivalent of the above is: ```sql -SELECT * FROM clients ORDER BY clients.id ASC LIMIT 3 +SELECT * FROM customers ORDER BY customers.id ASC LIMIT 3 ``` On a collection that is ordered using `order`, `first` will return the first record ordered by the specified attribute for `order`. ```ruby -client = Client.order(:first_name).first -# => # +customer = Customer.order(:first_name).first +# => # ``` The SQL equivalent of the above is: ```sql -SELECT * FROM clients ORDER BY clients.first_name ASC LIMIT 1 +SELECT * FROM customers ORDER BY customers.first_name ASC LIMIT 1 ``` The `first!` method behaves exactly like `first`, except that it will raise `ActiveRecord::RecordNotFound` if no matching record is found. @@ -230,14 +261,14 @@ The `first!` method behaves exactly like `first`, except that it will raise `Act The `last` method finds the last record ordered by primary key (default). For example: ```ruby -client = Client.last -# => # +customer = Customer.last +# => # ``` The SQL equivalent of the above is: ```sql -SELECT * FROM clients ORDER BY clients.id DESC LIMIT 1 +SELECT * FROM customers ORDER BY customers.id DESC LIMIT 1 ``` The `last` method returns `nil` if no matching record is found and no exception will be raised. @@ -247,31 +278,31 @@ If your [default scope](active_record_querying.html#applying-a-default-scope) co You can pass in a numerical argument to the `last` method to return up to that number of results. For example ```ruby -clients = Client.last(3) +customers = Customer.last(3) # => [ -# #, -# #, -# # +# #, +# #, +# # # ] ``` The SQL equivalent of the above is: ```sql -SELECT * FROM clients ORDER BY clients.id DESC LIMIT 3 +SELECT * FROM customers ORDER BY customers.id DESC LIMIT 3 ``` On a collection that is ordered using `order`, `last` will return the last record ordered by the specified attribute for `order`. ```ruby -client = Client.order(:first_name).last -# => # +customer = Customer.order(:first_name).last +# => # ``` The SQL equivalent of the above is: ```sql -SELECT * FROM clients ORDER BY clients.first_name DESC LIMIT 1 +SELECT * FROM customers ORDER BY customers.first_name DESC LIMIT 1 ``` The `last!` method behaves exactly like `last`, except that it will raise `ActiveRecord::RecordNotFound` if no matching record is found. @@ -281,52 +312,52 @@ The `last!` method behaves exactly like `last`, except that it will raise `Activ The `find_by` method finds the first record matching some conditions. For example: ```ruby -Client.find_by first_name: 'Lifo' -# => # +Customer.find_by first_name: 'Lifo' +# => # -Client.find_by first_name: 'Jon' +Customer.find_by first_name: 'Jon' # => nil ``` It is equivalent to writing: ```ruby -Client.where(first_name: 'Lifo').take +Customer.where(first_name: 'Lifo').take ``` The SQL equivalent of the above is: ```sql -SELECT * FROM clients WHERE (clients.first_name = 'Lifo') LIMIT 1 +SELECT * FROM customers WHERE (customers.first_name = 'Lifo') LIMIT 1 ``` The `find_by!` method behaves exactly like `find_by`, except that it will raise `ActiveRecord::RecordNotFound` if no matching record is found. For example: ```ruby -Client.find_by! first_name: 'does not exist' +Customer.find_by! first_name: 'does not exist' # => ActiveRecord::RecordNotFound ``` This is equivalent to writing: ```ruby -Client.where(first_name: 'does not exist').take! +Customer.where(first_name: 'does not exist').take! ``` ### Retrieving Multiple Objects in Batches -We often need to iterate over a large set of records, as when we send a newsletter to a large set of users, or when we export data. +We often need to iterate over a large set of records, as when we send a newsletter to a large set of customers, or when we export data. This may appear straightforward: ```ruby # This may consume too much memory if the table is big. -User.all.each do |user| - NewsMailer.weekly(user).deliver_now +Customer.all.each do |customer| + NewsMailer.weekly(customer).deliver_now end ``` -But this approach becomes increasingly impractical as the table size increases, since `User.all.each` instructs Active Record to fetch _the entire table_ in a single pass, build a model object per row, and then keep the entire array of model objects in memory. Indeed, if we have a large number of records, the entire collection may exceed the amount of memory available. +But this approach becomes increasingly impractical as the table size increases, since `Customer.all.each` instructs Active Record to fetch _the entire table_ in a single pass, build a model object per row, and then keep the entire array of model objects in memory. Indeed, if we have a large number of records, the entire collection may exceed the amount of memory available. Rails provides two methods that address this problem by dividing records into memory-friendly batches for processing. The first method, `find_each`, retrieves a batch of records and then yields _each_ record to the block individually as a model. The second method, `find_in_batches`, retrieves a batch of records and then yields _the entire batch_ to the block as an array of models. @@ -334,11 +365,11 @@ TIP: The `find_each` and `find_in_batches` methods are intended for use in the b #### `find_each` -The `find_each` method retrieves records in batches and then yields _each_ one to the block. In the following example, `find_each` retrieves users in batches of 1000 and yields them to the block one by one: +The `find_each` method retrieves records in batches and then yields _each_ one to the block. In the following example, `find_each` retrieves customers in batches of 1000 and yields them to the block one by one: ```ruby -User.find_each do |user| - NewsMailer.weekly(user).deliver_now +Customer.find_each do |customer| + NewsMailer.weekly(customer).deliver_now end ``` @@ -347,8 +378,8 @@ This process is repeated, fetching more batches as needed, until all of the reco `find_each` works on model classes, as seen above, and also on relations: ```ruby -User.where(weekly_subscriber: true).find_each do |user| - NewsMailer.weekly(user).deliver_now +Customer.where(weekly_subscriber: true).find_each do |customer| + NewsMailer.weekly(customer).deliver_now end ``` @@ -368,8 +399,8 @@ below. The `:batch_size` option allows you to specify the number of records to be retrieved in each batch, before being passed individually to the block. For example, to retrieve records in batches of 5000: ```ruby -User.find_each(batch_size: 5000) do |user| - NewsMailer.weekly(user).deliver_now +Customer.find_each(batch_size: 5000) do |customer| + NewsMailer.weekly(customer).deliver_now end ``` @@ -377,11 +408,11 @@ end By default, records are fetched in ascending order of the primary key. The `:start` option allows you to configure the first ID of the sequence whenever the lowest ID is not the one you need. This would be useful, for example, if you wanted to resume an interrupted batch process, provided you saved the last processed ID as a checkpoint. -For example, to send newsletters only to users with the primary key starting from 2000: +For example, to send newsletters only to customers with the primary key starting from 2000: ```ruby -User.find_each(start: 2000) do |user| - NewsMailer.weekly(user).deliver_now +Customer.find_each(start: 2000) do |customer| + NewsMailer.weekly(customer).deliver_now end ``` @@ -390,11 +421,11 @@ end Similar to the `:start` option, `:finish` allows you to configure the last ID of the sequence whenever the highest ID is not the one you need. This would be useful, for example, if you wanted to run a batch process using a subset of records based on `:start` and `:finish`. -For example, to send newsletters only to users with the primary key starting from 2000 up to 10000: +For example, to send newsletters only to customers with the primary key starting from 2000 up to 10000: ```ruby -User.find_each(start: 2000, finish: 10000) do |user| - NewsMailer.weekly(user).deliver_now +Customer.find_each(start: 2000, finish: 10000) do |customer| + NewsMailer.weekly(customer).deliver_now end ``` @@ -409,20 +440,21 @@ order is present in the relation. #### `find_in_batches` -The `find_in_batches` method is similar to `find_each`, since both retrieve batches of records. The difference is that `find_in_batches` yields _batches_ to the block as an array of models, instead of individually. The following example will yield to the supplied block an array of up to 1000 invoices at a time, with the final block containing any remaining invoices: +The `find_in_batches` method is similar to `find_each`, since both retrieve batches of records. The difference is that `find_in_batches` yields _batches_ to the block as an array of models, instead of individually. The following example will yield to the supplied block an array of up to 1000 customers at a time, with the final block containing any remaining customers: ```ruby -# Give add_invoices an array of 1000 invoices at a time. -Invoice.find_in_batches do |invoices| - export.add_invoices(invoices) +# Give add_customers an array of 1000 customers at a time. +Customer.find_in_batches do |customers| + export.add_customers(customers) end ``` `find_in_batches` works on model classes, as seen above, and also on relations: ```ruby -Invoice.pending.find_in_batches do |invoices| - pending_invoices_export.add_invoices(invoices) +# Give add_customers an array of 1000 recently active customers at a time. +Customer.recently_active.find_in_batches do |customers| + export.add_customers(customers) end ``` @@ -438,28 +470,28 @@ The `find_in_batches` method accepts the same options as `find_each`: Just like for `find_each`, `batch_size` establishes how many records will be retrieved in each group. For example, retrieving batches of 2500 records can be specified as: ```ruby -Invoice.find_in_batches(batch_size: 2500) do |invoices| - export.add_invoices(invoices) +Customer.find_in_batches(batch_size: 2500) do |customers| + export.add_customers(customers) end ``` **`:start`** -The `start` option allows specifying the beginning ID from where records will be selected. As mentioned before, by default records are fetched in ascending order of the primary key. For example, to retrieve invoices starting on ID: 5000 in batches of 2500 records, the following code can be used: +The `start` option allows specifying the beginning ID from where records will be selected. As mentioned before, by default records are fetched in ascending order of the primary key. For example, to retrieve customers starting on ID: 5000 in batches of 2500 records, the following code can be used: ```ruby -Invoice.find_in_batches(batch_size: 2500, start: 5000) do |invoices| - export.add_invoices(invoices) +Customer.find_in_batches(batch_size: 2500, start: 5000) do |customers| + export.add_customers(customers) end ``` **`:finish`** -The `finish` option allows specifying the ending ID of the records to be retrieved. The code below shows the case of retrieving invoices in batches, up to the invoice with ID: 7000: +The `finish` option allows specifying the ending ID of the records to be retrieved. The code below shows the case of retrieving customers in batches, up to the customer with ID: 7000: ```ruby -Invoice.find_in_batches(finish: 7000) do |invoices| - export.add_invoices(invoices) +Customer.find_in_batches(finish: 7000) do |customers| + export.add_customers(customers) end ``` @@ -474,16 +506,16 @@ The `where` method allows you to specify conditions to limit the records returne ### Pure String Conditions -If you'd like to add conditions to your find, you could just specify them in there, just like `Client.where("orders_count = '2'")`. This will find all clients where the `orders_count` field's value is 2. +If you'd like to add conditions to your find, you could just specify them in there, just like `Book.where("title = 'Introduction to Algorithms'")`. This will find all books where the `title` field value is 'Introduction to Algorithms'. -WARNING: Building your own conditions as pure strings can leave you vulnerable to SQL injection exploits. For example, `Client.where("first_name LIKE '%#{params[:first_name]}%'")` is not safe. See the next section for the preferred way to handle conditions using an array. +WARNING: Building your own conditions as pure strings can leave you vulnerable to SQL injection exploits. For example, `Book.where("title LIKE '%#{params[:title]}%'")` is not safe. See the next section for the preferred way to handle conditions using an array. ### Array Conditions -Now what if that number could vary, say as an argument from somewhere? The find would then take the form: +Now what if that title could vary, say as an argument from somewhere? The find would then take the form: ```ruby -Client.where("orders_count = ?", params[:orders]) +Book.where("title = ?", params[:title]) ``` Active Record will take the first argument as the conditions string and any additional arguments will replace the question marks `(?)` in it. @@ -491,21 +523,21 @@ Active Record will take the first argument as the conditions string and any addi If you want to specify multiple conditions: ```ruby -Client.where("orders_count = ? AND locked = ?", params[:orders], false) +Book.where("title = ? AND out_of_print = ?", params[:title], false) ``` -In this example, the first question mark will be replaced with the value in `params[:orders]` and the second will be replaced with the SQL representation of `false`, which depends on the adapter. +In this example, the first question mark will be replaced with the value in `params[:title]` and the second will be replaced with the SQL representation of `false`, which depends on the adapter. This code is highly preferable: ```ruby -Client.where("orders_count = ?", params[:orders]) +Book.where("title = ?", params[:title]) ``` to this code: ```ruby -Client.where("orders_count = #{params[:orders]}") +Book.where("title = #{params[:title]}") ``` because of argument safety. Putting the variable directly into the conditions string will pass the variable to the database **as-is**. This means that it will be an unescaped variable directly from a user who may have malicious intent. If you do this, you put your entire database at risk because once a user finds out they can exploit your database they can do just about anything to it. Never ever put your arguments directly inside the conditions string. @@ -517,7 +549,7 @@ TIP: For more information on the dangers of SQL injection, see the [Ruby on Rail Similar to the `(?)` replacement style of params, you can also specify keys in your conditions string along with a corresponding keys/values hash: ```ruby -Client.where("created_at >= :start_date AND created_at <= :end_date", +Book.where("created_at >= :start_date AND created_at <= :end_date", {start_date: params[:start_date], end_date: params[:end_date]}) ``` @@ -532,38 +564,39 @@ NOTE: Only equality, range, and subset checking are possible with Hash condition #### Equality Conditions ```ruby -Client.where(locked: true) +Book.where(out_of_print: true) ``` This will generate SQL like this: ```sql -SELECT * FROM clients WHERE (clients.locked = 1) +SELECT * FROM books WHERE (books.out_of_print = 1) ``` The field name can also be a string: ```ruby -Client.where('locked' => true) +Book.where('out_of_print' => true) ``` In the case of a belongs_to relationship, an association key can be used to specify the model if an Active Record object is used as the value. This method works with polymorphic relationships as well. ```ruby -Article.where(author: author) -Author.joins(:articles).where(articles: { author: author }) +author = Author.first +Book.where(author: author) +Author.joins(:books).where(books: { author: author }) ``` #### Range Conditions ```ruby -Client.where(created_at: (Time.now.midnight - 1.day)..Time.now.midnight) +Book.where(created_at: (Time.now.midnight - 1.day)..Time.now.midnight) ``` -This will find all clients created yesterday by using a `BETWEEN` SQL statement: +This will find all books created yesterday by using a `BETWEEN` SQL statement: ```sql -SELECT * FROM clients WHERE (clients.created_at BETWEEN '2008-12-21 00:00:00' AND '2008-12-22 00:00:00') +SELECT * FROM books WHERE (books.created_at BETWEEN '2008-12-21 00:00:00' AND '2008-12-22 00:00:00') ``` This demonstrates a shorter syntax for the examples in [Array Conditions](#array-conditions) @@ -573,13 +606,13 @@ This demonstrates a shorter syntax for the examples in [Array Conditions](#array If you want to find records using the `IN` expression you can pass an array to the conditions hash: ```ruby -Client.where(orders_count: [1,3,5]) +Customer.where(orders_count: [1,3,5]) ``` This code will generate SQL like this: ```sql -SELECT * FROM clients WHERE (clients.orders_count IN (1,3,5)) +SELECT * FROM customers WHERE (customers.orders_count IN (1,3,5)) ``` ### NOT Conditions @@ -587,13 +620,13 @@ SELECT * FROM clients WHERE (clients.orders_count IN (1,3,5)) `NOT` SQL queries can be built by `where.not`: ```ruby -Client.where.not(locked: true) +Customer.where.not(orders_count: [1,3,5]) ``` In other words, this query can be generated by calling `where` with no argument, then immediately chain with `not` passing `where` conditions. This will generate SQL like this: ```sql -SELECT * FROM clients WHERE (clients.locked != 1) +SELECT * FROM customers WHERE (customers.orders_count NOT IN (1,3,5)) ``` ### OR Conditions @@ -602,11 +635,11 @@ SELECT * FROM clients WHERE (clients.locked != 1) relation, and passing the second one as an argument. ```ruby -Client.where(locked: true).or(Client.where(orders_count: [1,3,5])) +Customer.where(last_name: 'Smith').or(Customer.where(orders_count: [1,3,5])) ``` ```sql -SELECT * FROM clients WHERE (clients.locked = 1 OR clients.orders_count IN (1,3,5)) +SELECT * FROM customers WHERE (customers.last_name = 'Smith' OR customers.orders_count IN (1,3,5)) ``` Ordering @@ -617,40 +650,40 @@ To retrieve records from the database in a specific order, you can use the `orde For example, if you're getting a set of records and want to order them in ascending order by the `created_at` field in your table: ```ruby -Client.order(:created_at) +Customer.order(:created_at) # OR -Client.order("created_at") +Customer.order("created_at") ``` You could specify `ASC` or `DESC` as well: ```ruby -Client.order(created_at: :desc) +Customer.order(created_at: :desc) # OR -Client.order(created_at: :asc) +Customer.order(created_at: :asc) # OR -Client.order("created_at DESC") +Customer.order("created_at DESC") # OR -Client.order("created_at ASC") +Customer.order("created_at ASC") ``` Or ordering by multiple fields: ```ruby -Client.order(orders_count: :asc, created_at: :desc) +Customer.order(orders_count: :asc, created_at: :desc) # OR -Client.order(:orders_count, created_at: :desc) +Customer.order(:orders_count, created_at: :desc) # OR -Client.order("orders_count ASC, created_at DESC") +Customer.order("orders_count ASC, created_at DESC") # OR -Client.order("orders_count ASC", "created_at DESC") +Customer.order("orders_count ASC", "created_at DESC") ``` If you want to call `order` multiple times, subsequent orders will be appended to the first: ```ruby -Client.order("orders_count ASC").order("created_at DESC") -# SELECT * FROM clients ORDER BY orders_count ASC, created_at DESC +Customer.order("orders_count ASC").order("created_at DESC") +# SELECT * FROM customers ORDER BY orders_count ASC, created_at DESC ``` WARNING: In most database systems, on selecting fields with `distinct` from a result set using methods like `select`, `pluck` and `ids`; the `order` method will raise an `ActiveRecord::StatementInvalid` exception unless the field(s) used in `order` clause are included in the select list. See the next section for selecting fields from the result set. @@ -662,18 +695,18 @@ By default, `Model.find` selects all the fields from the result set using `selec To select only a subset of fields from the result set, you can specify the subset via the `select` method. -For example, to select only `viewable_by` and `locked` columns: +For example, to select only `isbn` and `out_of_print` columns: ```ruby -Client.select(:viewable_by, :locked) +Book.select(:isbn, :out_of_print) # OR -Client.select("viewable_by, locked") +Book.select("isbn, out_of_print") ``` The SQL query used by this find call will be somewhat like: ```sql -SELECT viewable_by, locked FROM clients +SELECT isbn, out_of_print FROM books ``` Be careful because this also means you're initializing a model object with only the fields that you've selected. If you attempt to access a field that is not in the initialized record you'll receive: @@ -687,23 +720,23 @@ Where `` is the attribute you asked for. The `id` method will not rai If you would like to only grab a single record per unique value in a certain field, you can use `distinct`: ```ruby -Client.select(:name).distinct +Customer.select(:last_name).distinct ``` This would generate SQL like: ```sql -SELECT DISTINCT name FROM clients +SELECT DISTINCT last_name FROM customers ``` You can also remove the uniqueness constraint: ```ruby -query = Client.select(:name).distinct -# => Returns unique names +query = Customer.select(:last_name).distinct +# => Returns unique last_names query.distinct(false) -# => Returns all names, even if there are duplicates +# => Returns all last_names, even if there are duplicates ``` Limit and Offset @@ -714,25 +747,25 @@ To apply `LIMIT` to the SQL fired by the `Model.find`, you can specify the `LIMI You can use `limit` to specify the number of records to be retrieved, and use `offset` to specify the number of records to skip before starting to return the records. For example ```ruby -Client.limit(5) +Customer.limit(5) ``` -will return a maximum of 5 clients and because it specifies no offset it will return the first 5 in the table. The SQL it executes looks like this: +will return a maximum of 5 customers and because it specifies no offset it will return the first 5 in the table. The SQL it executes looks like this: ```sql -SELECT * FROM clients LIMIT 5 +SELECT * FROM customers LIMIT 5 ``` Adding `offset` to that ```ruby -Client.limit(5).offset(30) +Customer.limit(5).offset(30) ``` -will return instead a maximum of 5 clients beginning with the 31st. The SQL looks like: +will return instead a maximum of 5 customers beginning with the 31st. The SQL looks like: ```sql -SELECT * FROM clients LIMIT 5 OFFSET 30 +SELECT * FROM customers LIMIT 5 OFFSET 30 ``` Group @@ -743,7 +776,7 @@ To apply a `GROUP BY` clause to the SQL fired by the finder, you can use the `gr For example, if you want to find a collection of the dates on which orders were created: ```ruby -Order.select("date(created_at) as ordered_date, sum(price) as total_price").group("date(created_at)") +Order.select("created_at").group("created_at") ``` And this will give you a single `Order` object for each date where there are orders in the database. @@ -751,9 +784,9 @@ And this will give you a single `Order` object for each date where there are ord The SQL that would be executed would be something like this: ```sql -SELECT date(created_at) as ordered_date, sum(price) as total_price +SELECT created_at FROM orders -GROUP BY date(created_at) +GROUP BY created_at ``` ### Total of grouped items @@ -762,14 +795,14 @@ To get the total of grouped items on a single query, call `count` after the `gro ```ruby Order.group(:status).count -# => { 'awaiting_approval' => 7, 'paid' => 12 } +# => { 'being_packed' => 7, 'shipped' => 12 } ``` The SQL that would be executed would be something like this: ```sql SELECT COUNT (*) AS count_all, status AS status -FROM "orders" +FROM orders GROUP BY status ``` @@ -781,20 +814,31 @@ SQL uses the `HAVING` clause to specify conditions on the `GROUP BY` fields. You For example: ```ruby -Order.select("date(created_at) as ordered_date, sum(price) as total_price"). - group("date(created_at)").having("sum(price) > ?", 100) +Order.select("created_at, sum(total) as total_price"). + group("created_at").having("sum(total) > ?", 200) ``` The SQL that would be executed would be something like this: ```sql -SELECT date(created_at) as ordered_date, sum(price) as total_price +SELECT created_at as ordered_date, sum(total) as total_price FROM orders -GROUP BY date(created_at) -HAVING sum(price) > 100 +GROUP BY created_at +HAVING sum(total) > 200 ``` -This returns the date and total price for each order object, grouped by the day they were ordered and where the price is more than $100. +This returns the date and total price for each order object, grouped by the day they were ordered and where the total is more than $200. + +You would access the `total_price` for each order object returned like this: + +```ruby +big_orders = Order.select("created_at, sum(total) as total_price") + .group("created_at") + .having("sum(total) > ?", 200) + +big_orders[0].total_price +# Returns the total price for the first Order object +``` Overriding Conditions --------------------- @@ -804,31 +848,31 @@ Overriding Conditions You can specify certain conditions to be removed using the `unscope` method. For example: ```ruby -Article.where('id > 10').limit(20).order('id asc').unscope(:order) +Book.where('id > 100').limit(20).order('id desc').unscope(:order) ``` The SQL that would be executed: ```sql -SELECT * FROM articles WHERE id > 10 LIMIT 20 +SELECT * FROM books WHERE id > 100 LIMIT 20 # Original query without `unscope` -SELECT * FROM articles WHERE id > 10 ORDER BY id asc LIMIT 20 +SELECT * FROM books WHERE id > 100 ORDER BY id desc LIMIT 20 ``` -You can also unscope specific `where` clauses. For example: +You can also unscope specific `where` clauses. For example, this will remove `id` condition from the where clause: ```ruby -Article.where(id: 10, trashed: false).unscope(where: :id) -# SELECT "articles".* FROM "articles" WHERE trashed = 0 +Book.where(id: 10, out_of_print: false).unscope(where: :id) +# SELECT books.* FROM books WHERE out_of_print = 0 ``` A relation which has used `unscope` will affect any relation into which it is merged: ```ruby -Article.order('id asc').merge(Article.unscope(:order)) -# SELECT "articles".* FROM "articles" +Book.order('id desc').merge(Book.unscope(:order)) +# SELECT books.* FROM books ``` ### `only` @@ -836,16 +880,16 @@ Article.order('id asc').merge(Article.unscope(:order)) You can also override conditions using the `only` method. For example: ```ruby -Article.where('id > 10').limit(20).order('id desc').only(:order, :where) +Book.where('id > 10').limit(20).order('id desc').only(:order, :where) ``` The SQL that would be executed: ```sql -SELECT * FROM articles WHERE id > 10 ORDER BY id DESC +SELECT * FROM books WHERE id > 10 ORDER BY id DESC # Original query without `only` -SELECT * FROM articles WHERE id > 10 ORDER BY id DESC LIMIT 20 +SELECT * FROM books WHERE id > 10 ORDER BY id DESC LIMIT 20 ``` @@ -854,51 +898,61 @@ SELECT * FROM articles WHERE id > 10 ORDER BY id DESC LIMIT 20 The `reselect` method overrides an existing select statement. For example: ```ruby -Post.select(:title, :body).reselect(:created_at) +Book.select(:title, :isbn).reselect(:created_at) ``` The SQL that would be executed: ```sql -SELECT `posts`.`created_at` FROM `posts` +SELECT `books`.`created_at` FROM `books` ``` -In case the `reselect` clause is not used, +Compare this to the case where the `reselect` clause is not used: ```ruby -Post.select(:title, :body).select(:created_at) +Book.select(:title, :isbn).select(:created_at) ``` the SQL executed would be: ```sql -SELECT `posts`.`title`, `posts`.`body`, `posts`.`created_at` FROM `posts` +SELECT `books`.`title`, `books`.`isbn`, `books`.`created_at` FROM `books` ``` ### `reorder` -The `reorder` method overrides the default scope order. For example: +The `reorder` method overrides the default scope order. For example if the class definition includes this: ```ruby -class Article < ApplicationRecord - has_many :comments, -> { order('posted_at DESC') } +class Author < ApplicationRecord + has_many :books, -> { order(year_published: :desc) } end +``` -Article.find(10).comments.reorder('name') +And you execute this: + +```ruby +Author.find(10).books ``` The SQL that would be executed: ```sql -SELECT * FROM articles WHERE id = 10 LIMIT 1 -SELECT * FROM comments WHERE article_id = 10 ORDER BY name +SELECT * FROM authors WHERE id = 10 LIMIT 1 +SELECT * FROM books WHERE author_id = 10 ORDER BY year_published DESC ``` -In the case where the `reorder` clause is not used, the SQL executed would be: +You can using the `reorder` clause to specify a different way to order the books: + +```ruby +Author.find(10).books.reorder('year_published ASC') +``` + +The SQL that would be executed: ```sql -SELECT * FROM articles WHERE id = 10 LIMIT 1 -SELECT * FROM comments WHERE article_id = 10 ORDER BY posted_at DESC +SELECT * FROM authors WHERE id = 10 LIMIT 1 +SELECT * FROM books WHERE author_id = 10 ORDER BY year_published ASC ``` ### `reverse_order` @@ -906,53 +960,53 @@ SELECT * FROM comments WHERE article_id = 10 ORDER BY posted_at DESC The `reverse_order` method reverses the ordering clause if specified. ```ruby -Client.where("orders_count > 10").order(:name).reverse_order +Customer.where("orders_count > 10").order(:last_name).reverse_order ``` The SQL that would be executed: ```sql -SELECT * FROM clients WHERE orders_count > 10 ORDER BY name DESC +SELECT * FROM customers WHERE orders_count > 10 ORDER BY last_name DESC ``` If no ordering clause is specified in the query, the `reverse_order` orders by the primary key in reverse order. ```ruby -Client.where("orders_count > 10").reverse_order +Customer.where("orders_count > 10").reverse_order ``` The SQL that would be executed: ```sql -SELECT * FROM clients WHERE orders_count > 10 ORDER BY clients.id DESC +SELECT * FROM customers WHERE orders_count > 10 ORDER BY customers.id DESC ``` -This method accepts **no** arguments. +The `reverse_order` method accepts **no** arguments. ### `rewhere` -The `rewhere` method overrides an existing, named where condition. For example: +The `rewhere` method overrides an existing, named `where` condition. For example: ```ruby -Article.where(trashed: true).rewhere(trashed: false) +Book.where(out_of_print: true).rewhere(out_of_print: false) ``` The SQL that would be executed: ```sql -SELECT * FROM articles WHERE `trashed` = 0 +SELECT * FROM books WHERE `out_of_print` = 0 ``` -In case the `rewhere` clause is not used, +If the `rewhere` clause is not used, the where clauses are ANDed together: ```ruby -Article.where(trashed: true).where(trashed: false) +Book.where(out_of_print: true).where(out_of_print: false) ``` the SQL executed would be: ```sql -SELECT * FROM articles WHERE `trashed` = 1 AND `trashed` = 0 +SELECT * FROM books WHERE `out_of_print` = 1 AND `out_of_print` = 0 ``` Null Relation @@ -961,21 +1015,23 @@ Null Relation The `none` method returns a chainable relation with no records. Any subsequent conditions chained to the returned relation will continue generating empty relations. This is useful in scenarios where you need a chainable response to a method or a scope that could return zero results. ```ruby -Article.none # returns an empty Relation and fires no queries. +Order.none # returns an empty Relation and fires no queries. ``` ```ruby -# The visible_articles method below is expected to return a Relation. -@articles = current_user.visible_articles.where(name: params[:name]) +# The highlighted_reviews method below is expected to always return a Relation. +Book.first.highlighted_reviews.average(:rating) +# => Returns average rating of a book -def visible_articles - case role - when 'Country Manager' - Article.where(country: country) - when 'Reviewer' - Article.published - when 'Bad User' - Article.none # => returning [] or nil breaks the caller code in this case +class Book + # Returns reviews if there are atleast 5, + # else consider this as non-reviewed book + def highlighted_reviews + if reviews.count > 5 + reviews + else + Review.none # Does not meet minimum threshold yet + end end end ``` @@ -986,12 +1042,12 @@ Readonly Objects Active Record provides the `readonly` method on a relation to explicitly disallow modification of any of the returned objects. Any attempt to alter a readonly record will not succeed, raising an `ActiveRecord::ReadOnlyRecord` exception. ```ruby -client = Client.readonly.first -client.visits += 1 -client.save +customer = Customer.readonly.first +customer.visits += 1 +customer.save ``` -As `client` is explicitly set to be a readonly object, the above code will raise an `ActiveRecord::ReadOnlyRecord` exception when calling `client.save` with an updated value of _visits_. +As `customer` is explicitly set to be a readonly object, the above code will raise an `ActiveRecord::ReadOnlyRecord` exception when calling `customer.save` with an updated value of _visits_. Locking Records for Update -------------------------- @@ -1009,16 +1065,18 @@ Optimistic locking allows multiple users to access the same record for edits, an **Optimistic locking column** -In order to use optimistic locking, the table needs to have a column called `lock_version` of type integer. Each time the record is updated, Active Record increments the `lock_version` column. If an update request is made with a lower value in the `lock_version` field than is currently in the `lock_version` column in the database, the update request will fail with an `ActiveRecord::StaleObjectError`. Example: +In order to use optimistic locking, the table needs to have a column called `lock_version` of type integer. Each time the record is updated, Active Record increments the `lock_version` column. If an update request is made with a lower value in the `lock_version` field than is currently in the `lock_version` column in the database, the update request will fail with an `ActiveRecord::StaleObjectError`. + +For example: ```ruby -c1 = Client.find(1) -c2 = Client.find(1) +c1 = Customer.find(1) +c2 = Customer.find(1) -c1.first_name = "Michael" +c1.first_name = "Sandra" c1.save -c2.name = "should fail" +c2.first_name = "Michael" c2.save # Raises an ActiveRecord::StaleObjectError ``` @@ -1029,8 +1087,8 @@ This behavior can be turned off by setting `ActiveRecord::Base.lock_optimistical To override the name of the `lock_version` column, `ActiveRecord::Base` provides a class attribute called `locking_column`: ```ruby -class Client < ApplicationRecord - self.locking_column = :lock_client_column +class Customer < ApplicationRecord + self.locking_column = :lock_customer_column end ``` @@ -1041,10 +1099,10 @@ Pessimistic locking uses a locking mechanism provided by the underlying database For example: ```ruby -Item.transaction do - i = Item.lock.first - i.name = 'Jones' - i.save! +Book.transaction do + book = Book.lock.first + book.title = 'Algorithms, second edition' + book.save! end ``` @@ -1052,28 +1110,30 @@ The above session produces the following SQL for a MySQL backend: ```sql SQL (0.2ms) BEGIN -Item Load (0.3ms) SELECT * FROM `items` LIMIT 1 FOR UPDATE -Item Update (0.4ms) UPDATE `items` SET `updated_at` = '2009-02-07 18:05:56', `name` = 'Jones' WHERE `id` = 1 +Book Load (0.3ms) SELECT * FROM `books` LIMIT 1 FOR UPDATE +Book Update (0.4ms) UPDATE `books` SET `updated_at` = '2009-02-07 18:05:56', `title` = 'Algorithms, second edition' WHERE `id` = 1 SQL (0.8ms) COMMIT ``` You can also pass raw SQL to the `lock` method for allowing different types of locks. For example, MySQL has an expression called `LOCK IN SHARE MODE` where you can lock a record but still allow other queries to read it. To specify this expression just pass it in as the lock option: ```ruby -Item.transaction do - i = Item.lock("LOCK IN SHARE MODE").find(1) - i.increment!(:views) +Book.transaction do + book = Book.lock("LOCK IN SHARE MODE").find(1) + book.increment!(:views) end ``` +NOTE: Note that your database must support the raw SQL, that you pass in to the `lock` method. + If you already have an instance of your model, you can start a transaction and acquire the lock in one go using the following code: ```ruby -item = Item.first -item.with_lock do +book = Book.first +book.with_lock do # This block is called within a transaction, - # item is already locked. - item.increment!(:views) + # book is already locked. + book.increment!(:views) end ``` @@ -1094,112 +1154,88 @@ There are multiple ways to use the `joins` method. You can just supply the raw SQL specifying the `JOIN` clause to `joins`: ```ruby -Author.joins("INNER JOIN posts ON posts.author_id = authors.id AND posts.published = 't'") +Author.joins("INNER JOIN books ON books.author_id = authors.id AND books.out_of_print = FALSE") ``` This will result in the following SQL: ```sql -SELECT authors.* FROM authors INNER JOIN posts ON posts.author_id = authors.id AND posts.published = 't' +SELECT authors.* FROM authors INNER JOIN books ON books.author_id = authors.id AND books.out_of_print = FALSE ``` #### Using Array/Hash of Named Associations Active Record lets you use the names of the [associations](association_basics.html) defined on the model as a shortcut for specifying `JOIN` clauses for those associations when using the `joins` method. -For example, consider the following `Category`, `Article`, `Comment`, `Guest` and `Tag` models: - -```ruby -class Category < ApplicationRecord - has_many :articles -end - -class Article < ApplicationRecord - belongs_to :category - has_many :comments - has_many :tags -end - -class Comment < ApplicationRecord - belongs_to :article - has_one :guest -end - -class Guest < ApplicationRecord - belongs_to :comment -end - -class Tag < ApplicationRecord - belongs_to :article -end -``` - -Now all of the following will produce the expected join queries using `INNER JOIN`: +All of the following will produce the expected join queries using `INNER JOIN`: ##### Joining a Single Association ```ruby -Category.joins(:articles) +Book.joins(:reviews) ``` This produces: ```sql -SELECT categories.* FROM categories - INNER JOIN articles ON articles.category_id = categories.id +SELECT books.* FROM books + INNER JOIN reviews ON reviews.book_id = books.id ``` -Or, in English: "return a Category object for all categories with articles". Note that you will see duplicate categories if more than one article has the same category. If you want unique categories, you can use `Category.joins(:articles).distinct`. +Or, in English: "return a Book object for all books with reviews". Note that you will see duplicate books if a book has more than one review. If you want unique books, you can use `Book.joins(:reviews).distinct`. #### Joining Multiple Associations ```ruby -Article.joins(:category, :comments) +Book.joins(:author, :reviews) ``` This produces: ```sql -SELECT articles.* FROM articles - INNER JOIN categories ON categories.id = articles.category_id - INNER JOIN comments ON comments.article_id = articles.id +SELECT books.* FROM books + INNER JOIN authors ON authors.id = books.author_id + INNER JOIN reviews ON reviews.book_id = books.id ``` -Or, in English: "return all articles that have a category and at least one comment". Note again that articles with multiple comments will show up multiple times. +Or, in English: "return all books with their author that have at least one review". Note again that books with multiple reviews will show up multiple times. ##### Joining Nested Associations (Single Level) ```ruby -Article.joins(comments: :guest) +Book.joins(reviews: :customer) ``` This produces: ```sql -SELECT articles.* FROM articles - INNER JOIN comments ON comments.article_id = articles.id - INNER JOIN guests ON guests.comment_id = comments.id +SELECT books.* FROM books + INNER JOIN reviews ON reviews.book_id = book.id + INNER JOIN customer ON customers.id = reviews.id ``` -Or, in English: "return all articles that have a comment made by a guest." +Or, in English: "return all books that have a review by a customer." + ##### Joining Nested Associations (Multiple Level) ```ruby -Category.joins(articles: [{ comments: :guest }, :tags]) +Author.joins(books: [{reviews: { customer: :orders} }, :supplier] ) ``` This produces: ```sql -SELECT categories.* FROM categories - INNER JOIN articles ON articles.category_id = categories.id - INNER JOIN comments ON comments.article_id = articles.id - INNER JOIN guests ON guests.comment_id = comments.id - INNER JOIN tags ON tags.article_id = articles.id +SELECT * FROM authors + INNER JOIN books ON books.author_id = authors.id + INNER JOIN reviews ON reviews.book_id = books.id + INNER JOIN customers ON customers.id = reviews.customer_id + INNER JOIN orders ON orders.customer_id = customers.id +INNER JOIN suppliers ON suppliers.id = books.supplier_id ``` -Or, in English: "return all categories that have articles, where those articles have a comment made by a guest, and where those articles also have a tag." +Or, in English: "return all authors that have books with reviews _and_ have been ordered by a customer, and the suppliers for those books." + #### Specifying Conditions on the Joined Tables @@ -1207,17 +1243,19 @@ You can specify conditions on the joined tables using the regular [Array](#array ```ruby time_range = (Time.now.midnight - 1.day)..Time.now.midnight -Client.joins(:orders).where('orders.created_at' => time_range) +Customer.joins(:orders).where('orders.created_at' => time_range) ``` +This will find all customers who have orders that were created yesterday, using a `BETWEEN` SQL expression to compare `created_at`. + An alternative and cleaner syntax is to nest the hash conditions: ```ruby time_range = (Time.now.midnight - 1.day)..Time.now.midnight -Client.joins(:orders).where(orders: { created_at: time_range }) +Customer.joins(:orders).where(orders: { created_at: time_range }) ``` -This will find all clients who have orders that were created yesterday, again using a `BETWEEN` SQL expression. +This will find all customers who have orders that were created yesterday, again using a `BETWEEN` SQL expression. ### `left_outer_joins` @@ -1225,18 +1263,18 @@ If you want to select a set of records whether or not they have associated records you can use the `left_outer_joins` method. ```ruby -Author.left_outer_joins(:posts).distinct.select('authors.*, COUNT(posts.*) AS posts_count').group('authors.id') +Customer.left_outer_joins(:reviews).distinct.select('customers.*, COUNT(reviews.*) AS reviews_count').group('customers.id') ``` Which produces: ```sql -SELECT DISTINCT authors.*, COUNT(posts.*) AS posts_count FROM "authors" -LEFT OUTER JOIN posts ON posts.author_id = authors.id GROUP BY authors.id +SELECT DISTINCT customers.*, COUNT(reviews.*) AS reviews_count FROM customers +LEFT OUTER JOIN reviews ON reviews.customer_id = customers.id GROUP BY customers.id ``` -Which means: "return all authors with their count of posts, whether or not they -have any posts at all" +Which means: "return all customers with their count of reviews, whether or not they +have any reviews at all" Eager Loading Associations @@ -1246,38 +1284,38 @@ Eager loading is the mechanism for loading the associated records of the objects **N + 1 queries problem** -Consider the following code, which finds 10 clients and prints their postcodes: +Consider the following code, which finds 10 books and prints their authors' last_name: ```ruby -clients = Client.limit(10) +books = Book.limit(10) -clients.each do |client| - puts client.address.postcode +books.each do |book| + puts book.author.last_name end ``` -This code looks fine at first sight. But the problem lies within the total number of queries executed. The above code executes 1 (to find 10 clients) + 10 (one per each client to load the address) = **11** queries in total. +This code looks fine at the first sight. But the problem lies within the total number of queries executed. The above code executes 1 (to find 10 books) + 10 (one per each book to load the author) = **11** queries in total. **Solution to N + 1 queries problem** Active Record lets you specify in advance all the associations that are going to be loaded. This is possible by specifying the `includes` method of the `Model.find` call. With `includes`, Active Record ensures that all of the specified associations are loaded using the minimum possible number of queries. -Revisiting the above case, we could rewrite `Client.limit(10)` to eager load addresses: +Revisiting the above case, we could rewrite `Book.limit(10)` to eager load authors: ```ruby -clients = Client.includes(:address).limit(10) +books = Book.includes(:author).limit(10) -clients.each do |client| - puts client.address.postcode +books.each do |book| + puts book.author.last_name end ``` The above code will execute just **2** queries, as opposed to **11** queries in the previous case: ```sql -SELECT * FROM clients LIMIT 10 -SELECT addresses.* FROM addresses - WHERE (addresses.client_id IN (1,2,3,4,5,6,7,8,9,10)) +SELECT * FROM books LIMIT 10 +SELECT authors.* FROM authors + WHERE (authors.id IN (1,2,3,4,5,6,7,8,9,10)) ``` ### Eager Loading Multiple Associations @@ -1287,18 +1325,18 @@ Active Record lets you eager load any number of associations with a single `Mode #### Array of Multiple Associations ```ruby -Article.includes(:category, :comments) +Customer.includes(:orders, :reviews) ``` -This loads all the articles and the associated category and comments for each article. +This loads all the customers and the associated orders and reviews for each. #### Nested Associations Hash ```ruby -Category.includes(articles: [{ comments: :guest }, :tags]).find(1) +Customer.includes(orders: {books: [:supplier, :author]}).find(1) ``` -This will find the category with id 1 and eager load all of the associated articles, the associated articles' tags and comments, and every comment's guest association. +This will find the customer with id 1 and eager load all of the associated orders for it, the books for all of the orders, and the author and supplier for each of the books. ### Specifying Conditions on Eager Loaded Associations @@ -1307,14 +1345,14 @@ Even though Active Record lets you specify conditions on the eager loaded associ However if you must do this, you may use `where` as you would normally. ```ruby -Article.includes(:comments).where(comments: { visible: true }) +Author.includes(:books).where(books: { out_of_print: true }) ``` This would generate a query which contains a `LEFT OUTER JOIN` whereas the `joins` method would generate one using the `INNER JOIN` function instead. -```ruby - SELECT "articles"."id" AS t0_r0, ... "comments"."updated_at" AS t1_r5 FROM "articles" LEFT OUTER JOIN "comments" ON "comments"."article_id" = "articles"."id" WHERE (comments.visible = 1) +```sql + SELECT authors.id AS t0_r0, ... books.updated_at AS t1_r5 FROM authors LEFT OUTER JOIN "books" ON "books"."author_id" = "authors"."id" WHERE (books.out_of_print = 1) ``` If there was no `where` condition, this would generate the normal set of two queries. @@ -1323,11 +1361,11 @@ NOTE: Using `where` like this will only work when you pass it a Hash. For SQL-fragments you need to use `references` to force joined tables: ```ruby -Article.includes(:comments).where("comments.visible = true").references(:comments) +Author.includes(:books).where("books.out_of_print = true").references(:books) ``` -If, in the case of this `includes` query, there were no comments for any -articles, all the articles would still be loaded. By using `joins` (an INNER +If, in the case of this `includes` query, there were no books for any +authors, all the authors would still be loaded. By using `joins` (an INNER JOIN), the join conditions **must** match, otherwise no records will be returned. @@ -1342,63 +1380,63 @@ Scoping allows you to specify commonly-used queries which can be referenced as m To define a simple scope, we use the `scope` method inside the class, passing the query that we'd like to run when this scope is called: ```ruby -class Article < ApplicationRecord - scope :published, -> { where(published: true) } +class Book < ApplicationRecord + scope :out_of_print, -> { where(out_of_print: true) } end ``` +To call this `out_of_print` scope we can call it on either the class: + +```ruby +Book.out_of_print # => [all books out of print] +``` + +Or on an association consisting of `Book` objects: + +```ruby +author = Author.first +author.books.out_of_print # => [all out of print books by this author] +``` + Scopes are also chainable within scopes: ```ruby -class Article < ApplicationRecord - scope :published, -> { where(published: true) } - scope :published_and_commented, -> { published.where("comments_count > 0") } +class Book < ApplicationRecord + scope :out_of_print, -> { where(out_of_print: true) } + scope :out_of_print_and_expensive, -> { out_of_print.where("price > 500") } end ``` -To call this `published` scope we can call it on either the class: - -```ruby -Article.published # => [published articles] -``` - -Or on an association consisting of `Article` objects: - -```ruby -category = Category.first -category.articles.published # => [published articles belonging to this category] -``` - ### Passing in arguments Your scope can take arguments: ```ruby -class Article < ApplicationRecord - scope :created_before, ->(time) { where("created_at < ?", time) } +class Book < ApplicationRecord + scope :costs_more_than, ->(amount) { where("price > ?", amount) } end ``` Call the scope as if it were a class method: ```ruby -Article.created_before(Time.zone.now) +Book.costs_more_than(100.10) ``` However, this is just duplicating the functionality that would be provided to you by a class method. ```ruby -class Article < ApplicationRecord - def self.created_before(time) - where("created_at < ?", time) +class Book < ApplicationRecord + def self.costs_more_than(amount) + where("price > ?", amount) end end ``` - + These methods will still be accessible on the association objects: ```ruby -category.articles.created_before(time) +author.books.costs_more_than(100.10) ``` ### Using conditionals @@ -1406,7 +1444,7 @@ category.articles.created_before(time) Your scope can utilize conditionals: ```ruby -class Article < ApplicationRecord +class Order < ApplicationRecord scope :created_before, ->(time) { where("created_at < ?", time) if time.present? } end ``` @@ -1414,7 +1452,7 @@ end Like the other examples, this will behave similarly to a class method. ```ruby -class Article < ApplicationRecord +class Order < ApplicationRecord def self.created_before(time) where("created_at < ?", time) if time.present? end @@ -1429,8 +1467,8 @@ If we wish for a scope to be applied across all queries to the model we can use `default_scope` method within the model itself. ```ruby -class Client < ApplicationRecord - default_scope { where("removed_at IS NULL") } +class Book < ApplicationRecord + default_scope { where(out_of_print: false) } end ``` @@ -1438,14 +1476,14 @@ When queries are executed on this model, the SQL query will now look something l this: ```sql -SELECT * FROM clients WHERE removed_at IS NULL +SELECT * FROM books WHERE (out_of_print = false) ``` If you need to do more complex things with a default scope, you can alternatively define it as a class method: ```ruby -class Client < ApplicationRecord +class Book < ApplicationRecord def self.default_scope # Should return an ActiveRecord::Relation. end @@ -1457,73 +1495,77 @@ when the scope arguments are given as a `Hash`. It is not applied while updating a record. E.g.: ```ruby -class Client < ApplicationRecord - default_scope { where(active: true) } +class Book < ApplicationRecord + default_scope { where(out_of_print: false) } end -Client.new # => # -Client.unscoped.new # => # +Book.new # => # +Book.unscoped.new # => # ``` Be aware that, when given in the `Array` format, `default_scope` query arguments cannot be converted to a `Hash` for default attribute assignment. E.g.: ```ruby -class Client < ApplicationRecord - default_scope { where("active = ?", true) } +class Book < ApplicationRecord + default_scope { where("out_of_print = ?", false) } end -Client.new # => # +Book.new # => # ``` ### Merging of scopes -Just like `where` clauses scopes are merged using `AND` conditions. +Just like `where` clauses, scopes are merged using `AND` conditions. ```ruby -class User < ApplicationRecord - scope :active, -> { where state: 'active' } - scope :inactive, -> { where state: 'inactive' } +class Book < ApplicationRecord + scope :in_print, -> { where(out_of_print: false) } + scope :out_of_print, -> { where(out_of_print: true) } + + scope :recent, -> { where('year_published >= ?', Date.current.year - 50 )} + scope :old, -> { where('year_published < ?', Date.current.year - 50 )} end -User.active.inactive -# SELECT "users".* FROM "users" WHERE "users"."state" = 'active' AND "users"."state" = 'inactive' +Book.out_of_print.old +# SELECT books.* FROM books WHERE books.out_of_print = 'true' AND books.year_published < 1969 ``` We can mix and match `scope` and `where` conditions and the final SQL will have all conditions joined with `AND`. ```ruby -User.active.where(state: 'finished') -# SELECT "users".* FROM "users" WHERE "users"."state" = 'active' AND "users"."state" = 'finished' +Book.in_print.where('price < 100') +# SELECT books.* FROM books WHERE books.out_of_print = 'false' AND books.price < 100 ``` If we do want the last `where` clause to win then `Relation#merge` can be used. ```ruby -User.active.merge(User.inactive) -# SELECT "users".* FROM "users" WHERE "users"."state" = 'inactive' +Book.in_print.merge(Book.out_of_print) +# SELECT books.* FROM books WHERE books.out_of_print = true ``` One important caveat is that `default_scope` will be prepended in `scope` and `where` conditions. ```ruby -class User < ApplicationRecord - default_scope { where state: 'pending' } - scope :active, -> { where state: 'active' } - scope :inactive, -> { where state: 'inactive' } +class Book < ApplicationRecord + default_scope { where('year_published >= ?', Date.current.year - 50 )} + + scope :in_print, -> { where(out_of_print: false) } + scope :out_of_print, -> { where(out_of_print: true) } end -User.all -# SELECT "users".* FROM "users" WHERE "users"."state" = 'pending' +Book.all +# SELECT books.* FROM books WHERE (year_published >= 1969) -User.active -# SELECT "users".* FROM "users" WHERE "users"."state" = 'pending' AND "users"."state" = 'active' +Book.in_print +# SELECT books.* FROM books WHERE (year_published >= 1969) AND books.out_of_print = true -User.where(state: 'inactive') -# SELECT "users".* FROM "users" WHERE "users"."state" = 'pending' AND "users"."state" = 'inactive' +Book.where('price > 50') +# SELECT books.* FROM books WHERE (year_published >= 1969) AND (price > 50) ``` As you can see above the `default_scope` is being merged in both @@ -1536,68 +1578,99 @@ especially useful if a `default_scope` is specified in the model and should not applied for this particular query. ```ruby -Client.unscoped.load +Book.unscoped.load ``` This method removes all scoping and will do a normal query on the table. ```ruby -Client.unscoped.all -# SELECT "clients".* FROM "clients" +Book.unscoped.all +# SELECT books.* FROM books -Client.where(published: false).unscoped.all -# SELECT "clients".* FROM "clients" +Book.where(out_of_print: true).unscoped.all +# SELECT books.* FROM books ``` -`unscoped` can also accept a block. +`unscoped` can also accept a block: ```ruby -Client.unscoped { - Client.created_before(Time.zone.now) +Book.unscoped { + Book.out_of_print } +# SELECT books.* FROM books WHERE books.out_of_print ``` Dynamic Finders --------------- -For every field (also known as an attribute) you define in your table, Active Record provides a finder method. If you have a field called `first_name` on your `Client` model for example, you get `find_by_first_name` for free from Active Record. If you have a `locked` field on the `Client` model, you also get `find_by_locked` method. +For every field (also known as an attribute) you define in your table, + Active Record provides a finder method. If you have a field called `first_name` on your `Customer` model for example, + you get the instance method `find_by_first_name` for free from Active Record. + If you also have a `locked` field on the `Customer` model, you also get `find_by_locked` method. -You can specify an exclamation point (`!`) on the end of the dynamic finders to get them to raise an `ActiveRecord::RecordNotFound` error if they do not return any records, like `Client.find_by_name!("Ryan")` +You can specify an exclamation point (`!`) on the end of the dynamic finders + to get them to raise an `ActiveRecord::RecordNotFound` error if they do not return any records, like `Customer.find_by_name!("Ryan")` -If you want to find both by name and locked, you can chain these finders together by simply typing "`and`" between the fields. For example, `Client.find_by_first_name_and_locked("Ryan", true)`. +If you want to find both by `name` and `orders_count`, you can chain these finders together by simply typing "`and`" between the fields. +For example, `Customer.find_by_first_name_and_orders_count("Ryan", 5)`. Enums ----- -The `enum` macro maps an integer column to a set of possible values. +An `enum` lets you define an Array of values for an attribute and refer to them by name. The actual value stored in the database is an integer that has been mapped to one of the values. +Declaring an enum will: + +* Create scopes that can be used to find all objects that have or do not have one of the enum values +* Create an instance method that can be used to determine if an object has a particular value for the enum +* Create an instance method that can be used to change the enum value of an object + +for all possible values of an `enum`. + +For example, given this `enum` declaration: + ```ruby -class Book < ApplicationRecord - enum availability: [:available, :unavailable] +class Order < ApplicationRecord + enum status: [:shipped, :being_packaged, :complete, :cancelled] end ``` -This will automatically create the corresponding [scopes](#scopes) to query the -model. Methods to transition between states and query the current state are also -added. +These [scopes](#scopes) are created automatically and can be used to find all objects with or wihout a particular value for `status`: ```ruby -# Both examples below query just available books. -Book.available -# or -Book.where(availability: :available) - -book = Book.new(availability: :available) -book.available? # => true -book.unavailable! # => true -book.available? # => false +Order.shipped +# finds all orders with status == :shipped +Order.not_shipped +# finds all orders with status != :shipped +... ``` -Read the full documentation about enums -[in the Rails API docs](https://api.rubyonrails.org/classes/ActiveRecord/Enum.html). +These instace methods are created automatically and query whether the model has that value for the `status` enum: -Understanding The Method Chaining ---------------------------------- +```ruby +order = Order.first +order.shipped? +# Returns true if status == :shipped +order.complete? +# Returns true if status == :shipped +... +``` + +These instance methods are created automatically and will first update the value of `status` to the named value + and then query whether or not the status has been successfully set to the value: + +```ruby +order = Order.first +order.shipped! +# => UPDATE "orders" SET "status" = ?, "updated_at" = ? WHERE "orders"."id" = ? [["status", 0], ["updated_at", "2019-01-24 07:13:08.524320"], ["id", 1]] +# => true +... +``` + +Full documentation about enums can be found [here](https://api.rubyonrails.org/classes/ActiveRecord/Enum.html). + +Understanding Method Chaining +----------------------------- The Active Record pattern implements [Method Chaining](https://en.wikipedia.org/wiki/Method_chaining), which allow us to use multiple Active Record methods together in a simple and straightforward way. @@ -1608,46 +1681,44 @@ a single object (see [Retrieving a Single Object Section](#retrieving-a-single-o have to be at the end of the statement. There are some examples below. This guide won't cover all the possibilities, just a few as examples. -When an Active Record method is called, the query is not immediately generated and sent to the database, -this just happens when the data is actually needed. So each example below generates a single query. +When an Active Record method is called, the query is not immediately generated and sent to the database. + The query is sent only when the data is actually needed. So each example below generates a single query. ### Retrieving filtered data from multiple tables ```ruby -Person - .select('people.id, people.name, comments.text') - .joins(:comments) - .where('comments.created_at > ?', 1.week.ago) +Customer + .select('customers.id, customers.last_name, reviews.body') + .joins(:reviews) + .where('reviews.created_at > ?', 1.week.ago) ``` The result should be something like this: ```sql -SELECT people.id, people.name, comments.text -FROM people -INNER JOIN comments - ON comments.person_id = people.id -WHERE comments.created_at > '2015-01-01' +SELECT customers.id, customers.last_name, reviews.body +FROM customers +INNER JOIN reviews + ON reviews.customer_id = customers.id +WHERE (reviews.created_at > '2019-01-08') ``` ### Retrieving specific data from multiple tables ```ruby -Person - .select('people.id, people.name, companies.name') - .joins(:company) - .find_by('people.name' => 'John') # this should be the last +Book.select('books.id, books.title, authors.first_name') + .joins(:author) + .find_by(title: 'Abstraction and Specification in Program Development') ``` The above should generate: ```sql -SELECT people.id, people.name, companies.name -FROM people -INNER JOIN companies - ON companies.person_id = people.id -WHERE people.name = 'John' -LIMIT 1 +SELECT books.id, books.title, authors.first_name +FROM books +INNER JOIN authors + ON authors.id = books.author_id +WHERE books.title = $1 [["title", "Abstraction and Specification in Program Development"]] ``` NOTE: Note that if a query matches multiple records, `find_by` will @@ -1663,46 +1734,46 @@ It's common that you need to find a record or create it if it doesn't exist. You The `find_or_create_by` method checks whether a record with the specified attributes exists. If it doesn't, then `create` is called. Let's see an example. -Suppose you want to find a client named 'Andy', and if there's none, create one. You can do so by running: +Suppose you want to find a customer named 'Andy', and if there's none, create one. You can do so by running: ```ruby -Client.find_or_create_by(first_name: 'Andy') -# => # +Customer.find_or_create_by(first_name: 'Andy') +# => #Customer id: 5, first_name: "Andy", last_name: nil, title: nil, visits: 0, orders_count: nil, lock_version: 0, created_at: "2019-01-17 07:06:45", updated_at: "2019-01-17 07:06:45" ``` The SQL generated by this method looks like this: ```sql -SELECT * FROM clients WHERE (clients.first_name = 'Andy') LIMIT 1 +SELECT * FROM customers WHERE (customers.first_name = 'Andy') LIMIT 1 BEGIN -INSERT INTO clients (created_at, first_name, locked, orders_count, updated_at) VALUES ('2011-08-30 05:22:57', 'Andy', 1, NULL, '2011-08-30 05:22:57') +INSERT INTO customers (created_at, first_name, locked, orders_count, updated_at) VALUES ('2011-08-30 05:22:57', 'Andy', 1, NULL, '2011-08-30 05:22:57') COMMIT ``` -`find_or_create_by` returns either the record that already exists or the new record. In our case, we didn't already have a client named Andy so the record is created and returned. +`find_or_create_by` returns either the record that already exists or the new record. In our case, we didn't already have a customer named Andy so the record is created and returned. The new record might not be saved to the database; that depends on whether validations passed or not (just like `create`). Suppose we want to set the 'locked' attribute to `false` if we're creating a new record, but we don't want to include it in the query. So -we want to find the client named "Andy", or if that client doesn't -exist, create a client named "Andy" which is not locked. +we want to find the customer named "Andy", or if that customer doesn't +exist, create a customer named "Andy" which is not locked. We can achieve this in two ways. The first is to use `create_with`: ```ruby -Client.create_with(locked: false).find_or_create_by(first_name: 'Andy') +Customer.create_with(locked: false).find_or_create_by(first_name: 'Andy') ``` The second way is using a block: ```ruby -Client.find_or_create_by(first_name: 'Andy') do |c| +Customer.find_or_create_by(first_name: 'Andy') do |c| c.locked = false end ``` -The block will only be executed if the client is being created. The +The block will only be executed if the customer is being created. The second time we run this code, the block will be ignored. ### `find_or_create_by!` @@ -1713,10 +1784,10 @@ You can also use `find_or_create_by!` to raise an exception if the new record is validates :orders_count, presence: true ``` -to your `Client` model. If you try to create a new `Client` without passing an `orders_count`, the record will be invalid and an exception will be raised: +to your `Customer` model. If you try to create a new `Customer` without passing an `orders_count`, the record will be invalid and an exception will be raised: ```ruby -Client.find_or_create_by!(first_name: 'Andy') +Customer.find_or_create_by!(first_name: 'Andy') # => ActiveRecord::RecordInvalid: Validation failed: Orders count can't be blank ``` @@ -1726,29 +1797,29 @@ The `find_or_initialize_by` method will work just like `find_or_create_by` but it will call `new` instead of `create`. This means that a new model instance will be created in memory but won't be saved to the database. Continuing with the `find_or_create_by` example, we -now want the client named 'Nick': +now want the customer named 'Nina': ```ruby -nick = Client.find_or_initialize_by(first_name: 'Nick') -# => # +nina = Customer.find_or_initialize_by(first_name: 'Nina') +# => # -nick.persisted? +nina.persisted? # => false -nick.new_record? +nina.new_record? # => true ``` Because the object is not yet stored in the database, the SQL generated looks like this: ```sql -SELECT * FROM clients WHERE (clients.first_name = 'Nick') LIMIT 1 +SELECT * FROM customers WHERE (customers.first_name = 'Nina') LIMIT 1 ``` When you want to save it to the database, just call `save`: ```ruby -nick.save +nina.save # => true ``` @@ -1758,12 +1829,12 @@ Finding by SQL If you'd like to use your own SQL to find records in a table you can use `find_by_sql`. The `find_by_sql` method will return an array of objects even if the underlying query returns just a single record. For example you could run this query: ```ruby -Client.find_by_sql("SELECT * FROM clients - INNER JOIN orders ON clients.id = orders.client_id - ORDER BY clients.created_at desc") +Customer.find_by_sql("SELECT * FROM customers + INNER JOIN orders ON customers.id = orders.customer_id + ORDER BY customers.created_at desc") # => [ -# #, -# #, +# #, +# #, # ... # ] ``` @@ -1778,7 +1849,7 @@ This method will return an instance of `ActiveRecord::Result` class and calling object would return you an array of hashes where each hash indicates a record. ```ruby -Client.connection.select_all("SELECT first_name, created_at FROM clients WHERE id = '1'").to_a +Customer.connection.select_all("SELECT first_name, created_at FROM customers WHERE id = '1'").to_hash # => [ # {"first_name"=>"Rafael", "created_at"=>"2012-11-10 23:23:45.281189"}, # {"first_name"=>"Eileen", "created_at"=>"2013-12-09 11:22:35.221282"} @@ -1790,35 +1861,35 @@ Client.connection.select_all("SELECT first_name, created_at FROM clients WHERE i `pluck` can be used to query single or multiple columns from the underlying table of a model. It accepts a list of column names as an argument and returns an array of values of the specified columns with the corresponding data type. ```ruby -Client.where(active: true).pluck(:id) -# SELECT id FROM clients WHERE active = 1 +Book.where(out_of_print: true).pluck(:id) +# SELECT id FROM books WHERE out_of_print = false # => [1, 2, 3] -Client.distinct.pluck(:role) -# SELECT DISTINCT role FROM clients -# => ['admin', 'member', 'guest'] +Order.distinct.pluck(:status) +# SELECT DISTINCT status FROM orders +# => ['shipped', 'being_packed', 'cancelled'] -Client.pluck(:id, :name) -# SELECT clients.id, clients.name FROM clients -# => [[1, 'David'], [2, 'Jeremy'], [3, 'Jose']] +Customer.pluck(:id, :first_name) +# SELECT customers.id, customers.name FROM customers +# => [[1, 'David'], [2, 'Fran'], [3, 'Jose']] ``` `pluck` makes it possible to replace code like: ```ruby -Client.select(:id).map { |c| c.id } +Customer.select(:id).map { |c| c.id } # or -Client.select(:id).map(&:id) +Customer.select(:id).map(&:id) # or -Client.select(:id, :name).map { |c| [c.id, c.name] } +Customer.select(:id, :name).map { |c| [c.id, c.first_name] } ``` with: ```ruby -Client.pluck(:id) +Customer.pluck(:id) # or -Client.pluck(:id, :name) +Customer.pluck(:id, :first_name) ``` Unlike `select`, `pluck` directly converts a database result into a Ruby `Array`, @@ -1827,23 +1898,23 @@ a large or often-running query. However, any model method overrides will not be available. For example: ```ruby -class Client < ApplicationRecord +class Customer < ApplicationRecord def name - "I am #{super}" + "I am #{first_name}" end end -Client.select(:name).map &:name +Customer.select(:first_name).map &:name # => ["I am David", "I am Jeremy", "I am Jose"] -Client.pluck(:name) +Customer.pluck(:first_name) # => ["David", "Jeremy", "Jose"] ``` You are not limited to querying fields from a single table, you can query multiple tables as well. -```ruby -Client.joins(:comments, :categories).pluck("clients.email, comments.title, categories.name") +``` +Order.joins(:customer, :books).pluck("orders.created_at, customers.email, books.title") ``` Furthermore, unlike `select` and other `Relation` scopes, `pluck` triggers an immediate @@ -1851,10 +1922,10 @@ query, and thus cannot be chained with any further scopes, although it can work scopes already constructed earlier: ```ruby -Client.pluck(:name).limit(1) +Customer.pluck(:first_name).limit(1) # => NoMethodError: undefined method `limit' for # -Client.limit(1).pluck(:name) +Customer.limit(1).pluck(:first_name) # => ["David"] ``` @@ -1862,9 +1933,9 @@ NOTE: You should also know that using `pluck` will trigger eager loading if the ```ruby # store association for reusing it -assoc = Company.includes(:account) +assoc = Customer.includes(:reviews) assoc.pluck(:id) -# SELECT "companies"."id" FROM "companies" LEFT OUTER JOIN "accounts" ON "accounts"."id" = "companies"."account_id" +# SELECT "customers"."id" FROM "customers" LEFT OUTER JOIN "reviews" ON "reviews"."id" = "customers"."review_id" ``` One way to avoid this is to `unscope` the includes: @@ -1878,17 +1949,17 @@ assoc.unscope(:includes).pluck(:id) `ids` can be used to pluck all the IDs for the relation using the table's primary key. ```ruby -Person.ids -# SELECT id FROM people +Customer.ids +# SELECT id FROM customers ``` ```ruby -class Person < ApplicationRecord - self.primary_key = "person_id" +class Customer < ApplicationRecord + self.primary_key = "customer_id" end -Person.ids -# SELECT person_id FROM people +Customer.ids +# SELECT customer_id FROM customers ``` Existence of Objects @@ -1899,89 +1970,91 @@ This method will query the database using the same query as `find`, but instead object or collection of objects it will return either `true` or `false`. ```ruby -Client.exists?(1) +Customer.exists?(1) ``` The `exists?` method also takes multiple values, but the catch is that it will return `true` if any one of those records exists. ```ruby -Client.exists?(id: [1,2,3]) +Customer.exists?(id: [1,2,3]) # or -Client.exists?(name: ['John', 'Sergei']) +Customer.exists?(name: ['Jane', 'Sergei']) ``` It's even possible to use `exists?` without any arguments on a model or a relation. ```ruby -Client.where(first_name: 'Ryan').exists? +Customer.where(first_name: 'Ryan').exists? ``` -The above returns `true` if there is at least one client with the `first_name` 'Ryan' and `false` +The above returns `true` if there is at least one customer with the `first_name` 'Ryan' and `false` otherwise. ```ruby -Client.exists? +Customer.exists? ``` -The above returns `false` if the `clients` table is empty and `true` otherwise. +The above returns `false` if the `customers` table is empty and `true` otherwise. -You can also use `any?` and `many?` to check for existence on a model or relation. +You can also use `any?` and `many?` to check for existence on a model or relation. `many?` will use SQL `count` to determine if the item exists. ```ruby # via a model -Article.any? -Article.many? +Order.any? # => SELECT 1 AS one FROM orders +Order.many? # => SELECT COUNT(*) FROM orders # via a named scope -Article.recent.any? -Article.recent.many? +Order.shipped.any? # => SELECT 1 AS one FROM orders WHERE orders.status = 0 +Order.shipped.many? # => SELECT COUNT(*) FROM orders WHERE orders.status = 0 # via a relation -Article.where(published: true).any? -Article.where(published: true).many? +Book.where(out_of_print: true).any? +Book.where(out_of_print: true).many? # via an association -Article.first.categories.any? -Article.first.categories.many? +Customer.first.orders.any? +Customer.first.orders.many? ``` Calculations ------------ -This section uses count as an example method in this preamble, but the options described apply to all sub-sections. +This section uses `count` as an example method in this preamble, but the options described apply to all sub-sections. All calculation methods work directly on a model: ```ruby -Client.count -# SELECT COUNT(*) FROM clients +Customer.count +# SELECT COUNT(*) FROM customers ``` Or on a relation: ```ruby -Client.where(first_name: 'Ryan').count -# SELECT COUNT(*) FROM clients WHERE (first_name = 'Ryan') +Customer.where(first_name: 'Ryan').count +# SELECT COUNT(*) FROM customers WHERE (first_name = 'Ryan') ``` You can also use various finder methods on a relation for performing complex calculations: ```ruby -Client.includes("orders").where(first_name: 'Ryan', orders: { status: 'received' }).count +Customer.includes("orders").where(first_name: 'Ryan', orders: { status: 'shipped' }).count ``` Which will execute: ```sql -SELECT COUNT(DISTINCT clients.id) FROM clients - LEFT OUTER JOIN orders ON orders.client_id = clients.id - WHERE (clients.first_name = 'Ryan' AND orders.status = 'received') +SELECT COUNT(DISTINCT customers.id) FROM customers + LEFT OUTER JOIN orders ON orders.customer_id = customers.id + WHERE (customers.first_name = 'Ryan' AND orders.status = 0) ``` +assuming that Order has `enum status: [ :shipped, :being_packed, :cancelled ]` ### Count -If you want to see how many records are in your model's table you could call `Client.count` and that will return the number. If you want to be more specific and find all the clients with their age present in the database you can use `Client.count(:age)`. +If you want to see how many records are in your model's table you could call `Customer.count` and that will return the number. +If you want to be more specific and find all the customers with a title present in the database you can use `Customer.count(:title)`. For options, please see the parent section, [Calculations](#calculations). @@ -1990,7 +2063,7 @@ For options, please see the parent section, [Calculations](#calculations). If you want to see the average of a certain number in one of your tables you can call the `average` method on the class that relates to the table. This method call will look something like this: ```ruby -Client.average("orders_count") +Order.average("subtotal") ``` This will return a number (possibly a floating point number such as 3.14159265) representing the average value in the field. @@ -2002,7 +2075,7 @@ For options, please see the parent section, [Calculations](#calculations). If you want to find the minimum value of a field in your table you can call the `minimum` method on the class that relates to the table. This method call will look something like this: ```ruby -Client.minimum("age") +Order.minimum("subtotal") ``` For options, please see the parent section, [Calculations](#calculations). @@ -2012,7 +2085,7 @@ For options, please see the parent section, [Calculations](#calculations). If you want to find the maximum value of a field in your table you can call the `maximum` method on the class that relates to the table. This method call will look something like this: ```ruby -Client.maximum("age") +Order.maximum("subtotal") ``` For options, please see the parent section, [Calculations](#calculations). @@ -2022,7 +2095,7 @@ For options, please see the parent section, [Calculations](#calculations). If you want to find the sum of a field for all records in your table you can call the `sum` method on the class that relates to the table. This method call will look something like this: ```ruby -Client.sum("orders_count") +Order.sum("subtotal") ``` For options, please see the parent section, [Calculations](#calculations). @@ -2030,22 +2103,24 @@ For options, please see the parent section, [Calculations](#calculations). Running EXPLAIN --------------- -You can run EXPLAIN on the queries triggered by relations. For example, +You can run EXPLAIN on the queries triggered by relations. EXPLAIN output varies for each database. + +For example, running ```ruby -User.where(id: 1).joins(:articles).explain +Customer.where(id: 1).joins(:orders).explain ``` may yield -```sql -EXPLAIN for: SELECT `users`.* FROM `users` INNER JOIN `articles` ON `articles`.`user_id` = `users`.`id` WHERE `users`.`id` = 1 -+----+-------------+----------+-------+---------------+ -| id | select_type | table | type | possible_keys | -+----+-------------+----------+-------+---------------+ -| 1 | SIMPLE | users | const | PRIMARY | -| 1 | SIMPLE | articles | ALL | NULL | -+----+-------------+----------+-------+---------------+ +``` +EXPLAIN for: SELECT `customers`.* FROM `customers` INNER JOIN `orders` ON `orders`.`customer_id` = `customers`.`id` WHERE `customers`.`id` = 1 ++----+-------------+------------+-------+---------------+ +| id | select_type | table | type | possible_keys | ++----+-------------+------------+-------+---------------+ +| 1 | SIMPLE | customers | const | PRIMARY | +| 1 | SIMPLE | orders | ALL | NULL | ++----+-------------+------------+-------+---------------+ +---------+---------+-------+------+-------------+ | key | key_len | ref | rows | Extra | +---------+---------+-------+------+-------------+ @@ -2062,17 +2137,18 @@ Active Record performs a pretty printing that emulates that of the corresponding database shell. So, the same query running with the PostgreSQL adapter would yield instead -```sql -EXPLAIN for: SELECT "users".* FROM "users" INNER JOIN "articles" ON "articles"."user_id" = "users"."id" WHERE "users"."id" = 1 +``` +EXPLAIN for: SELECT "customers".* FROM "customers" INNER JOIN "orders" ON "orders"."customer_id" = "customers"."id" WHERE "customers"."id" = $1 [["id", 1]] QUERY PLAN ------------------------------------------------------------------------------ - Nested Loop Left Join (cost=0.00..37.24 rows=8 width=0) - Join Filter: (articles.user_id = users.id) - -> Index Scan using users_pkey on users (cost=0.00..8.27 rows=1 width=4) - Index Cond: (id = 1) - -> Seq Scan on articles (cost=0.00..28.88 rows=8 width=4) - Filter: (articles.user_id = 1) -(6 rows) + Nested Loop (cost=4.33..20.85 rows=4 width=164) + -> Index Scan using customers_pkey on customers (cost=0.15..8.17 rows=1 width=164) + Index Cond: (id = '1'::bigint) + -> Bitmap Heap Scan on orders (cost=4.18..12.64 rows=4 width=8) + Recheck Cond: (customer_id = '1'::bigint) + -> Bitmap Index Scan on index_orders_on_customer_id (cost=0.00..4.18 rows=4 width=0) + Index Cond: (customer_id = '1'::bigint) +(7 rows) ``` Eager loading may trigger more than one query under the hood, and some queries @@ -2080,18 +2156,18 @@ may need the results of previous ones. Because of that, `explain` actually executes the query, and then asks for the query plans. For example, ```ruby -User.where(id: 1).includes(:articles).explain +Customer.where(id: 1).includes(:orders).explain ``` -yields +may yield this for MySQL and MariaDB: -```sql -EXPLAIN for: SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 -+----+-------------+-------+-------+---------------+ -| id | select_type | table | type | possible_keys | -+----+-------------+-------+-------+---------------+ -| 1 | SIMPLE | users | const | PRIMARY | -+----+-------------+-------+-------+---------------+ +``` +EXPLAIN for: SELECT `customers`.* FROM `customers` WHERE `customers`.`id` = 1 ++----+-------------+-----------+-------+---------------+ +| id | select_type | table | type | possible_keys | ++----+-------------+-----------+-------+---------------+ +| 1 | SIMPLE | customers | const | PRIMARY | ++----+-------------+-----------+-------+---------------+ +---------+---------+-------+------+-------+ | key | key_len | ref | rows | Extra | +---------+---------+-------+------+-------+ @@ -2100,12 +2176,12 @@ EXPLAIN for: SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 1 row in set (0.00 sec) -EXPLAIN for: SELECT `articles`.* FROM `articles` WHERE `articles`.`user_id` IN (1) -+----+-------------+----------+------+---------------+ -| id | select_type | table | type | possible_keys | -+----+-------------+----------+------+---------------+ -| 1 | SIMPLE | articles | ALL | NULL | -+----+-------------+----------+------+---------------+ +EXPLAIN for: SELECT `orders`.* FROM `orders` WHERE `orders`.`customer_id` IN (1) ++----+-------------+--------+------+---------------+ +| id | select_type | table | type | possible_keys | ++----+-------------+--------+------+---------------+ +| 1 | SIMPLE | orders | ALL | NULL | ++----+-------------+--------+------+---------------+ +------+---------+------+------+-------------+ | key | key_len | ref | rows | Extra | +------+---------+------+------+-------------+ @@ -2116,7 +2192,18 @@ EXPLAIN for: SELECT `articles`.* FROM `articles` WHERE `articles`.`user_id` IN 1 row in set (0.00 sec) ``` -under MySQL and MariaDB. +and may yield this for PostgreSQL: + +``` + Customer Load (0.3ms) SELECT "customers".* FROM "customers" WHERE "customers"."id" = $1 [["id", 1]] + Order Load (0.3ms) SELECT "orders".* FROM "orders" WHERE "orders"."customer_id" = $1 [["customer_id", 1]] +=> EXPLAIN for: SELECT "customers".* FROM "customers" WHERE "customers"."id" = $1 [["id", 1]] + QUERY PLAN +---------------------------------------------------------------------------------- + Index Scan using customers_pkey on customers (cost=0.15..8.17 rows=1 width=164) + Index Cond: (id = '1'::bigint) +(2 rows) +``` ### Interpreting EXPLAIN