Introduction to the Course

Let's learn a bit about why we're using Rails and the application we'll build in this course!

So, you want to write some client-side code

“I need a website,” the client said.

“Great,” you think. Rails is a solid way to go. It’s still the best way for a small team to be as productive as a big team. You are ready for this. You start thinking of estimates… modeling data structures…

“I want it to look cool, with lots of stuff moving around, and be extremely interactive,” the client added.

“Ugh,” you think. That brings in JavaScript. And with it, a lot of decisions. What language? There’s not just JavaScript, but a host of languages that compile to JavaScript: TypeScript, Elm, ClojureScript. What framework? There are dozens: React, Vue, Ember, Stimulus, Svelte, Preact, and on and on. How to package the code and CSS? Should you use the Rails asset pipeline or Webpacker. What about Turbolinks, didn’t the Rails team just update that?

Suddenly you are overwhelmed by the added complexity.

Rails can also help with the client-side. Ruby on Rails version 6.1 has tools that help you interact with the JavaScript ecosystem to build an exceptional front-end experience. In this course, I’ll show you how you can enhance the user experience of a standard Rails application using front-end tools from the Rails ecosystem (Stimulus, Turbolinks, and Webpacker) and tools from the JavaScript ecosystem (webpack, TypeScript, and React) and create a great Rails-based app.

So that interactive website your client wants? No problem.

Prerequisites

Overall familiarity with web development will help. However, knowing Rails basics is a must to follow this course!

Basic assumptions

Rails is an opinionated framework, and this is an opinionated course. For Rails, being opinionated means Rails makes certain tasks easier if you are willing to structure your program the way the Rails core team thinks you should. For this course, being opinionated does not mean showing you every possible way that Rails and JavaScript can combine, but focusing on the tools that will be most useful

We’re starting with an important opinion: we’re going to use JavaScript to enhance a Rails application rather than use JavaScript to build a completely separate single-page application (SPA) that only uses Rails as an API.

My basic argument for not writing an SPA is that Rails and standard browser behavior handle a tremendous amount of complexity for you. Moving to an SPA structure requires you to build much of that functionality yourself. Over time, the front-end frameworks have gotten better at handling the complexity for you, but for now, for my money, Rails is still less complicated for many applications.

That said, there are legitimate places where an SPA might make sense. If your user experience is so different from the normal web structure that the existing behavior of Rails offered by the browser isn’t much help, then an SPA begins to look attractive. If your back end is already an application programming interface (API) supporting a mobile app or external services, then an SPA can also act as a consumer of that API, saving you from duplicating view-layer logic.

However, my experience is that most of the time, for most teams, starting by leveraging the Rails view and browser features is the best way to create a great application.

With this assumption—–Rails back end with some front----end interaction—there’s still a wide range of tools, architectures, and techniques that might be appropriate for the application. We’re going to navigate that space. Within that space, we are going to explore different ways of structuring a Rails/JavaScript collaboration.

Let’s build an app

Before we start talking about front-end structure, we need to have an app to attach all that front-end structure to.

I’ve created a sample app for a fictional music festival called North By North South, where multiple bands will perform at various concerts during the event. This app contains a schedule of all the concerts and venues.

There isn’t much to this app; I’ve used Rails scaffolding for a minimal amount of administration, but it’s just a structure that lets us get at the two pages we’ll be managing in this course: the schedule page and the concert display page.

The schedule page shows all the concerts, acts, and times for the entire festival. We’ll be adding features to this for basic show-hide behavior, and more complex date and search filters.

The concert view page lets you see a simplified theater diagram for each concert and select seats for a kind of ticket purchase. We’ll use this page to add more elaborate client-side behavior that goes beyond augmented forms.

The data model for the app looks like this:

  • The festival includes several concerts that take place at particular start times.
  • Each concert has a venue, and each venue has a number of rows and a number of seats per row (which I realize is vastly simplified from real music venues, but we’re just going to pretend for now because that gets very complicated very quickly).
  • Each concert has one or more gigs that make up the concert.
  • Each gig matches a band to a concert and has a start order and a duration.
  • Each concert has a bunch of sold tickets, which link a concert to a particular row and seat in the venue.

Here’s a diagram of the data model:

I chose the Bulma CSS framework, which I picked because it’s attractive, adds relatively few class names to the markup, and it’s usually possible to figure out what the CSS does from the class names.

The schedule page should look something like this (your randomized data will be different):

See the application in action

Here’s the application we’ll build at the beginning of the course. Click on the link next to “Your app can be found at:” once the server starts.

Bud1((	 Sbwspblob�dbbwspblob�bplist00�	
	]ShowStatusBar_SidebarWidthTenElevenOrLater[ShowToolbar[ShowTabView_ContainerShowSidebar\WindowBounds[ShowSidebar[ShowPathbar#@a 			_{{438, 266}, {770, 436}}	'FR^u����������Ħ����applg1Scomp��appmoDDblob���AappmodDblob���Aappph1Scomp0appvSrnlongbabel.config.jsIlocblob(������ @� @� @� @9dblg1Scomp6�dbmoDDblob����AdbmodDblob����Adbph1Scomp�dbvSrnlongdb_datalg1Scompdb_datamoDDblob��ܿ�Adb_datamodDblob��ܿ�Adb_dataph1ScompGemfileIlocblob��������Gemfile.lockIlocblob;$������libIlocblob�$������libbwspblob�bplist00�	
	]ShowStatusBar_SidebarWidthTenElevenOrLater[ShowToolbar[ShowTabView_ContainerShowSidebar\WindowBounds[ShowSidebar[ShowPathbar#@a 			_{{498, 206}, {770, 436}}	'FR^u�����������liblg1Scomp�libmoDDblob���AlibmodDblob���Alibph1ScomplibvSrnlonglogIlocblob$������logbwspblob�bplist00�



]ShowStatusBar[ShowPathbar[ShowToolbar[ShowTabView_ContainerShowSidebar\WindowBounds[ShowSidebar			_{{417, 287}, {770, 436}}	%1=I`myz{|}~��loglg1Scompd�logmoDDblob�.��AlogmodDblob�.��Alogph1ScompplogvSrnlongpackage.jsonIlocblob�$������postcss.config.jsIlocblob�$������publicIlocblob;�������publicbwspblob�bplist00�	
	]ShowStatusBar_SidebarWidthTenElevenOrLater[ShowToolbar[ShowTabView_ContainerShowSidebar\WindowBounds[ShowSidebar[ShowPathbar#@a 			_{{458, 246}, {770, 436}}	'FR^u�����������publiclg1Scomp[�publicmoDDblob�.��ApublicmodDblob�.��Apublicph1Scomp�publicvSrnlongRakefileIlocblob��������specIlocblob�������specbwspblob�bplist00�	
	]ShowStatusBar_SidebarWidthTenElevenOrLater[ShowToolbar[ShowTabView_ContainerShowSidebar\WindowBounds[ShowSidebar[ShowPathbar#@a 			_{{458, 246}, {770, 436}}	'FR^u�����������speclg1Scomp)�specmoDDblob����AspecmodDblob����Aspecph1Scomp`specvSrnlongstorageIlocblob��������storagebwspblob�bplist00�	
	]ShowStatusBar_SidebarWidthTenElevenOrLater[ShowToolbar[ShowTabView_ContainerShowSidebar\WindowBounds[ShowSidebar[ShowPathbar#@a 			_{{518, 186}, {770, 436}}	'FR^u�����������storagelg1ScompstoragemoDDblob�e��AstoragemodDblob�e��Astorageph1ScompstoragevSrnlong
tsconfig.jsonIlocblob��������vendorIlocblob; ������vendorlg1ScompvendormoDDblob�e��AvendormodDblob�e��Avendorph1ScompvendorvSrnlongyarn-error.logIlocblob� ������appIlocblob�(������appbwspblob�bplist00�	
	]ShowStatusBar_SidebarWidthTenElevenOrLater[ShowToolbar[ShowTabView_ContainerShowSidebar\WindowBounds[ShowSidebar[ShowPathbar#@a 			_{{498, 206}, {770, 436}}	'FR^u�����������applg1Scomp��appmoDDblob���AappmodDblob���Aappph1Scomp0appvSrnlongbabel.config.jsIlocblob(������binIlocblob�(������binbwspblob�bplist00�	
	]ShowStatusBar_SidebarWidthTenElevenOrLater[ShowToolbar[ShowTabView_ContainerShowSidebar\WindowBounds[ShowSidebar[ShowPathbar#@a 			_{{478, 226}, {770, 436}}	'FR^u�����������binlg1Scomp#binmoDDblob����AbinmodDblob����Abinph1Scomp�binvSrnlongconfigIlocblob�(������configbwspblob�bplist00�



]ShowStatusBar[ShowPathbar[ShowToolbar[ShowTabView_ContainerShowSidebar\WindowBounds[ShowSidebar			_{{784, 256}, {770, 436}}	%1=I`myz{|}~��configicvpblob�bplist00�	

_backgroundColorBlue[gridSpacingXtextSize_backgroundColorRed^backgroundType_backgroundColorGreen[gridOffsetX[gridOffsetY_scrollPositionY\showItemInfo_viewOptionsVersion_scrollPositionXYarrangeBy]labelOnBottom_showIconPreviewXiconSize#?�#@K#@(##@>Tnone		#@P+AMVkz��������"+4=?HQRTYZ[dconfiglg1ScompTconfigmoDDblobb��ҿ�AconfigmodDblobb��ҿ�Aconfigph1Scomp�configvSrnlong	config.ruIlocblob;�������dbIlocblob��������
	]ShowStatusBar_SidebarWidthTenElevenOrLater[ShowToolbar[ShowTabView_ContainerShowSidebar\Win(E	 DSDB `�0@� @� @

_backgroundColorBlue[gridSpacingXtextSize_backgroundColorRed^backgroundType_backgroundColorGreen[gridOffsetX[gridOffsetY_scrollPositionY\showItemInfo_viewOptionsVersion_scrollPositionXYarrangeBy]labelOnBottom_showIconPreviewXiconSize#?�#@K#@(##@>Tnone		#@P+AMVkz��������"+4=?HQRTYZ[dconfiglg1ScompTconfigmoDDblobb��ҿ�AconfigmodDblobb��ҿ�Aconfigph1Scomp�configvSrnlong	config.ruIlocblob;�������dbIlocblob��������
	]ShowStatusBar_SidebarWidthTenElevenOrLater[ShowToolbar[ShowTabView_ContainerShowSidebar\Win