Welcome to Ruby on Rails. We're going to build a demo blog to demonstrate how to get started
with the framework, build something real, and then deploy to production. Let's go. We're going to start with
the creation of the Rails skeleton. This includes all the default directories and some stock files
for you to fill in with all your content and it also sets up all the dependencies that the Rails
framework uses by default now that we have this skeleton we can start by generating the first
model for our blog a post model that's going to have a string as a title and a text field
as content we're using this generator to create the files needed for the controller for the post
model for the views for everything let's have a look at what's been created
the first thing we're going to have a look at is the migration the migration is what sets
up the database table and as you can see here we designated that we want a string title and a text
content and then by default rails also adds a set of timestamps for created at and updated ad this
is then connected to the post model that will instantiate and encapsulate these database rows
which again is invoked by the posts controller it has all the actions needed to encapsulate a
rest style approach to developing web applications you have the seven default actions and a handful
of these actions then have templates like index show new and edit i could show you one of those
and that is basically all we need to get started with the most basic
uh outline of a web application so we will run the migration as the first thing to set up the
database and set up the post table and then we can have a look at that schema that's been generated
for us as you can see now it just has the post table in there let's have a look at how that looks
in the browser so we start the rails server and then we jump over to the browser and as you can
see here it just gives us a nice boot screen that shows the version of rails that we're using
and the version of ruby but our new app that we created with the scaffold lives under slash posts
it is the basic crud app hello world we create a new post first post and when we post that it'll
go to the create action which will redirect to the show action and then we can go back to the
index action i've done this demo a bunch of times before and it's always looked like this
it's not very nice so for this demo i'm going to pop in the simple css framework straight off
the cdn so system we can have something slightly nicer to look at and i'll do that by going to the
default layout that all the views are rendered within and i'm just going to pop in the symbol css
styles and then let's have a look if that doesn't look a little better it sure does okay
so now we have the crot actions not just for html but actually also for a json api
if i do json behind the post here you'll see the entire collection currently consisting just of one
element returned as json that you could hook up to a single page application exposes a public api
whatever you want if we jump back and take a quick look here at the controller you can see that
this is all mapped up and for these actions that create an update we have different paths whether
you are submitting from html or whether you're submitting from json okay let's actually start
developing our domain model so the first thing we could do is we could add
validations to the post we could say validates presence of title and now if we go to
attempt to make a new post that has no title we will get an error that simply says this
can't be saved because the title can't be blank that error is generated by the
the form html that we have the template here we have under app views posts form
and see if there are any errors it'll show all these errors first and you can trace that all
the way through from the create as we go through the create action we tried first to save if that
is successful then find the post that's created and redirect if it's not successful we will
render the new action again as an unpossible entity with those error messages in there
that is a user error if you will we can also make a programmer error here and see what that
looks like if we reference the wrong variable and then try to load it we can see we get this
error screen in development that shows the exception being raised and the back trace where
it's coming from and then we actually also have a console built right into the error screen that
lets you manipulate and look at all the variables that have been assigned in there now that console
is also accessible um from the command line we can jump in and start a brand new console here
and interact with the main model that we've been building up if we go to first post first it'll
fetch the first post that we created this was the one we created through the web interface but we
can create a another one through this console as well if we do title from the console content nice
we'll see you start a transaction insert into the posts table and there we have it another
post object we can also create these objects for example just post all will get us everything if
we want to find just some of it we can for example query on created ad we could do time now all day
that'll create a range for the entire day we'll see we find still the same two posts you can see
what that sql statement actually looks like in total we can try one where we don't find anything
time now yesterday didn't have anything posted then that's the sequel for it and this is the
empty array that's returning back okay great but having a blog that simply just accepts
content as plain text is a little boring so you use another set of rails features um this is
action text we're going to add to get what you see is what you get editing for our new blog
we'll start here by adding the action text elements rails action text install we'll add
a little bit of javascript that we need it'll add some css for the new editor and it'll add
a set of migrations for us to upload and store files using active storage in this example we're
just storing locally when you deploy to production you can store on s3 or other cloud storage setups
it also adds an image image processing gems so we can make variants of the files that we
upload so that we can get smaller versions of the images or otherwise so we'll bundle to get that
image processing gem on there and then we'll run railsdb migrate to pick up the
couple of migrations we have now we have to set up the model to say that we're going to use this
rich text setup and do has rich text content and then if we jump to the form for that
instead of having a text area we can add it as a rich text area
now we just have to restart the server because we added a new gem and then we can jump over and
see what our new posts look like oh we left the error in there gotta pick that out again let's
do that and then reload great now you see that the content is not just the plain text field
this is rich we can use rich text we can do the various forms of markups we have in here
and we can even do file uploads so file uploads can simply be dragged and dropped into
our editor here it'll upload it in the background via active storage
to either local as we're doing here in development or with direct upload to cloud storage but we're
just going to do it here rails logo and then we create the post great so now we have a post
it has an image attached and if we go back to all the posts we can see
both of the posts that we've put in there let me show you how the javascript for that is set up
rails by default uses something called import maps which allows us to use advanced javascript
without having node installed on our system we're simply using the
javascript as text and serving through the browser and using esm to to deliver that
this is set up in terms of what we can use in our map or in our app as this import map um
the default is this stuff up here rails ships with hot wire by default turned on that means the turbo
framework and the stimulus framework is already set up and ready to use and then when we ran the
action text installed generator it also added the tricks and action text pins and as you can see
here the application.js then imports tricks and action text and that is what makes those available
to our application so that we can use it but what if we wanted to add some other javascript from npm
let's have a look at that we will add a piece of javascript for localizing the timestamps
that these posts were created on our blog both localizing and using time ago setups so we will
use the bin stop import map and we will pin local time it will fetch that off npm figure out which
url it should use based off the default cdn that we're using for this javascript that is jspm
i'll show you later how you can also download that but we're just going to depend on the cdn
at this time so now we've added local time to our import map
let's see it right here now we can also import it and start using it as part of our
application.js we'll import local time from local time and then we'll start that local
time now we can use this have a look at the post here we will pop in a new bit where we can have a
posted and then have a time tag and this time tag will generate an html
time tag which the local time javascript will identify look at the data local
attribute it says time it goes it'll convert that utc time into time ago and that's all done in the
in the browser which means it's cache safe which is a lovely way to present time
so let's have a look at that jump here to the browser post it ten minutes ago posted six
minutes ago three minutes ago so this is all using npm directly it does not require node of any kind
does not require npm installed on your local machine this is all running off a api using jsp m
the cdn but you could also just add these pins to your application yourself wherever you want them
they can be off your own cdn setup or it can be off any of the other cdns that are out there like
js deliver but we could also download this local time instead of having it as a cdn dependency
let's do that um we'll download it instead and as you can see here it's going to download it
to vendor javascript local time js and when we jump back into see there we can see that that pin
is automatically mapped there we just get a little comment of which version
that we're using and if we jump over to the browser it works just the same
great so now our little blog has posts with localized times has a way to use
a trix editor to use what you see is what you get but we would also like to add some comments
to this because that'll allow us to show the relationship within a domain model between
multiple models at once so we'll go back to the terminal here and use our generator again instead
of rails generate we can also just do rails g and instead of a scaffold we'll do a resource which
adds just a thinner wrapper around the model that we're adding here we're going to add a
comment and that comet is simply going to have a post references which creates
a foreign key setup and a dependency in the model that this belongs to the post and it's also going
to have a content that's also just going to be text as you can see it creates another
migration and you can have a look at that quite similar you see it sets up the reference it will
create a foreign key for that reference it becomes the post underscore id and then it has a text
column for the content for us to use so let's run that migration and have a look at the main
model that we've just created rails console will find the first post and that post has comments
oh it doesn't have comments yet because we have not yet connected um as you can see here the
comment model belongs to the post but we also need to tell the post that the post has many
comments now if we pop back into the console here we can actually just reload in line and
voila updates to the main model live there are no comments right now so let's create the first one
it's going to have content of first comment there we go now we have a post in the system
that has comments but we don't have any way of seeing that in our web ui so let's add that
to the web ui jumping back in here and then going to um the post we're just going to have it on the
show template on the show template here we're going to render all the comments
we'll actually do a partial for it that'll be post slash
comments we'll pass in instant variable of postsystem we just use local variables
and let's do comments here and i've pre-baked some html that we can just pop in
there we go so this will render all the comments that belong to this post this
is shorthand for what you see up here we're going to render the partial of comments comment
with a collection of posts so it iterates over those and then we will also render a new form
so let's create those partials first we create the comment one
and i'm also just going to pop that in it's just going to be a basic div that has an id which we
will later use for live updating of stuff it has the comment content and we're using that time tag
again with um a time ago to see when the comments were posted and we're also going to create a um
partial for the new form so that we can create new comments on our post as you can see here we use
a helper called form width it uses the model then we pass into it actually we're going gonna pass in
local variable here um and the comment and um and there we go and then actually let's also
pop in a little notice on the comment itself or on the post itself just a counter to show how
many comments were uh posted on that we use one of those helpers pure lies pluralize that will go one
comment to comments and you'll see that in the ui in just a second here although we also actually
need to add the comments controller it starts out as just this empty shell here in order to be able
to create any comments we need a create action we're going to set that up with some prepaid here
before any of the actions are loaded we run a callback called setpost that will plug out
the post id from the url since we know which post that these comments are supposed to be created for
it will then take the parameters from the form and their scope by comment it'll require that
which means that if the comment scope is not present it'll erase an exemption exception
telling us that and then will only permit a allow list of attributes in this case just content
says that you can't sneak anything else in you can't set a different
foreign key or id and corrupt the system this is just one of those security benefits that you have
in rails out of the box great let's have a look and see what that looks like we
are now on post we'll go to this post and see we are missing the route that
post comment is supposed to look up and that's because we i forgot to edit the routes file
so the resources generator adds this comments uh resources but it's of course at the root
and what we want it is to have it nested such that the comments belong to the post
great see there are zero comments so far but we have our um form down here let's do let's do
one comment we'll create that comment and there we go we have our comment
and have a quick look at the console for the the log for this you see here we do
started a post against posts slash three slash comments this is the nested route that we have
here are the parameters it's using an authenticity token to make sure that we don't have any csrf
exploits and as you can see here it's even filtered just it doesn't show up in the log
we also filter other things like passwords and stuff that you shouldn't be putting into your
logs and then you could see the parameters um this was the required scope comment and it just
has one attribute that is going to be the content and what that content actually is then here you
can see setpost is calling um that id the three from up here it looks it up since we have the
post it proceeds then inserts the comments and when it's done it redirects to post number three
and that renders everything again so now we have a blog that has posts and those posts can have
comments but let's add another feature of rails let's add a mailer such that when a new
comment is posted the owner of the blog gets a email letting the person know that a new comment
has been posted so we use another generator for a mailer here um and that mail is going
to be called the comments mailer and it's just going to have one action on it called submitted
you see this generator of course does not generate any migrations just a bunch of
files for us to play with so first thing we're going to edit is the comments mailer and you
can see it's just a stub here we're going to be passing in the comment that we want to let
the owner know about we're going to assign that to an instant variable
such that it's available in the view we're going to mail that to let's say the blog
owner at example.com and we're going to set a subject for this email to be
new comment then we can edit the templates that go with this comment mailer that forms the content of
the email that's going to be sent out so that's going to be submitted html i have one that's
pre-baked we can just pop in and the neat thing here you can see is we're reusing the same partial
templates the comments comment template the html template for the html part of the email as well
this is how a lot of things in rails work that you can use the same templates across
things like emails the first render live updates you're never recreating templates
more than once and then of course we also have the plain text version of the email where we're
not going to use that partial we're just going to pop it in as plain text rails has this newest
feature a neat feature where we can actually see what these emails are going to look like
we are going to use one of these previews and it's going to just preview whatever
the first comment is and we can jump to this url in a browser to see what that looks like
you got a new comment on hello world first comment that's the html version here's the plain text
version and now we know that the mailer itself works let's hook the mailer up to our flow such
that when a new comment is created we will also send out the email so we have the comments mailer
it has the submitted action we're going to pass in the comment that is being created in this action
and then we're going to deliver later deliver later we'll use a asynchronous job queue such that
neither the rendering of the html for this mailer nor the delivery of the email itself
happens in line it happens asynchronously much faster response time in the ui
and that's pretty neat so let's um see if that actually works we'll jump over to
the browser again jump back to our post and then send a comment via email
create that comment and we can see whether it's been sent by checking the log scrolling back here
and you can see the email is being sent it's being sent to blog owner it has that subject
of new comment it's rendering a mime part for the html and my part for the clear text and
if you look up here you can see the deliver later is set up as an action mailer delivery
job which is this out-of-band asynchronous setup in production we would use a full queuing system
rescue or something else like that in development it is just happening in line great now we have a
blog that can also send email let's make this blog live and we're going to use the default
wire stack or at least one part of it the turbo part of it to make adding comments a live event
and it is surprisingly simple as you saw before when we looked at the application js hotwire is
already configured in a new rails app so we can simply start using it so we're going to
start using it by setting up a subscription to a turbo stream on the post um show action we're
going to use this turbo stream from and then the name of the stream we're going to use is going
to be derived from the post um since it has an exclusive stream for the comments that are posted
is that and then when a comment is created we're going to let it broadcast um to the post that will
broadcast both uh creates updates and destroys which we can now demonstrate so let's jump over to
our browser and then let's actually make another browser such that we can have them side by side
and see that this stuff is happening live okay let's scroll down here is this a live comment
it sure is it showed up over here as well because via web sockets we're delivering
all these updates that are made to the comments they're being broadcast
whether they're being invoked from a web ui is here or if they're being invoked from the console
as well since it just goes through the rails domain model so we could do here find the post
3 and then look at the comments we have those comments we could take a look at the last one
and what would happen if we destroyed last one boom it's gone and it's gone because
when we destroy the last one a callback is being triggered by that setup of broadcast 2
that will broadcast this turbostream element remove and the target is comment underscore id
which matches the dom id we had set up for the partial for the individual comment we can also
update here from the console so if we do a update of the content content uh updated from console you
can see that that update is sent out as well that update contrary to the delete update is actually
done asynchronously because we're rendering a template so that also happens out of band
and there you have it creates updates and deletes are all broadcast automatically and of course you
can tweak this and set it up to your heart consent but simply adding the broadcast to will do it for
all the three actions by default when you follow the conventions okay we've created quite a lot
of code let's have a look and see if we also have some tests and of course we do because all these
generators we've been running have been creating stop testing that actually exercises the app
and if we run those with rails test we'll see a couple of them failing we have one failing because
of a invalid foreign key and that happens when we go to destroy the post then you have
comments that depend on that post now having an invalid foreign key so we can fix that first
by jumping back into the post model and instead of just saying has many comments we'll also say that
they are dependent and they will be destroyed when we destroy a post then let's run the tests again
now that test is no longer failing we still have a failing test for the comments mailer
because that is of course just generated off
the defaults that weren't tweaked we'll use um a fixture here if you have a look at com gml you'll
see we set up fixtures for all models that are generated through the generators such that you can
set up a set of fixtures that you can refer to in your text in your tests that run very fast
and then i also remember we changed the subject to new comment and then we sent this to the blog
owner and it was from example we're not going to match on the body right now
let's go back and run that boom all our tests are passing on our new wonderful blog
that does what you see is what you get editing it does comments it does live comments
and now our application as such is is done but what if we wanted to show other people
this application let's deploy this to production and i'm going to use heroku to do this because
heroku is nice and simple and easy to use but where the rail skeleton by default is started
with sql lite as the database heroku uses postgres but thankfully there is a command here
railsdb system change where we can change the configuration of our database to use
postgres instead that will add the postgres adapter so we'll have to bundle for that
and then we can add um everything to git because that is how we will push
to heroku so we'll add everything to git we will commit everything that we've added as first here
and then we will create the heroku setup that we're going to deploy our app to
and once we've done that we can push our app straight to heroku heroku
main and we're going to get a little error here in just a second that we can correct
because there is a check here and you can see this is failing because i'm making this demo on an m1
machine and heroku is not running on an arm64 so we just have to add x64 here as our platform for
the bundle lock we'll do that and then we'll add the um gem file lock again commit that
added the platform and then we can push again
and now we have deployed our app to heroku we've been assigned the mighty tundra
as our url for this and we can go have a look at that in just a second we need
to do a couple more things we need to first of all
migrate our database which will also set it up or we use heroku run rake db migrate
and then we need to add redis as an add-on to our heroku setup
because red is is used by the turbo setup to send those live updates back and forth
great now everything should be setting up so let's have a look at our live app deployed in production
i can use this one here and you see the page you're looking for doesn't exist and that's
because we haven't actually set up a root route in our application so let's do that if we go back
to the routes here you'll see there's actually a comment just for that purpose so we'll set it
up so when you go to the root it'll go straight to the posts index and we will commit that setup
we'll add it and commit it adding the root route and then we can push that again to heroku
now that's set up so let's have a look and see if the app works in production
there we go we have an empty one of course because it's a brand new database this is the first post
now the only thing that does not work yet here in production is drag and dropping
files because heroku would require us to set up s3 and you can use that as an exercise for the
reader for you to set that up but it is quite easy to do just a matter of a little bit of
configuration and then you will also have the direct uploads with drag and drop here
but let's create this post and then let's create the first comment
and check that everything is working here in production and there you go there is now one
comment posted so let's do the test and see if we're set up and it works correctly with
the turbo back to get the comments from one side to the other this is the second comment
and it sure does so now we have the entire application running in production backed by
a live setup that uses redis and all the other goodies
and that's all you need to get going get started building something real with ruby
on rails taking it all the way from hello world to who knows perhaps maybe one day IPO thank you