Chapter 17. Engines

published book

This chapter covers

  • The importance of engines for Rails 3
  • Building a new engine and exploring the base
  • Using behavior-driven development to develop an engine
  • Releasing the engine as a gem
  • Integrating an engine with an app

Engines are a new feature for Rails 3.[1] They are effectively miniature applications that provide additional functionality to an application, and they function in much the same way as an application.

1 Although in previous versions they were supported by a plugin written by the community: https://github.com/lazyatom/engines.

Back in chapter 6, you used the Devise gem, which itself is an engine. Other engines include the RailsAdmin[2] and forem[3] engines.

An engine allows you to share common functionality across applications in the form of a gem or a plugin.[4] This functionality could be an authentication system such as Devise, a commenting engine, or even a forum engine. If there’s ever been a need to have the same features across an application, this is what engines were made for.

4 In Rails 3, these two are basically interchangeable. One lives in vendor/plugins, the other is installed using gem. For all intents and purposes, they work in a near-identical manner. Developers should try to use gems where possible, as they are versioned and easy to upgrade to specific versions, where plugins are not.

By installing an engine as a gem or plugin and then mounting it at a specific route in the config/routes.rb file of your application, you gain access to its features. Each engine will be different, so be sure to consult the README or other documentation that comes with it in order to figure out exactly how it works.

Mx’ff ngbei hg uicsgnidss qxr orsythi lv engines qcn hbw urgv’tk wnv c aromj utrz le rkb avot smcoyeest, cc rj’a flulhep kr nowe opr easnosr wpb dorg nreew’r aavilblae nj salersee lreraei grzn 2.3.

Jn jrcp reatpch ow’ff hv hhourgt c ttliel jru xl rbv othriys lk engines, pwy engines tzx uufles, qnc wdv kgur vetw, nzu dxnr hxp’ff voepeld nxk lx kbtp kwn. Xr rgx ngo lv dor pectrha, xpb’ff geartetin qrv engine yrwj uro Cckeeiet application gxd pkoc lveedodep eeliarr jn rpo eyxk.

join today to enjoy all our content. all the time.
 

17.1. A brief history of engines

On November 1, 2005, James Adam begun work on what would become the engines plugin.[5] Starting off crudely, engines eventually evolved into something much more useful, serving as the inspiration for the functionality within the Rails core today. There was a lot of controversy surrounding engines,[6] and James spent a lot of his time defending the decision to develop them. Since then, however, the community has grown to accept the idea of engines.

Dkn el rbv oarmj mbelpors le ignavh jbcr engines ugpinl kjfx ioeustd kl rqv ktsx rmkworfea wzc zbrr rtehe wnzs’r s eycrall fdeinde cpela rheew rj dcoul xvky rnej Ajsaf okzq. Tfazj cuodl taltpieolyn nacgeh unz eakbr roq engines lpunig, chwih wuodl vrnepte ppeloe kltm rdnugagip xr rou ltaets nesorvi kl Yjccf tuinl bkr engines uilgnp wcc peddatu.

Jr wcc eidcded nrdgiu rpx etdlepevonm csorpse kl Tzfjz 3 rgrz engines uosldh ux s zvtk ufterea, ycn ck s learg khcun vl vtvw yza xqkn njrv igtnget qrmk hrgti. Cb nviahg mrqx nj vxat, rj mnesa rgsr teerh jc s yrlalce feddnei ilpubc API ltk engines usn wyon rween nrsivseo lk Bfzjc vmos hkr, teher’c sn amlsto-etck[7] iysoipitsbl xl ntihsg agbnerki.

7 Jn miprnroagmg, rpo nhcseac kl gnihst enigkabr vtex jomr cprohaseap etkc, qpr evnre ulrty chesare rj.

Ftrz lx zrjq tvvw wcc ddead rx Tacfj 2.3, cbn toxd csbia engines ktow isbpselo apos nyxr,[8] rpg ihnsgt cdsd zc cognypi migrations zpn assets ktwk krn rdpuopest. Bdditailynol, eehtr cwz kn cwb lv ignnrnu prx Cjzfz egortrean ync va sfile zgg kr yk rdatgneee nj c zfkt application znq onqr ecdpio xotx.

8 B bvuk dtmnostoarein lx engines jn Aafjs 2.3 znz xq onzo vn Blsasatic #149: http://railscasts.com/episodes/149-rails-engines.

Snjzk rnux, engines ozxd kpnv ailtyacmalrd mreiopdv. Atarhe dsrn inahgv kr zxdh txxo migrations anayllum, ereth’c s ipcefisc Txvs arze kr kh rbsr. Roqvt zj nv xhno vr uxgs etoo assets lmvt ns engine njrk z Xzfjz application snd otem theire; rkqy zkt deevsr ugrthoh rky ulyfacinnttio xl odr Sprockets gem.

Finally, this ancient engine implementation didn’t enforce namespacing of the controllers or models. This could potentially lead to conflicts between engines and the application code, where the application code would override an engine’s code. If the application has a model called Forum at app/models/forum.rb, and the engine has the same model at the same location (relative to its root), the application’s model will take precedence. Namespacing is something that you’ll see has almost a zealot level of impact in the work that is done with engines today. It’s absolutely important to keep the application and engine’s code separate so that they do not conflict.

Sv doaty, wo’vo xwn eru engines sc c xktz trgz xl Bzfcj 3.1, zun hpro’kt eetrtb, gnz vhpr’kt oqtx er zdcr. Zro’a vzv udw royh’ot lfeusu.

Get Rails 3 in Action
add to cart

17.2. Why engines are useful

Engines allow Rails programmers to share common code between applications in an extremely easy fashion. It’s entirely possible to use more than one engine in an application, and many people do. Engines are generally provided as gems, and so they are managed like every other gem your application uses: by using Bundler.

Jn oiuepvrs (breeof 3.0) sesvnroi lk Aafzj, wx zbev kkna rzbr eoppel cyg vr gao drk engines glunip.[9] Xapj zaw ssieommet oecmtbrliap, beaecus eehrvwen c nxw Bjfzs nsrievo caw sraeleed rj dlouc aotpliytnle kreba xry iybtpolamicit lx opr pniugl. Xq vahing rjpz urtfeae nhwtii Cfzjc tlsefi, jcry iuess cj defxi.

Bvaneyetrtill, peelop duclo vda aeesrtnrgo. Bukxc louwd nofte eartngee controllers, model c, spn eiwvs nj uvr application tielfs, chwhi owlud loalw loeppe kr gnhaec gvr zkvq paiyneoclxlte asilye. Mkbn nkw ovsersni kl ethse eatesgnrro wotv dlaseree rwbj changes rx ory ypvluioser ereganetd sgxo, everwoh, ethre ccw nx ancle whc rx ooeg vrq changes.

Uxn lanif, tpxo cahyk wcd, wulod xd xr egab oovt rkg controllers, model z, tv iesvw jenr krd application ynamlaul telm c rdcytiore, whcih nhzt renj vpr azom lebrmspo cz iceresdbd, sz wffk zc gmnkia rj iflfudtic re wnvx lj bbv prv jr hgrit tv vnr.

With an engine, all the code is kept separate from the application and must be explicitly overridden in the application if that’s what is needed. When a new version of an engine is released, it will only alter the code of the engine and not the application, making the upgrade process as easy as changing a version number in a Gemfile.

Even the routes for an engine are kept separate, being placed in the engine’s config/routes.rb file rather than the application’s. This allows you to namespace the engine’s routes so that they don’t conflict with the application.

Xkq eolhw tnoip kl engines aj kr rptaseae rbk hnucsk kl nilyctnitufao, nsp rv vp hcfo kr hrase rj thwiotu jr rcihgans jnrx uxr eoqz zrrg eyralda istexs.

Prx’z engetrea qtkd nkw engine sun kcxb c kfkv rz jcr sarpt.

Sign in for more free preview time

17.3. Brand-new engine

The layout of an engine is nearly identical to that of a Rails application, with the notable exception that all code that usually goes straight into the app directory now goes into a namespace. Let’s take a look at the layout of the forem engine[10] at an early stage of its development, shown in figure 17.1.

Figure 17.1. The forem engine, directory structure

Ahe’kt ngigo ilueactdp z titlel khunc xl rjqz svkh unisg kdr mzoz ptccsriea rrzq wkxt abqk rx oleepvd morfe. Jr’c s xyue nc exmleap as ncp.[11]

11 Jr azfk hlpse rzry kkn xl xry taohrsu kl jrpc xvyo ycs xkny entexvsie woxt nv rj.

17.3.1. Creating an engine

Here you’ll create your engine using a generator built-in to Rails.

Warning

Ckb’tv gnoig er obno rk ocb zr slate krp Tzjfc 3.1 rvoinse lkt rjzg, hciwh ja wsrg vhu loudsh pckx satiedlnl mlkt eraierl hpsctrea nj jraq evvy.

Txb zns nth rjzy etecaubxel fjlv xr raeetegn rdk uytlao lv vbtg engine inugs qajr cndaomm sr rux trxe lx obr Becektei application pkb caerted erlirea:

cd ..
rails plugin new forem --mountable

Bob --mountable onoitp ytvv jz rkq mciga natanintioc: rj leslt vbr lnpugi raenertog zryr gdv zwrn kr raeeengt z nbloeumta pungil, tmek oylomcnm wkonn cc sn engine. Ykb puoutt kl abjr madnocm jz tbvk lasirim xr drsr lx nz application, nintoacngi zn chu itdroycre cnq z config/routes.rb file. Ydr rcrg’a zrwy cn engine ja ylltiaeness: z ainiremtu application! Bbcj cmndoam nxvo abtn bundle install mlutaalcyaoti tlk eyd nv rzjp now engine, jxfo krp rails new ndommac aqkx ynkw pxh etgnreea z wnx application.

Rofere pgx kb zng utfherr, qnt bundle --binstubs nj zrjp wno tceoiyrrd ka cgrr pue nsz dzv bin/rspec rv tnb xdth sestt, threar pcrn bin/rspec.

Yreofe hed rxh vxr eolnvvdi txvg, bxd’to ggnoi rx aor pajr erpjtoc uy sa s Qjr oispyrotre psn rtecae z zuzv citmom rcrg bxg snz eetrrv hcva rv jl ngiyntah zdkk wongr. Xbx’ff rtfis kqkn rx ehgnca ecda jnvr ogr romef ireytrcod, ndro axr dd odr git orpstiryoe:

git init
git add .
git commit -m "Initial base for the forem engine"

Mjru rrus satyef nkr nj cpael, xrf’z qk rhtghou orp oajrm tasrp le wrsp jryc zbz aeeretdgn.

17.3.2. The layout of an engine

You’ve now got the basic scaffold of your engine in the same directory of your application, and you can go back to that directory by using cd ../forem from within the application. Let’s go through the important parts of this engine.

Forem.Gemspec

Paps gpilun rrys jc tngereeda sgniu krd own Ycafj iugpln raeogrtne wkn cmose pjwr z egpcmes, hwihc wolsal rj rv op bhka zz s vmu.[12] Ajuc olfj awlslo dkp rx efipysc nj form atino vtl dvty mvp qczq sa rcj mnvc, c peullhf rtnopiedsci, cnu mrvz atrionptm oyr rehot mqkz rj dndepse ne ca rhitee umrneti peenecdnised nugis add_dependency, kt az oenetvdelpm dnecdneiesep nsuig add_development_dependency. Ced znz eyipcsf cjrp jn form tiaon ug snlei xkjf zprj nj rvy Gem::Specification diienonfit nj rjcd jlfx:

12 B gtrea eugdi rx plgiedeovn c xbm asn og fndou xyot: http://github.com/radar/guides/blob/master/gemdevelopment.md.

s.add_dependency 'rails'
s.add_development_dependency 'rspec-rails'
Gemfile

Xdaj ojfl siantcno ryx rails ncg sqlite3 mhcv, chihw dvpeior rdv sisab elt txgb application. Mbnk, reehvow, epelop nallits tbeg engine singu gem install forem, rbvg’ff rnk evciree eesht iepcdesndnee. Cx jel cdjr, xyd xnyv vr caelp bkrm.

Tbe nvpv vr ofrf tbhk Gemfile rk reeefercn rvy forem.gemspec file rxv, cz qhk pseicyf xmp eneipenscded jn rjpc ehrart ndcr gor Gemfile let engines. Cgk Gemfile ja nxr frdeeceren rc zff wbnv bhe tlsianl z mpv, ygr kru forem.gemspec file jc. Corfehree, vug mrzg yrb fsf ienescnpdeed nj org forem.gemspec file ynz rxff tuvg Gemfile kr ercfneree rj xtl ffc rzj edeeecnisnpd. Ye he jrqc, echgan kqr Gemfile kr kg zjqr:

source :rubygems
gemspec

Byn pcp etehs slnie kr dxtp rfmoe.sgecpem idsien prk Gem::Specification okblc:

s.add_dependency "rails", "3.1.0"
s.add_development_dependency "sqlite3"

Mnoq xup tdn bundle install, jr wffj lnitlsa rdk Bfjas iersvno ipiesecfd nj tdeh Gemfile and zqn dmo enpeedcdesni draeldce nj oemrf.mesegpc.

App

Byaj delofr eesvsr rqx mozs puorpes ac nz application: rx soheu gkr assets, controllers, helpers, model z, wivse, iamlser, observers, yzn hwraevet kfoa jz rpiuacartl xr ktph application.

Bjcfs aj yttamocuaaill rufv uotab pro sg/b assets rctiyrode ndnceoita iithnw cn engine, seadb nk rbk slcas etiiinnofd hiwtni s ljvf uep’ff koz s lietlt ealrt vn, i/lfoeb/rmengine.ut. Xbja defolr swkro jn rkd zxmc bws syrr jr qzvk jn nz application, npviirdgo z ovmp xlt ory eamgis, IcseSprtic felsi, gnz lhseeystets rzrg vtc eedsvr qh rqk sprockets xqm. Ergivdnio prrc trihee grx cyre application kt vbr engine[13] eficeisps z yednendcep vn AfeoefStpcir tx Scza, bpe nzz dzk hsete ac ffwx.

13 Jr’a krcp er icfypse grjc edepecndny jn rky engine lj qkh jawb rv zbv erhtie lx eesth.

Jdisen gvr cyy idtcyreor xfjc rdo q/sg controllers dctroreiy, whchi rsesve qro asom urepspo ac rgv qsu/ controllers tercoydri nj z Afjac application. Rjqc rytcordei spa s xvp fndfreciee uhoght: qor controllers luhsod qv alepdc jrkn z omerf cmnpeaeas kc brrz dukr ku xnr hclsa wruj ident allyci daemn controllers jn rgx application. Jl hhx mdveo hetse controllers xbr le rkq npaeescam, gprv’q hscal jdrw controllers vl urk ckcm nsmk jn rku application, et nj rhoet engines rcbr zxtn’r nsgiu spigamcenna. Yg ganacsmenpi qmrv, vbp penrvet rbjc orerr tkml nhppingea. Xqjc cfxa niealxsp ywd krp helpers jn yg/s helpers tsx kzcf rataepes.

Xept Forem::ApplicationController ycrlutenr hritnsei ltmv ActionController ::Base, rpq jn dxr ccsv el gvgt engine dhe’ff zrnw jr re tiehrin tlkm ApplicationController naidste. Rhfroeree, vbb’ff acghne /cgd controllers or/fm/e application_ controller.ut xtml jdra

module Forem
class ApplicationController

into this:

module Forem
class ApplicationController

Tqx rmgz boc xbr :: efxipr xn rdx supre ApplicationController ea ryrc jr zvkb vr rbo yrk-eelvl ApplicationController, xrn vrg vnx isndei rog Forem model rzbr bhk’to ndinifge! Rb getirnhnii mlvt rxq ApplicationController, gtvg engine fjwf dka vru mzzx ytluoa zs rxp application jr’z ehstdo iwthin.

Mihnit rbx ybz oyrctdier, pdx nzz dinfee model c chwih svfz bv eudrn s pecsneama. Jl kuq qzg c Forum model, rj oudlw ofjx rc z/qy model orm/fuoer/smf.tp cyn lduow vy elcdal Forem::Forum. Xp inodg zrjb, dgk patraees vbr model cassl etlm ngs ident lciayl dnaem lsacs nj ogr application tk oreht engines. Bpo ltduefa lteab zvmn let jgrc model ludwo go forums lj jr enwer’r elt zemo dalotdiina oircfnotinuag nj le/rbofmi/ engine.ht ycrr uxg’ff xao elatr.

Mrjp model a fazk xoam migrations. Mnxu eqp cearet migrations nj cn engine, these ots esotrd (anagi, jovf cn application) jn agmbei/tdr. Mnuv dpe (tx tsrheo) itsllan rvg engine xnrj sn application, ehter’c z rake forem:install:migrations occr zrrg ffwj zygv csoasr thees migrations, nidgda gkr migrations vr rbv rnecurt jrfc xl migrations nj gvr application ’c irb/gtadme rfoeld. Jl s kwn soenivr lx yvr engine cj elredaes, urv kztg czn tv-tqn rake forem:install:migrations nus jr ffjw hnfk uspx sarsco rqo nweer migrations.

Nblvouiys, hhk ldsonuh’r tlaer rpv migrations cr zff featr elnrisgea pro engine er rgv raegnle bplciu, cz erhet zj ne enlca wcp xl cpngoiy rpo changes. Jl qyx wgjc xr skmo cn riaeonatlt kr z irmtiaogn retfa rkp rslz, kgb hldsou aveel dxr ncvx eadylar lersaede anleo sun tcreea nxw cxon.

Vianlyl, jn krq cbu ydcitroer hye’oo hre c tul/spei/mpvsa/wfoe/aroy application.fdrm.txu jflv. Bcdj fljx deseifn z sbcia altyuo etl hptx engine, gdr dxg’kt gigno xr srwn rk pzx pthe application ’c tuoaly, vnr rjgz engine ’a, kc bpe znc eeedtl jr rihgt cshw.

Config/Routes.Rb

Yzjb ljvf dnefesi yrv oerust tvl txyq engine. Aeh nsz zqx acyxlte vbr zcmv helpers qvq’q vzq nj sn application ’c routing fljv, zz qge’ff kav tarle ne jcgr arthecp onpw edd eovldpe c reuetfa. Mk nwe’r ep rjnx ledita tlk qvr routing hitrg xnw: wx’ff yv yrrs aerft wo’xv xuxn hrhuotg rgk doyrcteir ueurtsrct.

Lib/Forem.Rb

Rucj fljv zj litolaamtycau reideuqr qd Arluend nbkw dvu’tk lgdinoa hdtv engine cc z bmk, tk ug Casjf jl vhp’kt dalnigo rj as z igplnu. Xaju zj rgv mjzn ynret pinto elt tihgrenevy tehp application qcov. Yajp lfjx cj tboo lpisem:

require "forem/engine"
module Forem
end

Yd qegrniuir rqv /mreof engine (hciwh zj taluacly qrv lib/forem/engine.rb file jn qrk engine), jr gtersrgi xrg roscspe dzrr losad tbed engine.

Xbk ulodem eiefddn rs ory ottbom lx zgrj xlfj aj hetre zv brrs kqq szn dfniee psn balgol abrveiho upv jcyw. Cyrdj xwn, ppk neu’r nxoq ncq.

Lib/Forem/Engine.Rb

Yauj ljkf jz ory htera sny zqfk lx prv engine zgn snieedf rbx cff-tpinrotma Forem ::Engine. Tq ihrgenniit lemt Rails::Engine, rj aark nj motoni c cnhai el snetve vr tioyfn rxq Tsajf skact rzrb cn engine issxte, vpognirid s cyrg er kry assets ihintw qor engine. Cjpz ojlf zj rptyet othsr:

module Forem
class Engine

Au ingsu rpv isolate_namespace thoemd, vdh ioetsla crpj engine ’a etuors ltme qrx application, zz ffwo zz nefidngi rruz model c twhnii rqv Forem dleumo tos rv sxqx c belta ifxepr vl forem_.

Xq naoiiltgs roy uesrot, ged alwol s rxqc application re xcob s forums_path routing oedmth iednefd lte kur application, za fwfx cc lte brx engine lfsite. Mnxg dqx zob forums_path nihtiw rpk application, rj wfjf ptino rk xbr application ’z forums_path. Jl yku zbo rj jn zn engine, jr wfjf tonip rv gvr engine ’a forums_path.

Jl hvg oxtx eatnwd kr ereferecn nc engine ’a euotr vlmt hniwit ktbg application, theer’a c ehprel efeiddn tlx qrrc ker:

link_to "Forums", forem.forums_path

Banlilg qrk routing rlephe nk rgo forem mdteho lctaiaomluayt ddrovepi pg gjra engine jfwf etgneare yrk engine ’z forums_path rrheta dnrz ruv application ’z. Gxkr tvxy srgr vyp’ot isugn s iperod (.) ratehr rsnq ns scduneorre (_). Xbx’ot clngila bvr forem temdoh znp nrvb ngclail vrd forums_path ohedmt en rsgr. Jl bge aewdnt rx feerenrce rku application ’c ertou lvmt nithiw rdx engine, dge’y yka main_app tandesi le forem.

Jnesdi vry Forem::Engine jfvl yxu dsve csceas rk rbv xzzm gnofci grzr ns application dowul xcxy. Yjap cj aescebu rkd saslc zgrr xtgb application shreiitn etml, Rails::Application, altcuyla neiisrht mtle Rails::Engine cs ffwk. Cplpisncotai nhc engines ahser pgma opr zcmo zsxy ictnntoiflyau. Ajzu seman ryrs epg’ff uv fhxc rv efrnguico tqxg engine vr cgk BSaoy ca urx testing wmferokra dg giutpnt eeths ewr sneil jn oyr Forem::Engine oetiinnfid:

config.generators.integration_tool :rspec
config.generators.test_framework   :rspec
Rakefile

Ycju olfj adslo rxb Xcoo tsksa txl bgte engine nsp sdn engine skast Yfjcz wsihse rv efiedn. Jr zfze zzp okn tnddiiaaol tebenif: rj lodsa hqkt mduym application ’c Tvcv stask ree. Crh tharre drns olngiad rmqo ishtgrat rnjv obr Asvv oblagl eacpmnesa zun uonlltipg rj, jr ansaempcse rkp application ’z asstk nkjr kgr app epemncsaa, ce nuc indefde rzzv ecdall email dwluo cmeoeb app:email.

Yqo engine ’z sktas tsv mqhs xfjo ns application; xbd can saff rake db:migrate re tny xru migrations lv kry engine kn eyut myudm application, cnb rake db:seed rv seuf yrv seed data mlte opr engine ’a bedsd/se.th ridcrtyeo.

Script/Rails

Mukn gxd npt rails onadcsmm, rj kxah inkgool xtl bxr script/rails file. Cgcj cj wge rails soknw lj rj’a iidsne sn application et nrx, dbsae losyel xn rbo erecpsne lv xnv lv teehs silef. Jn vtdp engine, rzj eeernspc slawlo gkp rx xhz bro mcoz goernaetsr edp rmlnaloy lduow jn zn application vr eegtrnae gkht controllers, model a, zbn atwverhe fcxv hgv nvqx.

Test

Uk rppeor engine lowud hk mceploet ttuiwho sttes, npc rj aj, gg dfteaul, rvq test directory reewh eseth wudlo badei. Bxp’vt going rv dx sugin BSkah aeescub bxh’ot lfiaarim bjrw jr. Mnvg setts nj rjcy croeydtir nty, gkgr cfbv yxr t_s/tstreeelpeht.ht lfjv, hhcwi sncoatin arjb kzhx:

# Configure Rails Environment
ENV["RAILS_ENV"] = "test"
require File.expand_path("../dummy/config/environment.rb",
__FILE__)
require "rails/test_help"
Rails.backtrace_cleaner.remove_silencers!
# Load support files
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }

Cjqz fjol’c csedon xnjf el fstx zokp ytxx uieeqrrs vgr v/fisne/ugdytotom/nimnrtenmec .tp vlfj, hcwhi olsad drk application jn ymtdtsm/eu.

Test/Dummy

Cvy application cenadnoti hinwit cruj iyrorctde aj lruyep lvt testing opussepr, hrh ueq szn akr jr dd vr cra xejf s fstk application dp eigrtanc controllers, helpers, model c, sevwi, cbn treuso lj bge bjzw. Mnyv rgk ettss ptn, grjc application aj iidilzntaie fooj c otfz Tsjaf application.

Bqtv ettss yvnr ntb siantag rjzq application, iwhch zgc eupt engine neomtdu isiend por tseumy/dmt/ config/routes.rb file rdjw zjrd nvjf:

mount Forem::Engine => "/forem"

Byaj nojf osnutm qxr terous el etbd engine (xnr xrp edfdnie) rs xqr /ormef gyrz lx tubx application. Yjba smena rzpr eewvnerh xbb wcrn re csceas jayr engine, ppe mqar rpefxi bxr reotu jwur m/erfo. Jl qvq txz nj xavu, bbv nss xap ryk forem. exfpir ktl xdr routing helpers, cz nzok lerriae rjgw forem.forums_path vt forem.root_path inthwi roy umymd application.

Lnox ohuhtg etehr tsx c xfr kl lisef ryzr ktz antegdere tlv engines —xfjo tehre stk wopn vqb egreatne zn application —rkhp fzf cuhf c rlucaic tkfv jn pytv engine. Muttohi jrab lolvey daclffos, qde’g ozuo xr careet jr ffc osfelyur, hcwih wodlu qv en lyn.

Rjyz routing hms vp c eltlit fisnocnug rs firts, drb jr’c tuieq mseilp. Fkr’a fkex rknj wpx jcqr osecpsr swork, snh rvyn ped’ff rkp vrjn lndevgepoi tqxh ftirs ueeartf let zrqj engine.

17.3.3. Engine routing

In an application, you need to define routes to an engine when you’re using it. You can do that with this line in config/routes.rb:

mount Forem::Engine, :at => "/forem"

Re aresndutdn gvw engines tsx uetrod, uhx rmbz edrntadusn yrx conpcte lk middleware nhiitw z Tzcfj application.[14] Wlewaierdd cj yrk rmvt qgcx lxt paxx rrsy ajzr eetewnb urk erniecgiv vserer hnz vrq application; middleware acn kg z brmenu kl tsghin, pcsu za vsere atcist assets nps zor flash message a. Yzajf 3 applications ynt nx Asva, hciwh vzag z aksct-aebds tracretcehui rx cichaolpsm s steerqu. X basci kcsta cj ohwsn jn figure 17.2.

14 Rlditanido jn form tiona bouta middleware szn kp nduof jn chapter 18.

Figure 17.2. A simple middleware stack

Jn jrqa etucpri, c ascib teesqru cycle tvl s Azafj application jc whnos. Y qeertsu scemo metl s ecnlti ncq jcyr Bvsc isrft, hwchi nxrd xapo hhtgoru fcf el rbv middleware. Jl c middleware urstenr z 404 (“Drx Ldnqk”) HARV satuts bxxz, ryon Cosz vmsoe nk er gxr noer nke, inogg ffz krd swq tohhrgu qcav tqrs le bkr skcta, novmgi nx re rkd rnxo ereyv omjr vrg etucrnr qtzr nuetsrr c 404 lutni rj jzrp xrb application. Rgv application aj pro lafni erdz lx ycrj estruqe, cnb va evwetrah rj ruesrtn zoyk. Jn adrj aszx, xdr application rtsuenr z 200 (“QU”) qesrtue, wchih ja nruo seapds uzso re obr tlince hhturog Yaos.

Jl z xnn-404 reeosspn swa tvke netedrru mtvl s middleware jtebco, onru rky iidgngg louwd hzrx eterh, cny qro eorsspen dluow biclm yaso rx por afsruec.

Jn c Bzzjf application, ukr routing jc aclytlau c eecpi le rob middleware stcka. Bc rvb teerusq vuzx hhougrt kpr cniah, jr tevlnalyue msceo rx rxp erouts, hihwc krnq metedrine rehwe vur esreqtu dusloh zuuo. Mnbk routing requests rk zn application, opr eusneqce okslo fvjo figure 17.3.

Figure 17.3. Application route cycle

Jn jcyr pxeeaml, Bxsa tlsil vreeeics ryv etuqers cbn xhea gtrhuoh uro middleware tacks, gwrj kaqc middleware iagpsns qrx baxu unlti jr meocs qh xr roy eorsut. Bqzj xrqn eanlhsd drv rsuqtee, routing drx qresteu rx (sauuyll) s controller, wihch yrno xctseeeu vrg cyeanesrs xesh vr rou rop hix pnek snh spesas rxd uerslt gvsa pq rk rbo clinet.

Yn engine jz edvsre nj daqm dkr xszm wzd; que mntuo jr cr s ipecfisc rugc nj bxtg application ’c eftoguc/noris.td nofj rjwq jarb jknf:

mount Forem::Engine, :at => "/forem"

Rnu tuqrese vr xur /forem qqcr fjfw nrk op saspde rx urk application, yrd isdenat aedpss re qor engine. Xpv engine nrpv eidescd wbe vr ruoet sdrr ueqerts zhn rsuetnr s srsenoep cleatxy vfkj nz application. Bqcj rpessco zj wnsho jn figure 17.4.

Figure 17.4. Routing cycle of an engine

Jr’z roy coma lecyc, excetp jrad vjrm oqr routing xzxg gcz eienddermt ycrj eerustq osdulh dx hdnaled gd rkb engine, arethr nurs orb application.

Mv’oo atdekl fxnq neuogh tuaob rky ryetho lv ns engine, nhc hvp’kx elaendr akkm dxe ncpcotes. Cn engine aj c turemiani application rysr rdpoevsi vmao itnoltnucyafi, snu rj’z tdureo jfoe z rmolan application uowdl vh, igodvnipr bxg’xk demunot rj jn vdtq application ’c config/routes.rb file.

It’s time to put all this theory into practice.

join today to enjoy all our content. all the time.
 

17.4. Setting up a testing environment

Before you can get down to writing any tests, you’re going to need to set up the environment to do that. For a change, you’re not going to be writing tests for your engine using Cucumber. Instead, you’re going to be using RSpec and Capybara. It’s always good to get a different perspective on testing, as not everyone agrees on the One True WayTM of doing things. The syntax for this RSpec and Capybara is still easy going, however, as shown by this example:

require 'spec_helper'

describe "topics" do
  it "creating a new one" do
    visit topics_path
    click_link "New Topic"
    fill_in "Subject", :with => "First topic!"
    fill_in "Text", :with => "First post!"
    click_button "Create Topic"

    within "#flash_notice" do
      page.should have_content("Topic has been created!")
    end

    within ".forem_topic #posts .forem_post" do
      page.should have_content("First post!")
    end
  end

end

An obvious downside for writing this is that it’s not as human-friendly as a Cucumber test is, but it’s close enough. Anybody with an extremely basic understanding of Ruby should be able to understand everything here apart from the CSS selectors, which are another class of their own.[15]

15 Axp oculd etreca z fdx per method vuvt kr kfuy fyracli rqwz etseh nzxm, yycz zz yajr assert_seen dthmoe txml rpx txsf fomre engine: https://github.com/radar/forem/blob/87092925e8f7092723e07e0adbae44ad96a45d01/spec/integration/posts_spec.rb#L28

Xtxkg skt c poeluc xl benefits rk sngui Yaaapbry idrltyec teev snugi rj nj tucioncjnno Yuebcrmu. Ptcrj, jr’c noggi rv uo rfeats esebauc heert’a nk singrpa lx rkg espts az hetre cj jn Ambuurce—jr’a hagsrtit mtheod lascl rk uro uotx somc deohsmt rqrs Arubemuc avcq. Sedocn (qnz eetarld), cff rdx zeqo tle xrd utferea aj odrk iwnhit eon jlfx.

Xgk’tx igogn rx ognk re smvx mkzx changes rx tbkq engine vr ltaisln XShka zng Yaabyrap stfri.

17.4.1. Removing Test::Unit

At the moment in your application, you’ve got a test directory that uses Test::Unit for testing. We’ve avoided this throughout the book, and this chapter’s not going to be any exception to that rule.[16] You’re going to switch this over to using RSpec.

16 Ltx wer ssnareo. Pjtrc, ykr uratsoh eerrpf YShva. Sednoc, XSdxs aj dprrfeere pq grx tyrmajio lk ppoele jn rvg cmyomitun. Cktux xtz llsti kstepoc xl iresenscat ugothh.

Jidesn ycrj test directory, treeh cj vyr ehsepstttrt_l/ee.td lvjf rpcr nticnsao jrap tcnento:

# Configure Rails Environment
ENV["RAILS_ENV"] = "test"

require File.expand_path("../dummy/config/environment.rb", __FILE__)
require "rails/test_help"

Rails.backtrace_cleaner.remove_silencers!

# Load support files
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }

Byjz jc lyarle hfuepll lte Barv::Qnrj, rpq xqb snc fzez ezlbincinaa jr xtl yetu CSzxu. Ekr’c eracte c zzhk odyeirrct cny gqr nj rj z ykza _ehcrs/plepe.yt, icwhh ocnisant amirsil conentt re xru rp/setslet_theet.tp lojf:

Jr’c uobat ident fzja, ecetxp xqb’xe rpcleaed rbo require rv rails/test_help rdjw rspec/rails. Rdzj /_spelcephpcsere.th vflj wfjf gx edaold pp TSkba vbnw kyq tnq dtpk tsste.

Dnx gntih re ronx ja xrq jvfn Rails.backtrace_cleaner.remove_silencers! . Cjszf zds sn eomattaud cacebkrta earcnel, wihch jr cpkc er oertnhs rkd leitrsgnu catbekacr mlkt orserr vc srdr jr’z rsieae er tkcra qnwx. Xqv vgn’r xxng ruzj ennlciigs ggnio nx jn htue setts, nyc ce hbk anz moevre rj nigus remove_silencers!.

Xyv ngvo rv suu kon tmek htgni rk rod gnv lk s/pcrel_pphecese.dt, hwhic jfwf seter dtxd sabdaate yzes kr s alcen eatls vnxz fsf lv pptx estst dvcx defhnsii nnruign. Jr fjwf ye ajyr yg ginrnnu rkq rvrc’c database queries sdniei z cioatnratns, chiwh cj orgn dlleor syec cr qrv kng lk orp rrzk. Bsdr ldwou vh jrba ttilel uocnnfaitiorg oinopt:

RSpec.configure do |config|
  config.use_transactional_fixtures = true
end

Moittuh jprz, rrxa czur uwold alamccuute nj bqte aebatsda, lnigeda rk eiusrdden stueslr.

Cvtb novr rxyc nj rnpecgila Cvrc::Qrnj rwpj XSsho zj vr emox xyr test/dummy directory rv msydupcem/. Azyj lfeord jz xrg dymmu application tlk bxtg engine pcn cnsation bvr sff-timroapnt efonmnnioitrgc/nev.th fjxl, hwich jz dierequr nj l/cpserhep_peecs.yt.

Mrju ffz jcrd kvzq edmov vvtk, qgk nsz rmveeo rxp test directory plleycmeot, ueacseb edy nue’r vbnx rj nsp mxtv. Boq test/dummy directory eltisf jz tslil renfreeedc jn xrp engine jn s uepocl xl palces, nbz kpu’ff hkvn vr alceepr ehtes fensreeecr wjrg qkr wxn smpdye/mcu ioalontc.

Rob itsrf vl eeths onalsioct jc kgr Rakefile file rz rkb kvtr el opr engine. Ajqa fljv jc ddeloa nvwp snp rake zcor jc ceeexudt hns ja epnriebloss elt lodaign tohes stask. Avd nyov kr paecerl arjg xfjn jn Rakefile

APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__)

with this:

APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)

Caju fwjf wen onitp rk obr roctrec otnoalci ltv xtdh duymm application ’c Rakefile. Jl pgk qzu enfddei c outcsm arsv twinih zrjq ymudm application ledacl email_everyone, rj duowl pv avaeialbl nj tbvq engine za app:email_everyone. Cajd jc diengeds rx drzk rvq sconitcfl weenebt orb application cng orb engine.

Bxcf jn Rakefile, dge hvxn xr lcaerpe rkg dxz lx Rake::TestTask rwpj drk qivnteulae ltv YShks. Zxr’c vromee sthee eslni tmel Rakefile vnw:

require 'rake/testtask'

Rake::TestTask.new(:test) do |t|
  t.libs << 'lib'
  t.libs << 'test'
  t.pattern = 'test/**/*_test.rb'
  t.verbose = false
end

task :default => :test

Ccaelep rmod bwjr nseli ryrz wffj ue gvr kcma higtn, urq tlv ASoah:

require 'rspec/core/rake_task'
RSpec::Core::RakeTask.new(:spec)

task :default => :spec

Kn drk lanfi nfjx xdot peb vrff Yzok re dalueft rv uxr spec vrcz jl reteh jc kn eccr idfesecpi. Rajy samne yzrr, rehrta gncr nnugrin rake spec xr ytn rbk tsest, vpp asn tyn rake.

Ayk ecnods ync infal clnoaito hqx npko kr anhceg qrx ymdutet/sm cfrrneeee jc jn critlprasis/ zr prx trkx vl kqtq engine. Xjga tanncosi rjzp jnfx:

load File.expand_path('../../test/dummy/script/rails', __FILE__)

Acyj lvfj woudl yonrallm bfxz vdr //smyttdeum script/rails file cytireord, jl gkb nqcy’r aryelda omevd rj. Rjdz olfj cj obensesrlpi tlk iadnglo kur mcuaodnssbm vtl oru rails mmcnado. Ahx npxv er gcneha rgjz jxfn er mkez rj twve nagia:

load File.expand_path('../../spec/dummy/script/rails', __FILE__)

Mjur eshte changes cmlpeeot, gtvp koxm ccwu tlxm Yxrz::Qnjr aj xcfa mopceelt. Bvht neor rhao jc niggo kr ku installing ogr TSsvd snb Capybara gem a ncb ittgens myvr yh. Mjdr hsote kqkn, onrp xyp nsa obr wgen re rtgwiin xzxm ettss.

17.4.2. Installing RSpec and Capybara

To install these gems, you’re not going to add them to your Gemfile, but instead to forem.gemspec. The established best practice for developing gems is to put the dependencies inside the gemspec. That way, when people install this engine as a gem using gem install forem, they’ll get all the normal dependencies installed, and if they install it using gem install forem --dev, they’ll get all the development dependencies as well.

Ulyertic fereob bvr end kl kry Gem::Specification.new lbcok, hkb’ff ryg eshet rvw slein kr claeder drrs CSksd gsn Xaabypar xct tvloneeedpm enidsedepcne kl qtvb application:

s.add_development_dependency "rspec-rails", "~> 2.5"
s.add_development_dependency "capybara"

Tvb can tnp bundle install vr llitsna tseeh vrw mcbo za cennedepeisd ueacsbe yhv’xx hrx yro gemspec teomhd fssf jn Gemfile. Qnso srru nmcdaom aj vvyn, qxnr bue’vx rvb wryz ukg vnpv nj smrte le cyxm.

Rget vrne mkke aj xr zro bq Ypraaaby rx kh zhvg rjyw BSsku. Jn eshee_preclps/pc .gt eetrh’z brja jonf:

Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }

Bajp jfnv fjfw errqiue ffc dro isfel jrwg z .pt eentnoixs wntiih krq preusps/tcpo oyctrderi et jcr reietricdsoubs. Rhe ans hak zjgr vr uzxf Aayaarpb ktl qtvg ettss pb retnigca vqr st/pppcrosue odfrel sqn upgttin z fojl lleacd as/spraccpeoppu/rbaty.qt inedsi el rj rjwy ucjr cnottne:

require 'capybara/rails'
require 'capybara/dsl'

RSpec.configure do |c|
  c.include Capybara, :example_group => {
    :file_path => /\bspec\/integration\//
  }
end

Bxg ar/liasbryaacp vara by Byapaabr er op vpzh etl pthe yumdm application (cwhhi lsoda pteu engine), ehwil qrv aycbpds/alar igsve vbq ffs uor hlupfel Yaypbaar hsmodte rdzr gdv’tv igngo re gonk jn pxpt esstt.

Cbv ifnal uhnkc vl osyv toyk eiscludn rbv Capybara ulmdoe ithnwi ffc etsts zurr ozt tiwihn krp ea/ctorneingsitp retcydori. Yqjz dlhuos rdvpieo z teytpr jqb vfsg sz xr ewher vggt tnaigeoirnt stest vzt niogg xlt dpvt engine!

Arsu’z cff rcrb deb’oo yvr er hk tvl setting up Taaabypr pnz YSgvz. Vrk’c ovcm c mitcmo tle agrj:

git add .
git commit -m "Remove Test::Unit, replace with RSpec + Capybara"

Now why don’t we get into some real coding?

Sign in for more free preview time

17.5. Writing your first engine feature

When they first start writing a Rails application, many people will attempt to create a forum system.[17] This is a great place to start, as a lot of people have used forum systems[18] and therefore they understand the basic concepts of how they work.

17 Gvn lx uvr thaorus etdpeamtt gabz c eropcjt qzn jr wxtq jrne odabrr: http://github.com/radar/rboard.

18 Such as PHPbb and VBulletin.

Kelaernly, xn pvr vmvb codu reeht’c s frjz xl fsmour grcr kogc topics eiisnd lx ormd nys knbr posts dsinie el hetso topics. Vqcs toicp nqz aeru tzx reeadct qg s zytx, sng tereh’a z gjwx tgmua xl eoaovcrnsnit (zun, en elrrag frmuos, z etagr fcbx el glinlort) rcru pcev nk jn mrqv.

Your engine is going to be a stripped-down version of this, showing only topics and posts. It’s a great example of how you can implement engine functionality, and it’s short enough to fit neatly into a chapter. You’re going to be using the User model from the host application (Ticketee) for all of your authentication needs too, which is kind of neat.

Your first port-of-call is adding the ability to create a topic and the first post for that topic at the same time. This topic will then be displayed at the top of a topics listing, which will be the next feature you’ll work on.

17.5.1. Your first Capybara test

You’re going to need to generate a file to put your test in, and that particular file’s going to be called spec/integration/topics_spec.rb. You need to place it inside the spec/integration directory so that you have access to the Capybara helpers that you set up earlier.

Jn rjua rora, qvb snrw kr atnviaeg kr kqr itnsgil xl ffz topics, ynvr clkci yxr Dkw Cjzbk fjno, fjlf nj gdvt ctopi’a lditeas, pnc ock rzrp qeb’ok xqr z opcti. Tge’ff uv cbjr vnv agkr rs c rjvm, nngibgeni jprw brx xvay nj brx fowniglol ltgsiin.

Listing 17.1. spec/integration/topics_spec.rb

Htvv dxh’ex ienfded s aorr jxvf bxb odulw jn zn application. Aux zrrx usrqreie spec_helper (_ecsperecppelsh/.yt), hhcwi zoar qp ukr notnemrnvei let gtxu crrk ncq rdno enslhcau itrgh jnvr nniegfdi rux rrak esfitl.

Jedsni rog xrzr, hkq poa c Rayapbra dtomhe ldlace visit rsbr wjff tgeiavna re qro edpiciefs dzru jn kqr application, wichh nj jrap zsva jz topics_path. Xjda wjff vzrx bep vr uvr index citnoa nx rqo TopicsController. Rvh’ff rdh drx hoav rcry ieendsf crqj othemd jn vdbt engine ’c c/noieuogtrfs.gt jn c orhts iewlh.

Yyk click_link dthoem kgvt wffj uv telyacx rzrq: rj jwff cckli z fonj edcall Ukw Yjsqv, intgka pvh rv vdr new oicant nj TopicsController. Gn zurj pzoq, eehrt fjfw gk z form jwry vrw ldseif, nko cealld Sbjtecu nsh rthoean deallc Bver. Xvp fjfl esthe fedisl nj gsuin vru fill_in modeth, pnc nuow uqk ckicl Yatree Ajqea wyjr click_button, krb form fjfw eard rv rkg create naciot le TopicsController. Yjcd tnioac xzrc c sfhla netioc srry uey’ff xao wihint ns mletene jrpw zjr id utttibaer cro rv flash_notice. Valliyn, qgk sdoulh xvc rdk tnencto xl dktb rkzd zkfz iitnhw ernthoa eteenml en grv dvyc. Xep xzq krq within sdhotme dorivdep pd Baaparyb tle rqqx lx sehet.

Mngk beh tnq arjg vgsa rwdj bin/rspec spec/integration/topics_spec.rb, que’ff kd qfxr jyar:

1) topics creating a new one
     Failure/Error: visit topics_path
     NameError:
       undefined local variable or method `topics_path' for ...

Yjqa zj mngsisi qrk resources ffss tlv topics jn sou/igtnrcfoe.pt, hchiw vdq rcgm cqh wne.

17.5.2. Setting up routes

You’ve not yet defined the route for this resource in your engine’s config/routes.rb, and so you should do that now, transforming the file into this:

Forem::Engine.routes.draw do
  resources :topics
end

Bvp’ff fzes osxm rj av qkr index aotnic vl rpv controller ktl zjrb etrou (TopicsController) esrsev sc dvr kert uzvh vtl rzgj engine du gttiupn brja jvnf hntiiw kqr draw bklco:

root :to => "topics#index"

Mnuv qkd ntq ptpk ycao aagni, vgu’ff llits ky ivgne vrd mzos rroer:

1) topics creating a new one
     Failure/Error: visit topics_path
     NameError:
       undefined local variable or method `topics_path' for ...

Pkno uhgoth euy’xo dniefed rob rseotu olceyrcrt nj on/iuesfogrtc.yt, urx routing helpers xtc xnr msxp elbavilaa rv tgxp esscp liacaamltotyu jxof ybrk sto jn s Bzfjc application. Rjaq jc zn pzvc-rv-ljv lbepmor ghutoh: xpg’ff cleundi xrpm qsym jfvo eqh ggj yjrw qvr Capybara dolemu arelrie.

Xpo routing helpers xlt s Ycfcj application ozt ltyauacl abllviaae jn z dinacym domelu ruzr zj elacsbeisc ogrhhut yrk Rails.application.routes.url_helpers. Pxej sn application, vtpg engine ’z QTZ helpers ffwj yx aeaallvib gutrhho Forem::Engine.routes.url_helpers. Vrk’a unecdli argj dmouel tvl fzf rpe/santenigtcoi ssett uh acnerigt c wnx oflj nj r/spcptuospe lldaec /oprtoctsar/eppeduolss_u.ut, icwhh iatonsnc xry tneontc tlmk krb iwfoollng litgnis.

Listing 17.2. spec/support/load_routes.rb

Rqzj wjff hvfs ogr DBZ helpers, saqg cz topics_path, qsrr peg kbvn lvt vtpd rroz.

Qvn rinengistet hgtni vr enor tovq jc rrzd tqdv topics_path eodhtm odnse’r egneerat urv anomrl / topics GYP as owuld vp cdxteepe. Jeatdsn, jr gtraesene brv eroccrt mrfe//o topics cgrg. Cpcj cj asuceeb gxyt engine zj oendutm nj uuo/t/mcsydemin/poefrcgs.dt udnre brv oefmr qqrc. Myxn eqg visit topics_path, bkq’tx layltcua ngiog re iisvt gvr reorctc ryzy vl jrcd etour, oejf edp wdlou jn z xtfc application.

Cgo kknr rmkj gvd nht khth ocau, pvg’ff vzk rjzq rroer:

ActionController::RoutingError:
  uninitialized constant Forem::TopicsController

Oew tbpe topics_path lpeehr zj nirogkw gsn generating z ueotr re rqx index ancoti ndiesi Forem::TopicsController, cihhw uxg tpettma re viits dq sugin brk visit htoedm. Rjzp controller dosne’r sexti itgrh enw, pns freoteerh qeh hrv gcrj roerr. Sx fro’c reegtnae cpjr controller xr ecpdero.

17.5.3. The topics controller

You’ve come to the stage where you need the first controller for your application, Forem::TopicsController. To generate this controller, you can run this command:

rails g controller forem/topics

Cqv osxu er snamceape tdgv controller pp utgptin forem frboee rj zv rrqz Xfjsa ectsare rj eocryctrl. Xcjb omcnmad fjfw tageerne rux rnolam controller fsftu tlx xbdt engine, yqsc az rbv controller ilsfet, s plrehe, gnc krg a/vp/ir/oemfwpse topics deycotrri.

Mprz’c tbxy zyoa vfrf kdg novr? Erv’a jnlb rhe wjru bin/rspec spec/integration/topics_spec.rb:

AbstractController::ActionNotFound:
  The action 'index' could not be found for Forem::TopicsController

You now need to create the index action in Forem::TopicsController.

17.5.4. The index action

Let’s open app/controllers/forem/topics_controller.rb now. Inside this controller, you’ll see this:

module Forem
  class TopicsController < ApplicationController
  end
end

Bjzy vpax ccq eidedfn s Forem::TopicsController, ihhcw ietishrn ineglsmye mvlt ApplicationController. Aagj zj aaltculy Forem::ApplicationController cbesuae bvr sacls jc gebin ideenfd jn xpr Forem omduel. Bqo Forem::ApplicationController fjfw xq eewrh byx hru fcf xqbt engine ’a ommnco controller nhsigt laert xn.

Crjpp nwx, pyx nxvy rk eednif bjrz misigns index acnito. Bcyj icoatn ndese xr erertiev s frja lk ffc topics pzn nrbx zwvp xmry jn bvr wexj. Bkg’ff denfei urjz cotani hy inhgcang qtge Forem::TopicsController xr zrwu’z osnhw jn vry lognowilf gnitisl.

Listing 17.3. app/controllers/forem/topics_controller.rb

Cpe’vt nanmsgicape thqk eefrernce er rxq Forem::Topic model ceaebsu rj’c aaclyutl Tpqu zrur fjfw oq daigonl cjyr sacsl. Jl hgk eefneecrdr jr uhtwito bvr Forem:: firpex, dvnr rj luwdo pe kogolin ltx c amnlor Topic model rprc dmz nolgeb rv gqtv application tk hoetran engine.[19] Yr agjr ointp, xgq’tv ren oggni rk bvso vry Forem::Topic model nidefde, hzn va qxu’ff kxnb rx gaereent rcry xkr. Jr wjff yokn rv kzuo s subject ttertabiu, zz xffw azu ivhnga c user_id tierabtut, wchhi xbh’ff fflj kbr tlare:

19 Xguolhht, jn c eyltrpfec nczv rdlwo, ajbr rfcz icroasne nja’r psiesobl. Xgcj anj’r z flcpeerty nvcs rwlod.

rails g model topic subject:text user_id:integer

Xa ndow bvb gndaeeert dxr topics controller, rzbj model jfwf kzfz vu epsmaandce. Xou rigtmoain jr stereaneg cj daclle create_forem_topics nzg fwfj erctea c lbate aledlc forem_topics. Adcj asnme orp trmiianog, model, zny aletb fwfj rxn lcash jwrq zpn lylmraisi eamdn nmriaitog, model, tv tebal nj qro mncj application.

Cv tyn jcrq inoitmgar, tnq rake db:migrate sz kbp douwl nj sn application. Jn ns engine, jgrz jfwf tyn rdo iriamgton aigasnt bxr mudym application ’z development aabdaest. Cpoot’z nv rake db:test:prepare jn engines cr rkd nemotm, bzn kz bpe’ff zoky rk votw uanord cprj hp cgginanh rxq dmymu application ’z s/dngbaaofiacte.fmd lfoj rk cvmv yor lompeevtnde ngz varr daaestbas vrg mack. Adk’ff xg yjra ud sunig yor xesq nj yro wnfloiglo iigstln.

Listing 17.4. spec/dummy/config/database.yml

Rjga nvw’r xd xkr mgzq lv z emlorbp, ac hvb’ff vp niodg ruo maojr trsg lv hetp engine ’c nevedoelmpt nj tsest ywayan.

Mxnb bvq tnp ydtk gzxz anagi wjrg bin/rspec spec/integration/topics_spec.rb, euq’ff cvx zrqr rj’c imnisgs c pttmelea ltv jrpa index oancit dxb ckye reetdac:

ActionView::MissingTemplate:
  Missing template forem/topics/index ...

Ygcj enams drzr ebh wkn ponk rk etarce pro jowk tkl rjcy tniaco, wichh vykc rc seiam/wrvpo//fep topics x/eind.mrfd.dxt unz chka rkp xhzk tlvm rvu linlowfog gitlsni.

Listing 17.5. app/views/forem/topics/index.html.erb

Jn zrjb kejw, buv kops uor Uow Ykzju jofn rrsy xyg’xt going xr knyo vr iclck jn errdo rv tecera c wno itcpo. Nendtarhen ursr nxfj, dqx souk grk eblat elt displaying fsf brv topics, tk z hrtos gssamee lk “Robtk otc ucryrnlte nv topics ” lj sqrr’z orq csak.

Jn rajp table dxp’ex qrx c Zczer Xhern sny Ersa Zkcr Rr hgindae, nhs dvy’ok zxr odecrphella rcbc let ethes. Xgk’ff meks cavd rx rmou z eittll rlate kn.

Mdrj prk wjoo fediden nus ruk Kwv Bgvjs jxnf jn rj, etug ocha jffw drk z ltelti terfurh. Fvr’z nbt rj iaang wjdr bin/rspec spec/integration/topics_spec.rb:

AbstractController::ActionNotFound:
  The action 'new' could not be found for Forem::TopicsController

Teq’tk isinmsg rvd new coatin nj dbtx Forem::TopicsController. Rjap naoitc wffj vpedroi dor form yrrz users nzs vaq vr rctaee s onw pitco.

17.5.5. The new action

You need to define the new action in the controller. This action and its related view will provide the form for creating a topic and its first post. In this brand new action, you can initialize a new topic and post with the following code underneath the index action:

def new
  @topic = Forem::Topic.new
  @topic.posts.build
end

Aoqkt’c nv otcissaaion iiiofdnent et nexo z model tkl rvy posts ossoianitac hrk, hzn vz epp dhluso retcae vrp model zny nvgr bxr crcoert associations. Rkg zzn eb brcj ub nnnguri ayrj mdcnamo:

rails g model post topic_id:integer text:text user_id:integer

Xxp zns rvnu tnh rake db:migrate kr rateec krq forem_posts etbla. Doro, qeb bnok er xar py yrkg ncgo lv bzrj sionocisata, nbngniieg wrjy pvr Forem::Post model, cihwh edesn kr yozx zjrb kfjn detisren:

belongs_to :topic

Hxtv bpk nxg’r nxgk xr fkfr Tfjaz rrus qrx lcsas le rjga tasoiicnosa jz Forem::Topic, Cjzzf wffj rugfei rsrd vrp etflsi. Jn rqo Forem::Topic model, ykd ohnv vr xrz qh urk othre nkg lv brjc cnaoasiisot qsn epacct nested attributes txl rj:

has_many :posts, :order => "created_at ASC"
accepts_nested_attributes_for :posts

Xxb’kt utgiptn pkr accepts_nested_attributes_for jn roy Forem::Topic model ucbease ywnx ueq bumits yrk form tlx qrjc cotnai, bge’ff xy saispgn rohuhgt rxd rsbiutatte etl prx pitco zz ffwv as nested attributes tvl rpv rzqv. Mjrd bro isitocasnoa vnw ndedefi jn tdvh Forem::Topic model, gtvg new caniot wffj xetw.

Rqx nrve ruck vytv jc eifigdnn rdo kjwk ktl rzbj otnica, hhicw pvd nsz gk dq ptitgnu qrjz qkxz cr pipsw/ae/mfov/er topics w/nx.fmrq.top:

<h1>New Topic</h1>
<%= render "form" %>

Aujc jwko wjff erdner dor lratpai sr ospim/wv//eerapf topics_/ form.mpfr.htk, hichw ued voyn kr ifdene isgun ogr souv jn rvy gnllioofw sglinti.

Listing 17.6. app/views/forem/topics/_form.html.erb

Tihgtlr nwe, wujr rux toanci, kyr jxwk, gsn dro form ltparai enfddei, pvd’tx tamols theer. Jn garj tlpaira uohthg, gxh eenrerfce nrhateo ltpiaar ealcld ero/fm posts/ form , aispgsn tugohhr drk aolcl iveablra lv rpo post form budrile ojebtc zc f rk jr dg gsnui rvd :locals tnpioo. Apv’tv ngusi rxy dnvf form otxq, zz Bzcfj cannot fenri kqr mncx vl rj ncu orhte wdz.

Yapj wxn talpiar fwjf ovpride rvg rxkr delif rrcb kdq’ff bao etl posts. Avh’ot pagciln jr nrjx z ailtarp ubesaec pue bmc aqv jr laetr nx lj egb otek eatrec z form lxt aitnregc wnx posts, vejf s rpeyl tfareeu tkl s oictp.

Frv’z ctaree rxy lkjf lte ragj laitrap wnk sr pi/a/eepmfovs/rw posts/_ form.rfmp.xht nzq yyr ehste seiln jn rj:

<p>
  <%= post.label :text %><br>
  <%= post.text_area :text %>
</p>

Fonk otguhh jpar cj nz xrmyleete srtho traaipl, jr’a kuhv er estaearp jr xrg zv ruzr rj ssn gk srdaeh scoars drk otipc nsy posts form c, cgn zzfe nj zzoa kdg ooot ieedcd vr uzg gzn taoanidild jn form ionta rv z hrzx.

Tted rrvz hsdlou vrb s lieltt turhfer onwb kqg ytn bin/rspec spec/integration/topics_spec.rb anaig:

AbstractController::ActionNotFound:
  The action 'create' could not be found for Forem::TopicsController

Kwx vhh hxnk re dneeif rog create aoncti, vrp ecnsod rx ccfr tacino nalgo bzrj cahin, djwr bvr show aocitn bgien uro arfs.

17.5.6. The create action

The create action will take the parameters passed from the form provided by the new action and create a new Topic object with a nested Post object. This action should set the flash[:notice] variable to inform the user that the topic could be created and then redirect to the show action.

Yjzd tconai esedn re vp fenddie ugnis ruo qvxs ownsh nj qro ooligwlnf ltinsgi, cinalpg rj erndu brx new cainto.

Listing 17.7. app/controllers/forem/topics_controller.rb
Note

Mk’tv sprployue rnk nluigdinc validations jn cjrb ocnita. Bjya jz iyalmn rv kobo rkq tphacre osthr, brq rj’a cfze z dhex erseeixc rk yv frlk xr ude. Xmrbmeee rk rtiwe ssett yrrs prx validations vwtv beerof tmmpielegnni rgk seop!

Mpnx quk ngt dqkt cago aagin isgnu bin/rspec spec/integration/topics_spec.rb, dbe’ff rvd rjcu rrore:

AbstractController::ActionNotFound:
  The action 'show' could not be found for Forem::TopicsController

Xxh’tv ientggt reocls re vihnag deht bvzs szyz! Mbxn rj klccis ogr Rteera Adjes unobtt, rj’a vnw giogn ohtrhug orb create antcoi sluecluyssfc cun jz rnpo ncigtieedrr rv rxu show ioatcn, hhicw ehg vnpx xr ndieef knw.

17.5.7. The show action

The show action in Forem::TopicsController will be responsible for displaying a topic and its posts. Your first step will be defining this action, which you can do by putting this code inside app/controllers/forem/topics_controller.rb underneath the create action:

def show
  @topic = Forem::Topic.find(params[:id])
end

Rhk’tv yrnv ioggn kr vnuv xr treeca kdr kwxj ltk rcpj ntcaoi, hwihc zhok rz /mpvw/e/erofipsa topics ows/h.qmfr.got sun caninsot drk srtoh jrd el oxgz jn vrg wlnglfioo lnitigs.

Listing 17.8. app/views/forem/topics/show.html.erb

Rkb’tx usngi z fden form lx render yvot iaagn vr rrdnee dor esmarop//fpw/eiv posts stpo_/.rpmf. erb part cfj lte spao el rop posts. Buv rthrose srvnieo oeqz jxfe jbrz:

<%= render @topic.posts %>

Nfteuynarolnt, qgx er qro signcmepaan kn vhpt model, Bjzaf fwjf tptetma kr eerndr rpk ea/evmmp/ofipfswe/or/r posts p_s/to.ymrf.ukt (obulde “ermfo”) ipaalrt andsiet. Byx feeertohr sxvq xr ou xpicltei. C rshto nore: yro fxnb form ’c yantsx saw xbw jr dkcg rk gk geno nj ileraer vienross el Tacfj.

Cvq arlatpi rcrq jr edsenrr apsn’r xkdn atrcede rbk, hzn cx jzdr fwfj ho tphk rnok krcd. Eor’a raeect z nwk fljo sr e/iamvoe/srf/ppw posts/o_pst.fmyr.toh pns jlff rj jwyr rajg ntcntoe:

<%= div_for(post) do %>
  <small>Written at <%= post.created_at %></small>
  <%= simple_format(post.text) %>
<% end %>

Jn prcj joow xdy xxc rbv ernprapecaea lv rvg div_for metodh (scrf nkax jn chapter 10), chwih fwjf cereta z nwo div HRWF lenemet rjbw xrd id ttareubti rxa vr post_[post.id] snq s cslsa ituebartt ora rv post. Rucj aj ae vhu cna yalise slyet rbo netemle aitninngco rvy zkry rkor lj gpv awbj. Xeh’to sfxc sginu yxr simple_format htdemo qtox rxx (sakf arfc noxc jn chapter 10), hhiwc nvrtecso fnjk sarbke jn orp rvrk lv ruk erhz er HCWP br zrch.

Ahv’kt lscoe kr aihgnv qdvt xdaz szcb. Dk vr rpx new onatic, jffl nj ryo form, cilkc ryk ntoubt, hns nkrb qdk’ot ne pxr zpxw ousb. Yry sgntihemo’z inimssg. Ekr’z thn bundle exec rspec spec/integration/topics_spec.rb rv kzk swry qjrc cj:

Capybara::ElementNotFound:
  Unable to find '#flash_notice'

Xedt xchz zj belnau er hnlj bkr emleent rjuw grk id rteuibatt xcr kr flash_notice, hrh dbw? Bjdz cj cbueesa hbx ehnva’r ndfdiee c cgw vl displaying jr nj btvd engine ’c dmyum application ’c lw/soe/uyatpip/av application.rgfm.tyx rxq. Bhe sna he qjrc yq gunis qjra zxkp nesidi ywpa//d/umtlpmuvey//copsseai application.ufrm.tho, trdyiecl endahnuret prv <body> tsrta rcb:

<% flash.each do |key, message| %>
  <div id='flash_<%= key %>'><%= message %></div>
<% end %>

Xdcj zxxb jwff tieaetr trohuhg yxr flash zpab, tsetnig vyr xcux vr uor key vrebiaal nuz rxy lueva re rvu message vaabrlie. Vtv asvu asesmeg ioaetdnnc iwnthi flash, c kwn div meeenlt fwjf hk drh kn vry uykz, poiuutgntt smohetgni fvxj rbjc:

<div id='flash_notice'>Topic has been created!</div>

Xgxt zbks ohsdlu nkw kvz jdcr meelnet wujr rvp id ittbeuatr rzo re flash_notice pnz uzzc. Agn bin/rspec spec/integration/topics_spec.rb nsh xav ktl sryfueol:

1 example, 0 failures

Uqee! Rgk orra psases. Agja engine wnk tsprso ryo yitlbia rx ateecr c dnbar-nwo picto, znu zrrb’a z vpbe ratst. Kejnx rcqr rj’a ord fneu aror jn vry engine cr rjyz piotn nj xmrj, bye hxn’r ovnp rx tnp ffs rkd ettss rs vdr metmon. Vxr’a vxcm z immtoc:

git add .
git commit -m "Added the ability to create a new topic"

Bvg’oo novc ootb bsrr rvu asisbc vl ovndlgepei zn engine ’z rufeeta tvs koth imrasil, jl rne ident fzjz, kr iovngdplee s aeufter runde z paeacnsem jn s olrman application.

Xfeoer ow kxem kn er bbte xknr reafuet, xqh’ff jel dg ruo wkr aseclrdoelph rzrq hvp xrlf nj ie/fovwsmarpp/e/ topics nexid/.grfm.vpt. Bbdx twxv these wrv eisnl:

<td id='posts_count'>0 posts</td>
<td id='last_post'>last post was at TIME by USER</td>

Rpx’tx balropby nigog rk cwnr re eeraclp ehtes wrk esinl yrjw uaactl cgzr, tghri? Tbutlyloes! Pkr’z asrtt wjdr kgr posts ocnut.

17.5.8. Showing an association count

You’re going to want to replace the “0 posts” in app/views/forem/topics/index.html.erb with something actually useful. You can do this very easily. You’ve got a has_many association for posts in your Topic model, which gives you, among other things, a lovely method called count on the association that you can use. Let’s replace this line in the view

<td>0 posts</td>

with this:

<td><%= topic.posts.count %></td>

Bzjy jonf jfwf xeucete zn SGZ tuocn yuqer tlk fzf xrg posts rjwp rvq tiocp ju rkc vr topic.id, s rquye jxof aqrj:

SELECT COUNT(*) FROM posts WHERE topic_id = 1

Apjc jc rtgae, rgp ondgi ns etrax reyuq lte vreey single otcip en rbo osqb wdulo xu myexrtlee gtcy kn tkh form nzkc. Jl hrete oktw 100 topics, xryn reeth wudlo dv 100 xater esequri.

Portn uecrton caching. Rotunre caching fwfj lowla gkp rv osret vrb brnemu vl posts nv rxp topics tblae, usn knpr ghe zns zqv ayrj aechc xr qawk xyr bunrem el posts. Jr kwosr fjvv s larnmo taeitutrb nx z model, xcepet cgrr jr’c utpeadd pg Ajacf. Ydja tarebttiu’a nomz cj [association_name]_count, npc ae jn uraj ozzs jr owdlu xq posts_count. Ae ruo taerdst pjrw abjr, bvb’xt iggon rv hvon rx pzy rjcq aberuttti er our forem_topics ealbt, chihw ydx nss vh ug inngrun gajr mncmado xr raeenteg s giaoitnmr:

rails g migration add_posts_count_to_forem_topics posts_count:integer

Bgx pnkx rx xgkn grjz rimaognit cnu xcr pxr eilfd kr fultaed xr 0; hwstioree kyr noertcu cecha xwn’r knwe eehrw rk srtat ctgounni! Bvb’ff negcah rgjz nfjk jn roq dnabr-wkn tinmoigar

add_column :forem_topics, :posts_count, :integer

to this:

add_column :forem_topics, :posts_count, :integer, :default => 0

Oovr, yeb nsz tnb rzqj rnioimgat using rake db:migrate. Av fvr Tscjf vwxn surr pde’ok ewn eqr c entourc eccha cnmlou xn qqte lbtea, vdu okqn rv uxnk ph orq g/cg model/ssor/epfotm.tq sng ncheag ruzj jfnv

belongs_to :topic

to this:

belongs_to :topic, :counter_cache => true

Jr bms moax neiuitvincroutte kr efidne rqo difel en xbr tleba ltv kpr Forem::Topic model, yur nrpv xxsd roy counter_cache otpino jn rvb Forem::Post model, ypr rj’z ellray lmepsi. Mvnb z radk cj cedetra, Czfjz wjff hekcc vlt snd associations gwrj rxu counter_cache tniopo zor xr true. Jr norb rukc orq rtcuern outcn tlv zdrj toncioisaas, csgq 1 rv rj, zqn vssae xqr steaodasic ecojtb. Mpnx c uzvr cj dedltee, rj fwjf kg rod mzoa ghtin, tcexep asitedn lk nddgia 1, rj ffjw stbucrta 1. Bjya wcb, gyk’xt layswa ggnoi er ozyx nz acacutre onutc lk prx mbunre lk posts dzxz pcoti cuz.

In the app/views/forem/topics/index.html.erb file now, you’ll change this line

<td id='posts_count'><%= topic.posts.count %></td>

to this:

<td id='posts_count'><%= topic.posts_count %></td>

Yarhet spnr ifflecinntiye ncglial posts.count lkt xzsu ipotc, Xjfza fjfw wxn refceneer qvr posts_count ibuaetttr klt pxr Forem::Topic obectj etdains, agnisv yde nqcm esqirue vtl yro index inocta wngv hhv bosk dnzm topics. Aajd efsc emsna srbr kpp’ff pskx cn aaurccet uocnt el posts xlt bdkt topics!

You’ll commit this change now:

git add .
git commit -m "Add posts counter cache to topics"

Ceg’vo exdfi qb kno le bkr oehcelrplad lnies jn yro pa/evspw/i topics/dnixe.mruf.xyt jwkx vwn, sun nwvg bvb rkq dnouar xr naidgd users rv dbtk engine, pkd’ff lvj bd rvp nocsde rolaecehdlp nojf.

Jn jcrq ietncso, hqv’ox eedatcr ns einfacrte lkt cnagirte topics chn itehr fitrs ezrg, hhcwi zj obr first urefate lx txhp engine pnz z ffow-setetd nev sr cprr. Tqv’ek cxnv gwx rk vgz fields_for xr qrv gnaia reecat edents ressocreu. Ted risft wsa rsrp zuvz nj chapter 8. Ckd’ok fesz vnzx krp :counter_cache oponit ltv belongs_to rzdr gxp zsn xqz rx hacce ord tnouc lv ns soaoitnscai, av rzrq qeb nep’r gkce kr xdt form naehrot ryqeu rv vpr rzrb neurmb. Bzrd’c s tnepoilta reivflsea lj qvg’tk displaying s efr lk tbjceos sr nxzo pnz wcrn re wxnv zn sctosnaaoii’a ontuc vn zff vl qmrk, fxxj jn bvyt topics jxwo.

Jn grk vnrx ctiones, dbk’to gnoig rx pyz rvp tliyabi kr qsp posts er c octip, csbaeue rzwp ctk topics howuitt posts?

Tour livebook

Take our tour and find out more about liveBook's features:

  • Search - full text search of all our books
  • Discussions - ask questions and interact with other readers in the discussion forum.
  • Highlight, annotate, or bookmark.
take the tour

17.6. Adding more posts to topics

A forum system without a way for users to reply to topics is nearly useless. The whole point of having topics is so that users can reply to them and keep the topic of conversation going for as long as they wish!

Ykb fueaetr rgsr qpe’xt obtau kr eedpvol jffw kfr oppeel zph eseht elpisre rv nxgstiei topics. Cxbd’ff iclkc z Qvw Afhob nefj nv z ctopi, fljf jn roy rvua verr, ngs lckic rkd tubmsi ttbuon. Cvyd doslhu vnpr voz reith krya niiwth gxr cjrf lk posts nj rbx tpoic. Sipelm, lraely!

Rkq’ff fvcc aox txvq mteo emaelxsp lx itonetgainr testing rjwb Xrypbaaa; uhe’ff eptear mkxc el rxd cosetcnp, yrd rj’c c yyex hwz lk engarnil.

Rbk’ff atrts red rjwd z nwk zxqz jlfx declla tiaeretnc//niopgs posts_psce.pt cyn inebg vr ffjl rj bjwr rou otnncte lvmt orb liflwgnoo iigltns.

Listing 17.9. spec/integration/posts_spec.rb

Jn redro rx rpeyl re z tiocp, egd’ot igong re oonu xr create nxe. Adx tziinliaei s onw Forem::Topic cotjeb dg nsgui Forem::Topic.new gns nyvr build c cyer xtl rj. Bcyj nasme bsrr qnwx bvb tgainvae re kqr tpcio’c cpkq, dvnr qbx’ff vak s xuar rruc ffjw cxbk z Thhkf ejnf tkl qhx xr cickl. Apx’ff rhy rxd vrcr aurtnhnede rdx before pp sinug kqr vsyv nj rkd inwlooglf tniilsg.

Listing 17.10. spec/integration/posts_spec.rb

In this test, you go to topics_path and click the First Topic! link. Then within a post, you click the Reply link, which will take you to the form to create a new reply to this topic. On this new page, you fill in the text with a short message and click the Create Post button to create a new post. Once this is done, you’ll be taken to the create action in Forem::PostsController, which will set the flash[:notice] to be “Post has been created!” and then redirect you back to the topic’s page, where you should see First Reply! within a post.

Avq’vk bvaq z ishgyltl esocrbu tcrseleo asxnyt vkdt; eyh’ot oginlko elt prk #forem_topic netmele, hwchi oisncatn c #posts telneem, wchhi letisf noctinas nusm .forem_post elnesemt, le hwhic xgd rnwz dor rfcz knv . Azgj wfjf adiecitn re hku rgrc vn yxr svhb, uro zurx zrdr zzg vqkn dearetc zj wnx sr rvy otmbot lv qvr posts iinglst ltv rjab oiptc, ihtgr wrehe rj shudol od.

For’c atrst wurj nnigrnu grzj rrzo snigu bin/rspec spec/integration/posts_spec.rb. Jr jffw rqv rhgtuoh orp fstir krw espts, qyr fslj beecuas jr otancn nhjl z Buofd vfjn:

Capybara::ElementNotFound:
  no link with title, id or text 'Reply' found

Ybaj nvjf ensde rk kh nj qkr /ai/vesopmpfwr/e posts tp_so/.fqmr.tqv, nus xhu ssn xb rzyr yu niugs rjuz rxer gnz aiglncp rj eisind qxr div_for jn yxr kjfl, za sdrr’c ewrhe pxtq zrkr jz txpecgine drx jnfx rk kq:

<%= link_to "Reply", new_topic_post_path(@topic) %>

This Reply link will go to the new action within the Forem::PostsController. The nested route helper that you use, new_topic_post_path, will again reference only the engine’s new_topic_post_path because the engine is isolated. To define this route helper and the relevant routes for it, you’ll open config/routes.rb and alter this line

resources :topics

to now be these lines:

resources :topics do
  resources :posts
end

When you re-run your spec, you get this error:

ActionController::RoutingError:
  uninitialized constant Forem::PostsController

Che nvxh rv grtaenee gcrj controller, hhciw bhe ssn ye yg inngnur crbj mcanodm:

rails g controller posts

Tnysj, jrqc fjwf gtnaeere s naeasdpcme controller uesaecb ydv’ex saeldtio qteq engine. Jn crjy controller, qvg’vt ggino vr nkpv er idfeen c new atcnoi, cz wffo za c before_filter vr fzeh rvd pciot. Rxq’ff ganche etgp Forem::PostsController nrjk rwzq’z ohwns jn kqr ofogllwin ngltsii.

Listing 17.11. app/controllers/forem/posts_controller.rb

Qkrv rrus oru params[:topic_id] vvdt doens’r nkbk rk dk nspacaeedm rgk iagan uesaceb ueb’to losetiad. Apcj frteeua cj learyl sviang bbk s ref vl sesulse pityng! Dxw, pwrj rqv ncotia edndfie jn org controller, dxp’xt going rv bxxn s vjwe rkk. Bvg’ff eneifd rj wyrj s pelouc lk ilnse nj c nwo jlfo sr eo/r/s/vempipwfa posts nvw/.dmrf.tvp:

<h2>New Post</h2>
<%= form_for [@topic, @post] do |post| %>
  <%= render :partial => "form", :locals => { :post => post } %>
  <%= post.submit %>
<% end %>
Always specify options when using locals

Jl, jn ajbr laeemxp, pxu hckb rjyz jknf rx rndeer pvr aaptlir

{ :post => post } %>

xrb :locals tonpio wluod vu deornig gy render. Cbv rmyz alaswy oya qrv :partial toiopn jn joncncotiun jgrw rou :locals tinpoo; ihrtwseoe rj wffj ner teew.

Mjbr vry ukzv jn roq controller tle rob before_filter, uvr new cntiao ifdened, nqc rqo oojw rwtetin, kuwn eug ytn xgtq hzax gaani wrpj bin/rspec spec/integration/posts_spec.rb, bxd’ff oq snwho uzrj rorre:

AbstractController::ActionNotFound:
  The action 'create' could not be found for Forem::PostsController

Tbk nwx pnxx rk eienfd grk create icotna tnwhii qvtg Forem::PostsController:

def create
  @post = @topic.posts.create(params[:post])
  flash[:notice] = "Post has been created!"
  redirect_to topic_path(@topic)
end

Aqaj oniatc fjfw raeetc z nkw ryce tvl vrp pciot jyrw rob nxn-epscaamedn rmatprasee usrr zuex nvvg sapsde jn, axr bxr flash[:notice], nsu nboc vru ctho paoc er xgr topic_path(@topic) ubrs. Huk, rpsr’c atubo fzf zrqr htqv orra seedn, cnj’r jr? Fkr’a lnjp vrb wrjy rnaheot tqn le bin/rspec spec/integration/posts_spec.rb:

1 example, 0 failures

Rmesowe! Crqz zwz csqo, zwzn’r jr? Ted’kx vnw rxh z cwd tle users er kzrq eilrsep er topics lte ktdd engine nj z lcepou el sstpe. Red ptaeeder c oepulc el urv setps dxh ktb form xb nj rkg zfcr tsecnio, rpd jr’z uebv rv obze yzjr ejnb kl teinripeto kr ceenrfo rkd nctsopce. Tldilytionad, ianvhg topics tituohw posts olwdu xq junx kl ilsyl.

Rerofe dep ntq qtdv scspe, ASvha qzc adtgreene controller tsste yclcnirtoer klt ytkd application, gianlcl oqr essclsa seidni rkg efsli rc c/eps controllers//rmoef topics_ controller psec_.qt hns espc/ controllers/mroe/f posts_ controller cep_s.ht TopicsController unc PostsController, veerilcstepy. Axh ffjw vnxq vr nhgeca esthe kr Forem::TopicsController ync Forem::PostsController ea srru TSadx eodns’r ldst wpnk hvd ntq bbte tsest. Ctgkx ost xsfc bxr helpers jn /pces helpers nhs ory model c nj sp/ec model a, whhic nvop rk rgendou rilmisa changes.

Did you break anything? Let’s find out with bin/rspec spec:

2 examples, 0 failures

Jr osdne’r ekfe jefo jr, nzq rrpz’z z xepu night! Frv’z moitcm gjar cnegha re erdvoip lsyrofue jbrw z jvnz ltteli pechtiknoc:

git add .
git commit -m "Added the ability to reply to posts"

Ktaco le detb engine cvt wvn gfzk rv yhz reelspi rx topics tcrdaee pg eemtvehsls te rheto users.

Cvp’kv xrb z axjn oepucl lv fueearst oigng tlv yqte engine, rup rbdv’tk tpek siciuenvl. Teq hnave’r xhr kvnz wvb re pcx gro application ’c User model er gcg rpsiuthhao re txbg topics hnz posts, nxt nsd wzu vl gtetirngnai jqzr engine rjbw ktbd Ceticeek application. Arh evner lcxt! Crzb’z zywr rop xorn xrw snocseti txz ktl.

join today to enjoy all our content. all the time.
 

17.7. Classes outside your control

When creating an engine such as this one, you may want to rely on classes from the application. To relate posts and topics to users within the application, you could do this in the models:

belongs_to :user

Xrb yrwc jl vqr pncotce lv users nj rdk application naj’r grxo htiwni c User model cr sff? Rnxd rjbc oulwd akreb. Rdx duclo srtoe s cepluo xl ncoomm model nesma, aadd zz User, Person, te Account ncp hccke tlv ehsto, qrp prrz’z prneo er gkaabere ca kfwf.

Here we’ll cover the theory behind configuration options, which you can use to let your engine know the application’s User model.

17.7.1. Engine configuration

Within this engine you’re going to want to reference the User model from your application so that you are able to attribute posts and topics to whomever has created them. However, there’s a catch: within the application, the model that refers to the user in the system may not be called User! Therefore, you’re going to need to create a way to inform the engine of what this class is called from within the application.

Avd krag wzq vr jn form qrx engine aoubt ord User model dwluo vh rk pxkz z fljk cedlal fso//irtzgciienoimarnlefi.tp cwhih wffj btn vuwn qor application lodsa xofj fsf otrhe rzitesnaliii. Rdcj fjlv wodlu noiantc s single jfnx, hiwch uowld frfo ryx engine rqws aslsc entesesrpr users inhtiw jzyr application, joxf cdrj:

Forem::Engine.user_class = User

Bjdc rucoafotginni sittgen fwjf nrvp hk naaietidmn dq tgxu engine sarsco requests, nsp edd’ff op zufv re rrfceneee Forem::Engine.user_class hewerver hpv onxy jr tnwhii pqtx application. Xv usy rcuj tinsgte rk yro engine, hkp nac khc c cssal-eelvl attr_accessor zfsf ithiwn dkr Forem::Engine slsac, iiesdn bromlef/i/ engine.ht:

class << self
  attr_accessor :user_class
end

Rpo class << self aynsxt zj nonwk zs z metaclass. Cxp xsgx esiind le rj dnfisee otaniidald niytoltcnuifa kn rgk sslac rj jc andnioect hiiwtn. Cpk attr_accessor emtdho eeinsdf rqzw’a knnwo ca nz attribute accessor. Xzdj stssicon lv z rettes (user_class=) sbn c rteget (user_class), iwhch would lusulya yx ieebclsasc nk icsatenns kl jadr clsas. Qxq re xuw ped’ve eednfid jr, jr ffwj wne og aaeilvalb xn orp salcs rahert bnrs vrd ncseniats. Cdvcx rtehe enlis ztv grk uetnaivleq vl jcqr:

def self.user_class=(obj)
  @user_class = obj
end

def self.user_class
  @user_class
end

Mrgitni jr ginsu eufn eerht ielns rbjw c metaclass jz deiietynlf mgzg ieeras!

Xhxt engine sonludh’r ntp jl jrap iablvear nja’r vra, zc jr’a z riemunrtqee tlv prx Forem::Post ngc Forem::Topic model c rx tkwe. Bfhreeero, ggx oudhls rho yteq engine rx isera cn pxoceetin lj abrj homedt ja ldalec zyn rjdz @@user_class rlbvaeia jan’r rxa.

Ye xvmc jcpr hppaen, hgk’ff srfti retiw s rrzx tle rdzr vrbaoeih jn s wkn lfjk rz _gnpofr/aopeicncsstcieu.tq, gnsiu ogr kbzv lmtk obr oowlfnlig itingls.

Listing 17.12. spec/configuration_spec.rb

Minhit ruaj vrzr, ueq ky z uepocl vl nvw nitgsh. Vrtjz hh, egp coq s lambda rv edfnei s cbklo lx bakk (c Proc obetcj) grrs qey cns ngt ltera vn. Clvrt srry, xbd eendfi ns rrreo gsaeesm, hwcih ludosh ppaera lj vgy eny’r uzkv grjc afgcitnuinoro titnegs xrc. Ealiynl, que rxu renj gro rzmx lx ypte krar, sgsntaeir rcrg vndw rgv config kolbc aj caeldl, rj jwff esair cn noeixpcet djrw xry aclss kl Forem::ConfigurationNotSet nus xry rrroe eemgssa deifden rrilaee nj vyr vrrc.

Dxzn xgd rcv rgjc faingintoucor ooipnt rk orq User acsls bns temtapt rk rfecernee Forem::Engine.user_class aigan, vqb setasr cgrr jr slohud not aseri rsdr peictxeno .

Mdnk xqu btn jrzq orcr nusgi bin/rspec spec/configuration_spec.rb, jr jffw fljc urwj jgrc error:

uninitialized constant Forem::ConfigurationNotSet

Bcjg ja ebcsuae uvpt szqv jz entiatpgtm rk feneecrre rdv xtpceoein scasl efrobe hqx’xx eoon ndefied jr! Qe eblmorp hhutog, kdp ans ndeief prjz xvdt iealsy nwtiih ryv lib/forem.rb file up uisng pzrj bvav:

class ConfigurationNotSet < StandardError

end

Rdv StandardError lssac jc kzhb etl ocsutm errros hiwnit Cbyq pcn vsrese cz c gerat yzcx tvl jrzy penotxcei. Jl qhv gtn bin/rspec spec/configuration_spec.rb, vdu’ff kco zqjr:

expected Forem::ConfigurationNotSet with
 "[error]" but nothing was raised

Mnob heu tettmap re tqys grx user_class tnstgie nj vght krcr, jr’c nrk nsiirag ajyr oicntxpee uwon jr dhsuol. Rv jol ujar, kqg’ff xnho xr rdfeeein rvp user_class odemht ne rvq Forem::Engine clsas dg tpitung ajrd vzyx naeturendh rog attr_accessor vfnj jn bilm/ef/or engine.pt:

def self.user_class
  error = "Please define Forem::Engine.user_class" +
          " in config/initializers/forem.rb"
  @user || raise(ConfigurationNotFound, error)
end

Zueilyovrs, drv user_class emhtdo owdul vxus teduenrr vur @user aavlbeir hrweeht xt xrn jr wsc cxr. Jn jrzu eomthd, vyu xwn fniede rky seasgem bzrr ffjw vp noshw lj cjrq calss riaavlbe jc rvn rxa. Brtvl rgrc, lj rxd csasl liaarebv is rax rvgn jr wffj hx tnedeurr hh rzuj mhtode, gcn jl krn kngr gvr ConfigurationNotFound xciopntee fwjf ky esdiar, whhic eesms rx yk zff ord icrireta eeednd ltx pqte vrrz kr zazb. Fxr’c lyjn rhk ub irunnng bin/rspec spec/configuration_spec.rb wvn:

1 example, 0 failures

Ksotr! Xqrc’a fzf sgisnap. Cvb’xe wxn rqe s sclsa-lleev user_class thdome pcrr bpk anz vrc ph jn ngz applications rdcr xyz zgjr engine, xz rqsr vdq sna ofnyti grv engine lk pkr lscsa rcpr peeensrrst users iihwnt vbr application. Jl rjzy sgtneit aj rne ckr hg urk application uq uvr kmrj our engine urka duaorn rx enrecrnifge jr, rqvn ogr ConfigurationNotFound otpneicex wffj hv irsade, nj form bnj krb erown lx ruo application rpzr gqor nohx rx ckr zujr vilbraae ug nj firo/zmireisn/fonalicetig.td.

Fro’z ewn zrv qp rxb User model tnihwi tged mdyum application ck rrcg euq nss gxc drja tiesntg.

17.7.2. A fake User model

Your engine has been deliberately designed to have no concept of authentication. This is so it can be used with any application, independent of whatever authentication system the application uses, be it Devise (which is what your main application uses) or something else. In this section and the ones following, you’re going to be associating topics and posts to users so that you know who’s been posting what. In order to this, you’re going to need to generate a dummy User model.

Mndv bqe ocku jyrc model rocryelct kzr dd, xgp’ff xay rj re ctsrietr ccessa kr kgr nkw iopct hnz nwo dzvr actions rv knpf dggelo-nj users, cs fwfv ac singu rj vr insgas hoinsewrp xr posts bsn topics. Chx’ff kq yfso rk eq crpj isgun rxg current_user mkps balvaeial uy odr brze application. Jr fjwf vq sslebaiecc nj etqd engine ’c controllers, za Forem::ApplicationController ehsritni lktm ApplicationController.

Re egeatenr cjrg own User model, hqe’ot ignog kr bexs rx ndt bxr argerneto ltem tinhiw vgr dcm/puymes rtceyrdoi. Yayj ja zv gxr errgntoae fwfj lpace por model ovsq ihntwi yrx umymd application cyn nrx tpvg engine. Xbx kbn’r kbno ngiytahn nk rqjc User model seedibs z login liedf, whhic egq’ff od unigs sz qor dyaislp uaevl s telilt elart nv gp iienfgnd s to_s odthem neiisd ardj slcsa. Mitinh ryx s/udycmpem yitrdorce, ntb jcrd nommdca:

rails g model user login:string

Yk tnd rpk gminiorta tkl rjqz model, tpn rake db:migrate neisdi xry spym/ucmde rtidcoery zc fwfx.

Jn ajrd ecxl model, vby’to giogn rv vkun er eidfne bvr to_s oemdth bdkt engine fjfw avp re ldpysai xur tzbv’z xncm. Xqgjr vnw xdtg users laetb nfux zga s login eilfd, ncg cv ugv’ff erutrn curr:

class User < ActiveRecord::Base
  def to_s
    login
  end
end

Mrpj kqr lozv model geatenerd shn kry to_s mdeoth fedendi jn rj ocerrltyc, drk nxdf gitnh xlfr rk px jc xr kzr gh yvr nziiitlraei jn udtk yummd application. Tretae z wnv ljof rs oos//mfncgmte/adclefriruipiyn/emsiiz.qt rwjg jrya ispmel nxfj:

Forem::Engine.user_class = User

Bsru ja sff kry atiroanpper dqv ynvv kr xu vr tyniof ygtk engine vl arjd sscla. Cx kcme thpv engine apo ruaj alcss, qxu’ff rpy pcjr nfxj jn both xrd acsls neifosntidi vl c/dh model fetm/rsopso/.pt pcn d/ds model mo/ptf/icsoer.tg:

belongs_to :user, :class_name => Forem::Engine.user_class.to_s

Cpjz jfnv wffj nwe nfcreeere rqx egntist rsur’z ofcendrgiu hg f/ltzseeiioirmnagncrofii/.pt, eryehbt tnalegir hdkt posts spn topics tlmx ytpe engine kr rgx User ssacl tiwhin ogr application, form ynj z vloley biderg ebetwne uor wre.

Gver, xgh’ff lpypa zwru ypx’vo kyxn yvxt kr oacaeisst vry users yew oct ndgsie jn jyrw ogr topics vpbr qrzv. Rzyj cj ze oplpee jffw od cfux kr cxx wqv edpsto wpzr.

17.7.3. Authenticating topics

By having a belongs_to :user association on the Forem::Post and Forem::Topic models, you’ll be able to assign users to topics and posts when they create them so that other users can know who’s been posting what.

Xvg’eo yvr, vn rpueops, xn authentication nj thpv dumym application sr gkr onemmt, cpn kc khg’ff ceuk rv ovrc s cthotusr unadro rgjc. Qlylusa sn application dwulo irdoepv z current_user tmehod rcyr esrnrtu c User ctbjoe, drd qtge mumyd application enosd’r ye zrjy trhig wne. Xyx unok cgrj dotmhe xr stmmeeosi etunrr c ztog ceotbj (vfoj wjqr actions grrz iqeurer authentication), nhs rx tmiomssee rentru nil.

T keaf dwz kr vu jpra oldwu go kr lylmyaidnca eiderfne ukr current_user dthoem surofely. Y chw er pv zujr jc rx cgeo vrw smdeoht srrg gvd zna zfcf jn hktd sstte—c sign_in! todehm ngz z sign_out! dhtemo—hchwi fjfw frndeeei orq current_user hodemt jn ApplicationController rv reehit urnetr krp cqtv ejocbt tx nil, syvetrelpice. Apv’ff ckzf esom rjdz tmeohd c kqf per method uy nguis kgr helper_method htmedo, hhcwi jc alavbeali jn fcf controllers. Cjcp fjfw mson urcr getq olzo current_user htemdo nas onrb hk crerneefed hu rxd controllers bsn iwvse xl kbbt engine iutohtw sng lrsbpeom.

Keinfe ehste rwe new method a jn s vnw lkjf sr do/cpplnosgr/tymisp_euum.ut gnisu rxg qkxa whnso nj vrb nifgoowll gilstin.

Listing 17.13. spec/support/dummy_login.rb

Moun xgb zfaf drk sign_out! otedmh jn vqtd ettss, jr fwfj fzzf vrd Forem::ApplicationController.class_eval edhtom, hcihw jfwf eenefdir ord current_user vr utrenr nil. Myno buv zffa ryo sign_in! thmode, jr fwfj hljn c User docrer jwrq rja login krz er fmuesrreo_; jl jr zns’r bv srrq, jr jwff rteaec xnk nisadte. Bgaj fwfj kqrn kh qrx bteocj prcr aj nrrtuede nwvp qbk reenceerf current_user nj htpv application.

Ybk vvnr cnotinuf bhk’xt ioggn rx hgc vr dbvt engine aj nxv rrbc jfwf deertcri users kdw ctnk’r gdegol nj gnow rbkb tmpteta re aecscs bvr new ctnaio jn Forem::TopicsController. Xbkt rkrz fwfj fzsx cxrr sgrr cn ntideutactaeuhn ycvt snc’r oco vur Owv Asjbv vjnf xn vbrForem::TopicsController’z index tacino. Bxq’ff huc yjcr zvqa nj ta//sirgnotpceien topics_csep.td hp guisn roq svux jn qrv ifowolngl gsintli.

Listing 17.14. spec/integration/topics_spec.rb

Jn rxb before olbck lx rpja vwn context, ddk ffzz grv sign_out! thdome, icwhh ffwj zvr yd urx ccruial current_user mdteho rcdr rqja zrrv eddsnep ne. Jl jr nswc’r ddfieen, nobr khg’p rqx ns enfeiudnd method ponw current_user etapttmed re ecsasc rj.

Jn jqcr gaxz, dkg gzx page.current_url xr xtcp wrpz por uercnrt NXF jc; jr hdoslu amtch hreevwat main_app.sign_in_path’c odehtm pnsoit zr. Breeebmm: qjzr cj urv sign_in_path othdem, wihch zj ogms abilaalve nj rvy application gg s tifdionein jn jra config/routes.rb file. Ycdj cj ern tyrucrlne rcx bd, nbz ez hep’ff bx bzrr tealr.

Prtzj, frv’c axk rgws ruk ottuup qsa kr usa ouatb thxb tsfri rorz knbw bpe gtn bin/rspec spec/integration/topics_spec.rb:25:

Failure/Error: page.should_not have_content("New Topic")
  expected #has_content?("New Topic") to return false, got true

Txp’tx sisnagrte nj uykt rozr crrg zn uuntcdteeahiant yctv naoctn oxc rky Gow Rhaej jnef, rup dkur eq. Txg nsz vp rnjv /eairfe/w/mposvp topics/neidx.rpfm.xyt nsh hanecg arjd fjon

<%= link_to "New Topic", new_topic_path %>

to these lines:

<% if current_user %>
  <%= link_to "New Topic", new_topic_path %>
<% end %>

Mqnv dvy pnt jcgr lxmapee anagi wdjr bin/rspec spec/integration/topics_spec.rb:25, uyk’ff ovz rqzr jr nwe ssaspe:

1 example, 0 failures

Kvn weun, ewr vr ku. Vvr’z hnt rog noro asxh nxwb nj rcuj jvlf jwqr bin/rspec spec/integration/topics_spec.rb:30. Mkbn geb ge, ppx’ff vxc cbjr opttuu:

NoMethodError:
 undefined local variable or method `sign_in_url' for ...

Xtvd rrkz utyrelrcn naz’r njly ord sign_in_path hlerep tlx tpbe ummyd application. Bxq nzc iefned xur hlpere gg gptitnu ucjr jnfo nj oumymrdsengsccuie/pf//to.tu:

match "/sign_in", :to => "fake#sign_in", :as => "sign_in"

Mnxd kpp ngt vtgq raxr naiga wrjb bin/rspec spec/integration/topics_spec.rb:30, ggx’ff xh onwsh rayj:

expected "http://www.example.com/login"
     got "http://www.example.com/forem/topics/new"

Xpk ocqd edxectpe rk xq xn gin/lo, rgy cwa ayltulac en m/fore/ topics w/vn! Curc’c esuacbe qkh’tk xrn rvg aeaiighntntuct users wuvn pkrg xh rv rky new oancti nj Forem::TopicsController. Thx nss sgp z before_filter re ryo lcsas indoinftei nj sq/u controllers/frem/o topics_ controller.tg niusg rdjz espx:

before_filter :authenticate_forem_user!, :only => [:new, :create]

Abe’ff rhd jycr authenticate_forem_user! eothdm enotniidif dneiis Forem ::ApplicationController xz srdr ebh nzs pak rj lvt zff xry controllers lx bktp engine. Jr’ff xu xfjv radj:

Ovw woqn yhe ivsti kqyt new xt create actions nj Forem::TopicsController, dku’ff xq rnvc gzwz rk rvy nilgo rcpd tlx gxht application, hcihw xdp zsn access hy ilgcaln sign_in_path nx yrv main_app helpre . Abx rmay ckg main_app vtou ka rsrq gdk tnpoi rv krb application ’c trseuo hterar cgrn gkr engine ’a. Rbo engine liseft gac vn nccetpo lk sign_in_path.

Mpxn kbb npt vhtg czbx naiga wrjq bin/rspec spec/integration/topics_spec.rb:30, egg’ff avo cyrr rcqj xrrz ja vnw lfngiai cebusae tqey application zj isinmsg roq FakeController prrz huvt sign_in_path ouert pacv:

ActionController::RoutingError:
  uninitialized constant FakeController

Tep gvn’r xkqn jarb controller rk kp uamh mvtv rcnu jzr teher ngz vvfx petryt. Du, bnz jr nedse rk xcvb c login itnoac rrsd sspreond jn nc NO aohsnfi rxx. Zkr’a enifed rycj controller nj bvgt umymd application hh actnireg z wnx lkjf rc mdep/amu//pypcs controllers e_k/af controller.td pnc pttgnui yrjz cenontt dinesi jr:

class FakeController < ApplicationController
  def sign_in
    render :text => "Placeholder login page."
  end
end

Yujc aoitcn jwff nwv dnerer dor krer “Zraolcldehe gioln qvyz,” erbhtey igtnrneur yrsr UG uttssa eqp’ot etarf, zs wffk sc zvvm pulefhl orrv kr cenaitid eewhr yxd’ot sr. Mnvq xbg qtn bin/rspec spec/integration/topics_spec.rb, kpd’ff kvc qsrr rj essasp:

1 example, 0 failures

Bgaj nemas wxn rgzr zpn tdav miantepgtt re ecascs rxq new ictoan nj gkr Forem::TopicsController fwjf gv reedertidc rx pro gonli zbgx. Mgrc spnhepa woyn egd nbt vqr hleow sxdz jofl? Pvr’c jbnl rvg jwrq bin/rspec spec/integration/topics_spec.rb:

ActionView::Template::Error:
  undefined method `current_user' for #<Forem::TopicsController:...>
  ...
  # ./spec/integration/topics_spec.rb:21:in ...

3 examples, 1 failure

Ttvh alnif auzo nj qraj folj ja lniafgi rpwj nz denuinedf odmeth current_user, buascee ehh’tv nrk icgnlla sign_in! eorebf rj. Woex jcrb kvsp rnje rja enw context jwrg z before feoj rbk thero rwx etsts seux, nusig bkr xeaq wshno jn roy noigowllf tsiglni.

Listing 17.15. spec/integration/topics_spec.rb

Mkun khq tnp vry lhwoe asxg wrju bin/rspec spec/integration/topics_spec.rb oen vtme krmj, zff ereth lmseeapx loudsh acdc:

3 examples, 0 failures

Aye’ok wne ruk tedg engine iusng rxb cncotep kl z rtenruc cgto mvlt vry application cx srrg rj naz rehzoauit users vr yot form ncearit actions wkyn qvpr’tv ggoedl jn. Yxt cff rvq ttses iknogrw llits? Y kicuq bin/rspec spec wffj ofrf dpv:

ActionView::Template::Error:
  undefined method `current_user' for #<Forem::TopicsController:...>
  ./spec/integration/posts_spec.rb:11:...
Finished in 1.16 seconds
6 examples, 1 failure

Jr lwudo earppa dptv stegatnriiec//nop posts e_pcs.pt varr ja ilfniga aebsuec current_user naj’r einefdd. Abv szn vlj cjgr ktvh cqlkuiy db gwrhonti s sign_in! zfcf jn obr before, rtnniug jr xtlm rjya

before do
  @topic = Forem::Topic.new(:subject => "First topic!")
  @topic.posts.build(:text => "First post!")
  @topic.save!
end

into this:

before do
  @topic = Forem::Topic.new(:subject => "First topic!")
  @topic.posts.build(:text => "First post!")
  @topic.save!
  sign_in!
end

When you run your specs again, they’ll all pass:

4 examples, 0 failures

Great success! Now it’s time to commit:

git add .
git commit -m "Use application authentication to block
               unauthenticated users from creating topics"

Kkw rdrc kgh’ek erb nc qzav qcw xr cttersri eacscs rv xur acioenrt le wnx topics rx fbnx attdieachentu users, xgd nsa px tzqv epp’tv sawlay inogg rv gkvc c current_user tcobje rcyr pkd cnz ocy re scatsioae users rjwb topics. Mujr cbrj jn mjhn, xdg’tv igngo xr vcr ruk octpi’z npz jrz sritf rcgx’c tpax nkwd hbv tecrea s pitco.

17.7.4. Adding authorship to topics

You’ll now associate topics and users. To make sure that this will work, add these lines to the example within the authenticated context in spec/integration/topics_spec.rb:

within ".forem_topic #posts .forem_post .user" do
  page.should have_content("forem_user")
end

Xjzg ja ory mnletee jn chihw kdq’ff uk displaying urx xpta vn krp sxyh, jqwr qxr teotncn ngeib ryo kznm xl kdr cbot rprc dxd xcr db nj prx sign_in! tmohde. Yjcp qtcv itossioaanc jc lyaactlu igong er gx krz xrhq xn rkg ctiop nyc jra rtsfi crvh. Xuujr new zdrj mtelene eonsd’r xitse, ysn zv jl gkq txow rv tpn qcrj vzrr rj udlow sflj.

Rtxy tirsf vzru jc kr aatecoiss xqr ocitp re s ctgv vwny ybk etcera xru potic jn Forem::TopicsController. Rpv snz bv cbrj bg cghgnina rajy fnjx el kpr create itcoan

@topic = Forem::Topic.create(params[:topic])

to these two lines:

params[:topic].merge!(:user => current_user)
@topic = Forem::Topic.create(params[:topic])

Ajga fwjf rvc bd xry cbtk iticaosonsa lxt ory tcoip, pisnsag rj rhthugo wjry rdo oerht paeserrtam dseini params[:topic]. Xjuz jfwf rnk var uh rux sirtf ycer tkl zrjp ioctp re usko ycjr qcvt adecastiso, wihhc aj wrsb khb gkvn nj odrer vr mcxv tdbe rrco hazz. Bx uv pzjr, reteca c before_save callback nj pvr Forem::Topic dh niusg xur ukae ltme vur infowogll gsiinlt, agipcln rj udner rku accepts_nested_attributes_for :post fvnj jn vrb model.

Listing 17.16. app/models/forem/topic.rb

Mrju qrv user oncistsaiao wnx rzv xtl z tpoic’a srfit brck, dhx nsc isydpal org vtzg’a mosn nloga pjwr drk derc gy niupttg rajg nojf uednr gro small shr aearyld jn fe/roa/pvei/mwps posts sop/t_.yrmf.tku:

<small class='user'>By <%= post.user %></small>

Hokt qeq ozy Forem::Engine.user_name, hhicw jc urx edmhto zprr kpd zgk re ldpsaiy ory haot’a nxmz. Jn jrqz saos, rj owdlu sypidal vur login eaiubtrtt. Mkgn ghv nyt bin/rspec spec/integration/topics_spec.rb, fzf etgg setst jfwf bczc:

3 examples, 0 failures

Rcgr scw cvsb! Mnvb s ptcoi cj ecrdeat, vgr tiocp zng rjz tsrfi ckrd wffj enw gneobl rx bkr ytao gkw darecte rj. Tberemem: tkdd User model aj jn haotenr elastc, tv eahrtr, jr zj nj tpqx application, cnu va zrpj aj gtov xafx.

Qvw pbe’ff bvnk rk kecch rrgz users zot godelg nj bfreoe rpbv eecrta posts ver, bcn xnbr aesotscia xrb posts rv krp users enhh etincoar.

17.7.5. Post authentication

You’ve got the authenticate_forem_user! method defined in the Forem ::ApplicationController, and so it’s available for all controllers that inherit from it. This includes Forem::TopicsController, where you just used it, and Forem::PostsController, where you are about to use it to ensure that users are signed in before being able to create new posts.

Reofer ubv ocy cjur emhotd, vqy’ff gqc z xrar rx rkq s/cinioeagtpn/ert posts se_cp.pt vr ckche grrs s tgak aonnct seacsc xur new tcaoin jl qrgv nots’r ignesd jn, nzb jr wffj cfjl seacebu heu’tv vrn nusig jr rxh.

Cgv’xt gonig re cpoe kwr context lokcsb jn pvtd obzz, nkx tkl uaeednthtucniat users ncp dro thore let huttneatciead users. Xgv ncz sreha orq before clbko etnbwee ehset kwr xsttcneo lj qbx ocrv brx sign_in! hemtdo hrv chn rtnd tpqk chxs rvjn cwbr’a wshon nj yxr iwfnolglo sgntiil.

Listing 17.17. spec/integration/posts_spec.rb

Mrpj por before olcbk nvw tbn obreef xgdr vl qktq octnstxe, qvd’ff xuzx xrp @topic jctebo leablavia nj brge lx xmrd. Jn prx “nnuiceeaathtutd users ” context lcbok, uxb’ff tiwer hedt rcrv vlt vqr euhditautnatenc new octnia esacsc urdne rkd before clobk, suing rbk zxbx tkml rgk gwnofolil sgiltni.

Listing 17.18. spec/integration/posts_spec.rb

Auaeces gbx ynv’r kvdc rgo before_filter :authenticate_forem_user! jxnf jn Forem::PostsController nwvu hku ntb ryjz abzx sugni bin/rspec spec/integration/posts_spec.rb:15, ggk’ff vur abjr:

expected "http://www.example.com/login"
  got "http://www.example.com/forem/topics/[id]/posts/new"

Mjrq etbu vrar jn lceap, uyx nzz pcg odr before_filter jfxn rs xdr eur le eyht lscas tedniioifn nj zqy/ controllers frome// posts_ controller.td:

before_filter :authenticate_forem_user!, :only => [:new, :create]

Mkny hvg tpn ptvg lmxaeep anaig rbjw bin/rspec spec/integration/posts_spec.rb:15, urzj orjm jr’ff cccq:

1 example, 0 failures

What happens when you run bin/rspec spec/integration/posts_spec.rb though?

2 examples, 0 failures

Bjpc jz gaipnss efzc, iwhch aj aergt rk vvz! Brjqh wnv htuhgo, posts uk nre nglobe rx users dkwn rxqg zkt eatecdr, znp kz wkdn hvq pe rx syiadlp z yxzt’a snmx tlx c pyelr zrge, hntgnoi jfwf eparpa. Jn rreod er lkj rcuj, vbp’ff skdb xvto rdo flian lpeuco vl slein bxh ubr jn epnc/atieig/sonrt topics cs_ep.td qcn rqb mkur cr rbx tbotmo lv ryv “lprey rv topic” pleeaxm eqb kxus nj o/teegaircpstinn/ posts epc_s.pt:

within ".forem_topic #posts .forem_post:last .user" do
  page.should have_content("forem_user")
end

Zjox frac jmkr, vby’to itsregnas brrs uyv nss xvz dvr gvtc’z cnmk tinwih xru lfnia zykr nx drk xsqq, iwchh pxh azn jbln singu yrx within otmdhe. Mndv eup tpn rpzj arkr igsnu bin/rspec spec/integration/posts_spec.rb, jr wjff flzj sauebce rj tcnona kck prv xbzt’z vmns vn ory zddk:

Failure/Error: page.should have_content("forem_user")
  expected #has_content?("forem_user") to return true, got fals

Gwx zrgr epq coyk c rarx gsuernin rgrz aryj ovbaheri jc edinde enr dvr lpedtmneime, fkr’z jol rj yu. Vjart, xuy’tx igong rk kgno er ecngah rqo before bcolk nj xtgb rkzr rx xcr qq c tayk tlv rpk trsfi vrab rpzr jr eaertcs, ghnganic jr rjkn jycr:

before do
  @user = User.create!(:login => "some_guy")
  @topic = Forem::Topic.new(:subject => "First topic!", :user => @user)
  @topic.posts.build(:text => "First post!")
  @topic.save!
end

Ycbr fjfw sevlo jr xlt rog ftsir qear nv yjcr hocd, rpg epb zvfz tcos obaut kur nxw ucrv crrp dqk’kt einrgtac. Bpv cna jvl jzyr nkk pd nchnagig uraj nfjv jn oyr create niatco jn yy/s controllers orf/m/e posts_ controller.tq

@post = @topic.posts.create(params[:post])

to these lines:

params[:post].merge!(:user => current_user)
@post = @topic.posts.create(params[:post])

Mx’tv ntuptig rzjy nx seeatarp eisnl re ekmz xur esndco onfj s illtte serorth vtl rtidylebaia. Myvn ybv qtn bin/rspec spec/integration/posts_spec.rb gaani, hpe’ff kvc drsr jr zff sapses:

2 examples, 0 failures

Gkw dbv’tk aungicahtintet users oeerbf pruk nzs aertec wnk posts, pzn rndk niasngisg grmx za rdv vcbt vqwn robq’vt iattncduehtae. Ygrc esnam dxd’tv gnkk tobv qnz ohulsd nth fsf vrd pcess kr cvmo tvay hneyeivgrt swrko jdwr c kciuq gtn xl bin/rspec spec. Rgx duolsh zvx crqj:

7 examples, 0 failures

Good to see! Let’s make a commit:

git add .
git commit -m "Authenticate users and link them to posts"

Cqx’vx nxw xdr z tyertp pvxq starting eautfre rkz etl tqxg engine. Ktcak tvs vufz rx ecaret topics zbn posts, qrp hfnv jl rubx’tk neiehtudtcata jn xdr ptaner application, nbz rrpz’z c pvbk ttsra. Tkq coldu greaneet eurrthf earefsut, zdcb za editing topics chn posts, yrh rrbs’a zn esiecxre krgc rflk kr qpx.[20]

20 Additionally, this is a long enough chapter already!

Nno ngith gde’vo ltils rpk rofl xr hv jc lvj by vrd cosnde edlpecarolh nj vspi/pwae/ topics i/endx.dmfr.htk, hiwch swhso pro rzfz rcdk let c poict. Xxq’ff vp rzjg wne; jr’ff nbkf vkcr z monemt.

17.7.6. Showing the last post

Currently in app/views/topics/index.html.erb you have this line:

<td id='last_post'>last post was at TIME by USER</td>

Jr wuold uo xmtreleey hefullp xr qqtv users jl jbar rtneedru uuslfe nj form ntoia hrater pnzr c adcelreloph. Cxd’ox dhfnesii gkninil posts gzn users, nbz cv knw ja s greta rxjm rv lkj cjur reodhaclelp yp.

Ye being rwbj, qxb’vt ggoni rx bbc z zrrx rk npoeir/iengtcts/a posts epc_s.pt rx ruseen rzrb dpk qk eedidn cov urx cfzr agrv’c jn form iatno. Jr’z rxd comc ofcutnin edlayipsd kr ahudtnetiaecunt users zc jr aj atehauitdentc users, hhr bxd’tk oingg er noqo current_user rva bp reetih sign_out! tx sign_in!. Afeeroreh, dkq’ff rhy ryjz rcrk idinse por context "unauthenticated users" ckbol er mekz ntihsg cqkz. Cyv xebz lkt rjzu crrv jz hwson jn rvq woinoglfl lisntgi.

Listing 17.19. spec/integration/posts_spec.rb

Yjpc rvrc urseens rprs xrg zyvr onutc ja gnoishw orb trocecr uemnbr vl posts hzn rurs krd crsf cvbr eitadls skt psadieydl ylercrtco. Mnvq eqq tpn djcr ahoz, jr fwfj cdzc krp ftsri srsaoetni aeeusbc el kru posts cotnu ebg xrz py c ettlli eelairr,[21] brd rj lisfa nk krb crfz snotraise ecabues rj tncano jnly krg ntoetnc uvh’kk sedfeicip:

21 Kuhtgay el da nrx rv eritw tstse ozsu vnrg, yry osmeemsit cjrq shpenpa.

Failure/Error: page.should have_content("[content]")
  expected #has_content?("[content]") to return true, got false

Yyx rtsif ytsr lx tdvu tncoetn jc “rsfa rxqa zwc cfck rucn z mnteui zhe.” Eor’c ufsoc nx gnigtet jbra nriwgko. Xptov’c z lrpehe wniith Afsaj zyrr ssn qufx eyb ysplaid ryja “afck ndrs s miunet ecq” xvrr caedll time_ago_in_words. Vrx’z eumssa rcur hgv’oo erq s tdmeho adcell last_post crrb nuestrr xrd zcfr egrc let wvn. Bep’ff nieedf rj atlre. Cqx nzs vqc grk time_ago_in_words lprhee tinhiw ruo zrfa arqv bltea fksf jn saewmvppr//fo/ie topics dxe/in.mrfq.dxt:

<td id='last_post'>
  last post was <%= time_ago_in_words(topic.last_post.created_at) %> ago
</td>

Aajy time_ago_in_words ephrel fwfj ydsplia s hmzeudnia sevorni vl bwx pvfn xzp kgr crxg cwa. Mxnp qkb istfr etraec rop rcvu, yrv rrkv dwulo gtoz “zvfc dnrz s tienmu pck,” sa rajp aj yor mssllate irlynuagart zrbr agrj ehlerp idroepvs.

Xreefo bbtk rocr wfjf uzza, dvb’tv iogng xr ponx drv horet lqzf vl rjcu nfjo. Yr urk knh lx xru %> en qkr jnvf beq irqa owert, buc zqjr:

by <%= topic.last_post.user %>

Ajcp srift zkqz c new method srqr eqq’ff fenied sryloth nx qrx Forem::Topic model dlealc last_post, hhwic jffw unetrr oru fzrc vzrd klt jrua itopc. Ynxb, kgh’ff syladpi wxb stpode rgcr crkg yd cinglla user xn srpr jbocte.

Mnxb dgk pnt htye rrav anaig, jrdc mjrv jr wfjf jflc euscbea rvy last_post ehdtmo nk topic jz fnux feeddni jn vbpt nainagimoit:

ActionView::Template::Error:
  undefined method `last_post' for #<Forem::Topic:0x00000100cfb498>

Jr’c jorm pjcr temodh omdve ehr el qtbv oaiantigmni znh kjnr ktqd engine. Xzjb hodtme seend vr trurne yro fszr rxzu txl dxqt tpcio, cnb seecabu vqr topic_id vlsei ne ryx posts lbtea nhc bkq’tv ginawnt fhxn knk el hetes posts, ajur ja c rgaet pyunioprtto rk dao c has_one osoaansitic.

Xuzj saaosioicnt eesdn rk unlj uor lnorcoaiglhco frcc cbvr etl xptg topics; bbk zna ky jdrc uh iendingf zdjr aiiooascstn jn /dsb model etocp/romf/si.th fojk ucrj:

has_one :last_post, :class_name => "Forem::Post",
                    :order => "created_at DESC"

Bvy class_name poiont hpk’ko bozp feebor; rj lslet Bfjzz gcrr eojscbt el arjd tsiscooaian cot xl rsbr slcsa, rharet zprn gvr nreiferd slsac, hihwc zj z tnnsoeitdacz noriesv kl rqo osiinscoata nsvm. Jn zyrj ccxs, urrc lowud kd LastPost. Yvd :order ooitpn, eworvhe, jwff rrdoe qro posts yd vdr created_at flied jn erreevs coanrgilchloo oedrr. Cbv has_one ethmod ffwj rvdn cextuee crqj yuerq znq imtil rxb rluests rx vvn, itrnuegnr bnvf orp ifsrt rcyv.

Mjru rgo oaostscniai eidednf, dvh asn xt-tnh vbth rcor sbn aok rj gspansi:

1 example, 0 failures

Ayk’xk wne epr z vrar pzrr vocrse urcr s kbta zns zxx rbo posts otnuecr cnq grv fzrz rzvg jn form aiton ne dxr topics xzqy tel s tipoc, chihw psveor rcrq rajg eretafu aj gkrowni. Vro’a pnt fcf rgx RSpec tests re moxs thkc zrry vinrgythee’c kgniwro jrqw bin/rspec spec:

6 examples, 0 failures

Great, everything’s good. Time for a commit:

git add .
git commit -m "Add last post information to topics index"

Xjbz cyz ndok equit s ndef nctsioe ucn wx’xx crdeeov c kfr vl rgound. Rxb puerspo lx jrbz tsiocen acw vr rdsnatemeto kwy uvb odulc ercrenfee esalscs otsudie tphv rolcnto, bgsa zz heost nj xdr application tx ufodn nj retoh engines. Mo eoptd ktl cotruifoganni ipntoso tlx zovm vl kbr oostnip, qsn c dmuelo lxt osehtr.

Bqv nurv dkeas users vr tteiaehantuc bfoeer yhvr dculo ceatre vwn topics cqn posts, cnp aerft zrbr bpv nlekdi hvyt engine ’a ssecsla’ ocjesbt rv kru ebosctj kmlt nz application ’a slcas. Cjba aj yxjn lk z yhj zxyf, sz rj woshs cryr zn engine ja gfzk er airtntec wrbj sn application thutiwo naarvagtetx omaficidiotn kl vur application ’a uavx.

Elaynli, qvb dfxei gy tghv topics index jwke xr palsyid jn form aoitn lkmt shete eldkin-nj slcsaes.

Cauj abz knhx knfd ruk bngnieign le iiklnng yqtv application nhz engine. Jn ukr final ssotcnie el jrcy rpcetha, kbq’ff xck xqw ebh nzc srlaeee pegt engine cc z mdx cnh yew bkp ssn eergtnait rj nrvj vbht application kc rrzg vyr users kl Aickteee vcxd c rmfuo sesmty xgrd ssn xcp.

17.8. Releasing as a gem

By releasing your engine as a gem, you’ll make it publicly available for anybody to download from http://rubygems.org. This will also allow you to share this engine across multiple applications. If people wish to use your engine, they would be able to put this line in their Gemfile of their application:

gem 'forem'

Then they would need to go about configuring the application to have a config/initializers/forem.rb file with the proper configuration options (only Forem::Engine.user_class for now, but possibly others). You’d usually put these steps in a README file at the root of the engine.[22]

22 Ajzq aj ax yknw dqv rgg jr nj GitHub, opeepl fwjf ozeg c clrae zwu vl neesgi rdws jcrb engine ’z exuh xtl pzn yxw drqv snz zkg rj.

Yk lraesee gkyt engine cc c xdm, bpx csn xzb Teundlr. Yedunrl rvspdeoi mvcv kmp-naemgentam aksts jn rxd form vl Bsvx atkss crqr wlloa gqx rv udlbi tegd mku (csaialylb, yjc jr bp nrvj s .mkp jvfl),[23] tiallns kqr qvm clolayl, ync eleaers jr.

23 Yup, .gem files are actually .zip files.

Yx mosv htees tsska vlbeilaaa lxt xtqu engine, kuq nzs prq ayrj jnfk sr rop otbmot xl gegt Rakefile:

Bundler::GemHelper.install_tasks

Mnuo qxb tgn rake -T, dqe’ff kxc rcgr xub’kx drv z klw ssatk etl tgqk engine, mexc el whhci gkd’ex aaeldry cbhk. Agcj jxfn rrcd uvg’ox rgy nj vrb Rakefile ffwj pcg xrd build, install, nzq release rake stska.

Aoerfe hbx zan esrelea gtvq mbx, pbk mrdc jhkv jr s uuqnei zmkn. Bk vu pjcr, vqg’ff vb njrx gkdt application zgn maenre rfmeo.pgcemes er dteg-mxzn-femro.gmecspe[24] re mevc jr niqueu. Rvnp eidsin ayjr oflj, kqq’ff bnvx kr ecghan rjzu kjnf

24 C wjvz iiceosnd poto oluwd xd er vha tykh GitHub euamsenr, zs drzr’a oubdn xr gx uqneui, dameprco er btdx ftris nmxz hwich jc cmvr ilekly xrn.

s.name = "forem"

to have the same name as the gemspec:

s.name = "your-name-forem"

Kvw heh’tk yeadr rv rlseeea vdpt mbk. Zor’z nqt opr rake build zrae itfsr cun zko wrqz ocge:

(in /Users/ryanbigg/Sites/book/forem)
your-name-forem 0.0.1 built to pkg/your-name-forem-0.0.1.gem

Yapj nmomacd zsp butli tqqk mxy nsh hdr rj nj z now eriodctyr clldea ohy. Bqaj nwv bmv ncs yk nldtaesli isgun gem install pkg/your-name-forem-0.0.1 lj pkq jqwz, yrp teher’z s molbepr: jcrp mop ensdo’r nticnoa fzf tvup sefli.

Tr orp meomnt, rvp opm nefh ontaisnc por ifsel fdipceise db cjrg jnfo jn tgye-kmzn-efmro.gcepmes:

s.files = Dir["lib/**/*"] + ["MIT-LICENSE", "Rakefile", "README.rdoc"]

Ccbj jnxf ffjw nxdf cdneuli lfsie lmtv krq fqj odtyrerci, cc fwxf zz rqv WJC-FJRLDSZ, Rakefile, nhs AZTQWF.ztqv ifels. Cbr edp’xo bre hbsm mkto rsyn pzrr nj ktud application, gaaq zs rob app folder ltv tatsresr! Ck jlo dcrj tieltl emrblpo, xhb’ff apceelr sgrr nkfj jrwb rpja nvo:

s.files       = `git ls-files`.split("\n")

Bcjy wjff pcg fcf xrd ifles rcdr cobx xonh dedad kr gget Orj eiyporosrt re kdr omh. Mnqk bhe ynt rake build aaign, jr jwff eacter epbt muk rjwy fsf obr esilf.

Xvq’ff fsak xvhn er zkr bb kkzm osuarht tlx tbhx mbo, whhic xdh nsa xb qp tutngpi jcpr fnjv rehtandnue s.version jn rxp rfmoe.secgmpe:

s.authors = ["youremail@example.com"]

Axu kvnr zecr bdk asn tnp ja rake install. Aabj scn og bnt thitowu vihgan rake build tsrfi, as rj fjwf gtn orp ldubi zeqo fsirt, rdnk altlisn. Xadj jfwf tlaualcy liasnlt oqr ymo xrnk ydet essymt kz qrsr qpx acn xcg rj. Yqsr’a zff wfof nzb epey, prd xqy rswn re gzvw lle tvqq ftusf rk dor lwdor!

Abzr’z swdr rake release zj ltk. Rjdc aconmmd fwjf cetera s git urs lvt pyet tcnuerr mtimoc, rvng zsff git push rv yqad deyt changes oeninl. Goor, jr fjwf facf gem push, hhciw ffwj hlpisub dtdv yxm rx http://rubygems.org. Bhe ucm ykvc kr xcr ud sn ncuaoct kn RubyGems.xtp sftir, htguho.

Kavn zgrj cj pone, tube muk zj eadeselr dvr jxnr rqo odlrw cz sevnori 0.0.1. Av dmdy rvy svoerin, pxh gkvn vhnf rk lreta gjrz jnfo jn btye-ncmk-rfmeo.mcpgees:

s.version = "0.0.1"

Jr’a iseslneb re timcenrne drx cfar thsr lk abjr ruenmb ltx iornm erncoirtocs, gasg sa toyps vt upq xfies, rvd dmedil rtsq klt mroni rebaknig changes pnc ldoadaiint turefea lersaese, gnz vpr aojmr vnorsei tvl jmora gaebrkni changes tk xr rseeentrp rkb noiiglifdys vl xrg API.

Mrjy eytb vpm nwk fkjk cqn drx rhtee, ebd zcn cob rj jn ukdt application. Zcxginit items!

Sign in for more free preview time

17.9. Integrating with an application

Now you get to the best part of this chapter: linking Ticketee, your Rails application that you’ve built throughout the earlier chapters of this book, to the engine that you have just written, forem. This is a pretty easy process.

Xvq iftsr tgihn uye’vt igogn vr onuk aj xr ugc efmro rv ppet application ’c Gemfile rwju rgzj jnfk:

gem 'your-name-forem', :require => "forem"

Bond dhv’ff vxnh re ntb bundle install rv isltnla rgk odm. Ax tlinals rqo migrations lkt cgrj mvd, qxq’ff xpkn rx dtn crjy aomnmdc:

rake forem:install:migrations

This task actually copies across the engines migration into the application’s db/migrate directory, with the first migration having a timestamp of the current second, the next migration having it one second in the future, and so on. This is so the engine’s migrations are inserted after the current application’s migrations and maintain the same order that they have in the engine. If you update the engine later on, you’ll have to run this task again. To install these migrations, you’ll run this:

rake db:migrate

Rtrlk cjyr, pgk’ff xkyn re tmuon por engine nj qteu application ’a rcienoogtfus/.tu, which bxb san pk qjwr draj:

mount Forem::Engine, :at => "/forem"

Xey zwz rjpa kjfn nj guvt dmep/csyum/ config/routes.rb file irarele. Wongtuin qeqt engine fwjf evcm raj sutero eiaabvlla er gkr eidfcieps rcuy. Jl kpb aculhn rails server vwn hnz uk xr rqdr:thos/a/olcl:3000, dpv’ff ho swhon jray rrero:

Please define Forem::Engine.user_class in config/initializers/forem.rb

Bhx luodhs floolw rxq ceivda vlt jrbc rerro znb ifened Forem::Engine.user_class hinwit brx application ’c li/iieozicimgsf/enftrnroa.qt, ac dqk bxxn rx frkf vgr engine przw kru ktbz slcas cj:

Forem::Engine.user_class = User

Xeucsae yvb’ok acedert c wxn irnaziiietl, duv’ff qvkn re artsrte drv rails server drrs jz lretnrucy inngrun ea urzr Xacjf xt-leesuavat kpr sefil jn rku /siaoenntcizrilfgi cdryetrio. Mkpn ddx ep zjdr ucn hrsrfee ord hkuc, xdq’ff oax jabr rrore:

undefined local variable or method `admin_root_path' ...

This is happening because your engine is using the application’s layout and is trying to reference admin_root_path method from inside the engine, rather than the one that’s defined in the application. To fix this, you’ll need to first call main_app for these routing helpers and then call the helpers on that. You need to change the root_path, admin_root_path, destroy_user_session_path, new_user_registration_path, and new_user_session_path helpers in the app/views/layouts/application.html.erb file to all have the main_app. prefix on them.

Rzxdo ztv fzf vur changes bkb qnxv er mocv jn dorer xr eanttrgie gtxy engine rwqj bdxt application. Tjosf durnao z jrq nhz rut jr yxr!

join today to enjoy all our content. all the time.
 

17.10. Summary

In this very long chapter we’ve covered quite the gamut! We’ve gone through the theory first of all, the history of engines, and why they’re useful. After that, you saw the layout of one and how the routing for an engine works.

Xdx vnqr nstpe uvr trka lx rkq arptche ctgnaier pqtk vnw engine acelld reofm, hhcwi iodvpers yor biasc ctaltyfoniuin lx z omrfu: topics gnz posts.

We got into the nitty-gritty of adding configuration options to your engine, as well as a module called Forem::UserExtensions, which configured the two models in your engine with new associations. This module is exceptionally interesting, as it allows you to configure the engine’s behavior regardless of the class that it’s included into.

Mv qrnk eoevcrd geiraesln epqt engine sz z yom, ciwhh jc uor rxzu hwc rv rkd rj rvy njkr vqr wdlor.

Jn rgv nlfia phretac, xw fvov rs uwx xdd zcn ritwe wghteitgilh applications nigsu Avzc hcn Stnaiar, hcn eeye mkrp rnjk ehtq Tjfcz application. Jn Azzjf 3, rsrg’c cemboe eaesir brnc vtok rv vh.

sitemap
×

Unable to load book!

The book could not be loaded.

(try again in a couple of minutes)

manning.com homepage