00:00:17.199
Um, as she said, my name is Harley
00:00:19.039
Magcguire and my talk today is the
00:00:21.039
ghosts of action view cache.
00:00:24.560
But first, a little bit about me. I am
00:00:26.720
part of the Rails issues team. You may
00:00:28.400
have seen my picture on GitHub. I also
00:00:30.400
work at Shopify where for the past few
00:00:32.640
years I've been on our database as a
00:00:34.800
service team which we call Kate SQL
00:00:36.640
which is where we run my SQL on
00:00:38.239
Kubernetes.
00:00:39.840
And in the past few months I've actually
00:00:42.000
had the opportunity to join the Rails
00:00:43.760
infrastructure team which is where I
00:00:45.120
work today. If you have questions about
00:00:47.920
contributing to Rails, our Kate SQL
00:00:50.000
platform or my time so far on the Rails
00:00:52.559
infrastructure team, feel free to come
00:00:54.239
say hi.
00:00:55.920
Now today I won't really be talking
00:00:57.760
about these things. Instead I want to
00:00:59.920
share with you a story
00:01:03.600
to really set the stage since Rails is
00:01:06.159
the oneperson framework. I want you to
00:01:08.000
imagine you're in the shoes of a solo
00:01:09.920
developer. You've been working on a
00:01:11.840
Rails application for a few years now
00:01:13.840
and this year you decide to go to
00:01:15.680
Railscom to really integrate with the
00:01:17.280
community.
00:01:19.200
Now it's the night before Rails comp and
00:01:21.680
you have an idea. I should upgrade my
00:01:24.080
Rails application so I can take
00:01:25.920
advantage of all the cool things I'll
00:01:27.439
hear about at the conference tomorrow.
00:01:30.080
You upgrade your app, CI passes, it
00:01:32.799
deploys to production, and you head to
00:01:35.520
bed.
00:01:38.320
But suddenly, you're startled awake.
00:01:40.320
Your phone is ringing. You're being
00:01:41.840
paged. You pull up your applications
00:01:44.400
dashboards and you see an error. It's
00:01:47.040
coming from a controller, and the code
00:01:48.880
looks like this.
00:01:50.960
And the error points to this cache
00:01:52.479
block.
00:01:55.119
Could the Rails upgrade have broken
00:01:56.560
caching?
00:01:58.240
But then you realize the problem is with
00:01:59.759
your code. This isn't caching a query.
00:02:02.240
It's caching an active record relation.
00:02:04.960
The fix is simple. Make sure that the
00:02:06.719
relation is loaded inside the block so
00:02:09.039
that it caches the queried records.
00:02:12.000
You commit the fix, but as you go to
00:02:14.080
deploy, you feel a strange presence.
00:02:18.000
Who's there? you exclaim, but there's no
00:02:20.640
response.
00:02:22.160
Finally, the silence is broken by the
00:02:24.319
faintest whisper.
00:02:26.720
Use action view.
00:02:30.400
You realize that you've been visited by
00:02:32.080
the ghost of action view past. And it's
00:02:34.640
here to suggest that instead of using
00:02:36.560
low-level caching with Rails.cache, you
00:02:39.120
use action views fragment caching. Let's
00:02:41.760
see what that looks like.
00:02:45.040
Instead of calling Rails cache fetch
00:02:47.200
inside a model or controller, you
00:02:49.440
instead move the expensive or slow
00:02:51.440
queries into your view template and wrap
00:02:53.840
the template render inside a cache
00:02:56.000
block. And this has a few advantages.
00:03:00.319
The first is that you can cache even
00:03:02.000
more of your request. While the slowest
00:03:04.480
part of a request may be a specific
00:03:06.239
query, caching the template enables you
00:03:08.400
to potentially cache even more queries.
00:03:10.959
In additional in uh additionally you can
00:03:13.840
cache the rendering of the final HTML.
00:03:17.120
So action view requ uh caching makes
00:03:19.599
your requests faster.
00:03:22.000
Additionally, you no longer have to
00:03:23.360
worry about what kind of object is being
00:03:25.120
put in the cache. The bug being fixed
00:03:27.680
was caused by a relation being put in
00:03:29.680
the cache instead of a model. But with
00:03:32.000
action view caching, you always cache
00:03:33.680
rendered HTML and you don't have to
00:03:35.920
worry about this kind of bug at all.
00:03:39.680
Now, a question you may have is how does
00:03:41.920
cach invalidation work?
00:03:44.560
If the application's templates are
00:03:46.239
changed and a new version of the app is
00:03:48.400
deployed, how does action view know to
00:03:50.480
render the new template instead of
00:03:52.319
serving HTML from the cache?
00:03:55.519
And this is an area where the magic of
00:03:57.200
Rails really shines. Developers don't
00:03:59.840
need to worry about cache invalidation
00:04:01.680
at all because action view handles it
00:04:03.840
for you transparently. And that is what
00:04:06.239
I really want to talk about today.
00:04:10.319
Cache keys are generated by a class
00:04:12.159
called action view digesttor. Let's see
00:04:14.720
how it works.
00:04:17.280
In this example, we'll have a product
00:04:19.199
show page that renders a product model
00:04:21.840
template and a list of variant model
00:04:24.240
templates. To calculate the cache key
00:04:26.960
for the product show template, we call
00:04:29.199
the digesttor's digest method, which
00:04:31.600
we'll first call the tree method to
00:04:33.759
build a tree representing the templates
00:04:35.520
renders. In this case, the root of the
00:04:38.320
tree is the product show template and
00:04:40.479
its children are the product and variant
00:04:42.560
model templates.
00:04:44.960
Then the digest method uses that tree to
00:04:47.680
generate the cache key. To create the
00:04:50.639
key for show, we first need the cache
00:04:52.400
keys for all of the children. So you can
00:04:54.720
start with the product template. And
00:04:56.400
since it has no children, its cache key
00:04:58.720
will just be a hex digest of the source
00:05:01.280
code of the template,
00:05:03.680
which will look something like this.
00:05:05.919
And then we move on to the next child
00:05:07.440
and digest its source. And now that
00:05:09.759
we've digested all of the children of
00:05:11.600
the show template, we can create the
00:05:13.280
final cache key by hex digesting the
00:05:15.600
combination of the two children digests
00:05:19.039
and the content of the show template.
00:05:21.919
And this is the magic behind cache
00:05:23.840
invalidation. Each template's cache key
00:05:26.479
depends on the content of a template
00:05:28.720
plus all of its dependencies.
00:05:31.360
If any of those templates change, the
00:05:33.199
cache is invalidated and the new version
00:05:35.280
of the template gets rendered.
00:05:37.919
Now, something I kind of glossed over is
00:05:39.759
how the digtor knows which templates are
00:05:42.080
rendered by a template in order to
00:05:44.160
create the tree. This is handled by
00:05:46.479
another class called action view
00:05:48.160
dependency tracker and more specifically
00:05:50.320
ERB tracker. Um, this class constructs
00:05:53.680
this really complicated regular
00:05:55.440
expression which I put here sideways so
00:05:57.440
that you can see how big it is without
00:05:58.880
really trying to read it.
00:06:00.639
um to and it extracts the template names
00:06:02.880
from these render calls um and that
00:06:05.360
tells you the dependency tracker what
00:06:07.280
the dependencies are and naturally
00:06:09.759
because we're using regular expressions
00:06:11.280
this isn't a perfect solution so it also
00:06:13.360
provides a fallback mechanism where you
00:06:15.120
can explicitly write a template's
00:06:16.800
dependencies in a ERB comment
00:06:21.199
and that's how action view does cache
00:06:23.199
invalidation the digtor constructs trees
00:06:26.319
of dependencies and the ERB tracker
00:06:28.639
parses renders
00:06:30.880
Okay, we'll revisit these classes, but
00:06:33.440
for now, let's go back to the story and
00:06:35.360
our solo developer.
00:06:38.400
As a reminder, our developer had to fix
00:06:40.639
a caching bug in their application, and
00:06:43.039
they've now used fragment caching to do
00:06:44.800
so. Wow, they think the conference
00:06:47.120
hasn't even started, and I've already
00:06:48.560
learned something to make my Rails
00:06:49.840
application better.
00:06:51.919
However, at this point, it's way past
00:06:53.759
their bedtime, and they'd really like to
00:06:55.440
get some sleep before the conference
00:06:56.960
kicks off bright and early tomorrow.
00:06:59.759
They push up their fragment caching bug
00:07:01.520
fix. It passes CI, deploys to
00:07:04.160
production, and once again they head to
00:07:06.560
bed.
00:07:08.560
And of course, once again, they get
00:07:10.160
paged. This time when they pull up their
00:07:12.639
dashboards, they see that their
00:07:13.919
application is running out of memory and
00:07:15.680
restarting.
00:07:17.199
Debugging memory issues can be
00:07:18.960
difficult, and it's the middle of the
00:07:20.800
night now, so the solo developer is
00:07:22.639
interested in a quick fix.
00:07:26.000
I have an idea, they think. Rails
00:07:28.560
enables YJIT by default to make
00:07:30.720
applications faster, but it does use
00:07:32.800
more memory. So, I could try turning it
00:07:34.880
off and see if my memory issues go away.
00:07:38.080
But before they can even open up their
00:07:39.840
editor, the feeling of a strange
00:07:42.080
presence returns.
00:07:44.400
And this time, its advice comes quickly.
00:07:47.599
Use action view.
00:07:50.800
The presence is new, but its purpose is
00:07:52.960
the same. to convince you to solve your
00:07:55.039
memory issues with action view. To
00:07:58.160
understand how that's even possible, we
00:08:00.319
first need to understand how Rails
00:08:02.240
servers operate.
00:08:04.960
Most production web servers will launch
00:08:06.720
with some sort of orchestrator process
00:08:08.720
that starts off by eager loading your
00:08:10.800
application and then the orchestrator
00:08:12.720
will fork off a number of workers to
00:08:14.800
actually handle requests.
00:08:16.960
And the major benefit to this eager load
00:08:19.280
then fork pattern is that the workers
00:08:21.599
are able to share a lot of their memory
00:08:23.919
due to an optimization called copy on
00:08:26.000
write which applies as long as the
00:08:28.000
memory isn't changed.
00:08:30.319
To show how important this pattern is
00:08:31.759
for reducing memory usage, let's look at
00:08:33.680
how Rails processes requests.
00:08:37.120
When a request comes into your freshly
00:08:39.200
started application, it gets routed to
00:08:41.279
one of the workers. If that request
00:08:43.519
requires ERB templates to be rendered,
00:08:45.680
it first checks to see if the ERB
00:08:47.680
template for the request has already
00:08:49.200
been compiled to Ruby code. Since the
00:08:51.440
application hasn't processed any
00:08:52.800
requests yet, it compiles the ERB
00:08:54.880
templates and renders a response.
00:08:57.760
Now, what happens if that same request
00:09:00.080
gets routed to a different worker?
00:09:03.279
Since the ERB templates were compiled in
00:09:05.279
the first worker, the second worker
00:09:07.040
doesn't know they exist. So it does its
00:09:09.839
own compilation of the templates and
00:09:11.760
renders a response.
00:09:14.080
And as requests continue to get routed
00:09:16.160
to other workers, they will continue to
00:09:18.320
recompile the same ERB templates and
00:09:21.040
continue to increase your application's
00:09:22.800
memory usage even though the exact same
00:09:25.120
templates are being compiled in each
00:09:27.279
worker.
00:09:28.800
So at this point, the question you are
00:09:30.320
probably asking is why doesn't Rails
00:09:32.399
just compile the templates during eager
00:09:34.320
load so that the memory can be shared?
00:09:37.200
And the answer basically boils down to
00:09:38.800
the fact that it's kind of a hard thing
00:09:40.399
to do.
00:09:42.399
Skipping over a ton of details of how
00:09:44.160
ERB works, the important thing to know
00:09:46.399
is that templates get compiled into
00:09:48.080
regular Ruby methods. And one part of
00:09:51.120
this process is adding a translation
00:09:53.200
layer so to the compiled method so that
00:09:55.440
you can use locals passed into the
00:09:57.040
template just like local variables
00:09:58.959
inside the template itself.
00:10:01.440
But when a template is being compiled
00:10:03.040
into a method, how does it know which of
00:10:05.120
these things are variables and which
00:10:06.959
should be method calls to view helpers?
00:10:09.839
The answer is it can't know without
00:10:11.839
seeing the context, the render method
00:10:14.320
that actually renders the template.
00:10:17.040
So for Rails to be able to pre-ompile
00:10:19.440
all of the ERB templates in your app, it
00:10:22.000
would probably need something that can
00:10:23.519
parse the renders.
00:10:26.800
Wait a second. The ERB tracker already
00:10:28.880
parses renders.
00:10:30.880
Unfortunately, it doesn't quite work in
00:10:32.480
this case because it only grabs the
00:10:33.920
template names out of render calls and
00:10:36.160
we need to know the other arguments, the
00:10:38.000
locals.
00:10:39.519
Maybe the ERB tracker could be improved
00:10:41.519
to parse the locals, but do we really
00:10:43.680
want to tack on more regular expressions
00:10:45.440
to the existing regular expressions?
00:10:48.320
What if instead of using regular
00:10:50.000
expressions to parse all of the Ruby
00:10:51.760
code, we just use Ruby's parser?
00:10:55.760
And this is exactly what John Hawthorne
00:10:57.839
did in a gym called action view
00:10:59.680
pre-ompiler.
00:11:01.519
It uses Ruby's ripper parser to not only
00:11:04.480
extract template names from render
00:11:06.399
methods, but locals as well. And because
00:11:09.519
of this, it's able to compile ERB
00:11:11.360
templates when the application boots,
00:11:13.519
which can lower the total memory usage.
00:11:16.399
The way the gym works is actually quite
00:11:18.240
straightforward. So let's do a quick
00:11:19.760
walk through.
00:11:22.000
The first thing it does is it gathers
00:11:23.760
all of the view templates, view helpers,
00:11:25.920
and controllers in the application. The
00:11:29.040
templates are found using action
00:11:30.560
controller bases view paths, while
00:11:32.640
controllers and helpers are found using
00:11:34.720
the application's paths object.
00:11:38.399
Then it uses the ripper parser I
00:11:40.079
mentioned before to parse the render
00:11:41.760
calls for each of those files. And when
00:11:45.120
parsing these renders, it stores the
00:11:46.560
name of the template being rendered
00:11:48.720
along with the local variables used as
00:11:50.640
well.
00:11:52.560
And once it has gathered all of the
00:11:53.920
template names and locals, the only
00:11:55.760
thing left to do is to actually look up
00:11:58.320
the template class and call its internal
00:12:00.240
compile method. And that's actually all
00:12:02.720
there is to it.
00:12:05.360
As a result of action view pre-ompiler,
00:12:07.519
a ripper implementation of the ERB
00:12:10.000
tracker was actually upstream to Rails
00:12:12.000
as well. And because this new tracker
00:12:14.800
uses a real Ruby parser, it's able to
00:12:17.360
handle many of the edge cases that the
00:12:19.120
ERB tracker just can't. For example, it
00:12:22.160
knows to ignore the string render if it
00:12:24.240
appears in an HTML class. And it also
00:12:26.880
knows to ignore renders in commented out
00:12:29.040
ERB tags.
00:12:31.279
And since we're talking about Ruby
00:12:32.720
parsers, I also have to mention the
00:12:34.639
creation of Prism, which is a brand new
00:12:36.959
Ruby parser, and it became the default
00:12:38.959
in Ruby 3.4.
00:12:41.279
In addition to working on the parser
00:12:42.720
itself, Kevin Newton also contributed
00:12:44.959
prism implementations of the ripper
00:12:46.959
parser to action view pre-ompiler and
00:12:49.200
the dependency tracker in Rails.
00:12:52.160
One shortcoming of the ripper parser is
00:12:54.560
that it can only be used with C Ruby
00:12:56.880
because other Ruby implementations have
00:12:58.880
their own parsers. However, since Prism
00:13:01.680
was built to be integrated with any Ruby
00:13:03.680
implementation, the Prism based render
00:13:05.839
parsers are much more compatible.
00:13:10.240
We've gotten a little bit off track
00:13:11.519
here, so let's take a step back and
00:13:13.440
summarize where we are. Action view
00:13:15.920
pre-ompiler can lower your application's
00:13:18.160
total memory usage by compiling your ERB
00:13:20.880
templates during boot instead of while
00:13:23.200
processing requests.
00:13:25.440
The pre-ompiler works by parsing renders
00:13:27.600
to extract template names and local
00:13:29.680
variables, which it can then use to
00:13:31.440
compile the templates.
00:13:34.320
Now, one more time, let's continue the
00:13:36.480
story of the solo developer.
00:13:40.399
As a reminder, our developer had to fix
00:13:42.480
their application's memory usage, and
00:13:44.480
they've now used action view pre-ompiler
00:13:46.399
to do so. Two pages in one night, they
00:13:49.200
exclaim, "How unlucky. At least now I'll
00:13:52.240
finally be able to get some sleep." They
00:13:55.040
push up their action view pre-ompiler
00:13:56.720
fix. It passes CI, deploys to
00:13:59.199
production, and once again, they head to
00:14:01.680
bed.
00:14:04.079
Now, I'd say the developer gets paged
00:14:06.639
again, but the next thing I'm going to
00:14:08.399
tell you is how action view can be used
00:14:10.240
to find dead code in your application.
00:14:12.399
And who actually gets paged for that
00:14:14.240
kind of thing?
00:14:16.000
Instead, let me tell you my story.
00:14:19.360
Over enough time, I think any
00:14:20.959
application probably ends up with some
00:14:22.399
amount of dead code,
00:14:25.120
especially dead view templates. As files
00:14:28.079
are moved around, new files added, old
00:14:30.320
files removed. Can we really guarantee
00:14:32.079
that we don't leave anything behind?
00:14:35.279
Around a year ago, the application I
00:14:37.279
work on was going through a large
00:14:38.560
migration. We were switching UI
00:14:40.639
libraries.
00:14:42.160
So naturally, this led to a lot of churn
00:14:44.480
in our views. And by the end of the
00:14:46.880
migration, the goal was every file
00:14:48.880
should be rewritten or removed.
00:14:52.240
But I remember there was one week in
00:14:53.839
particular where there was multiple
00:14:55.519
times I noticed we had some dead view
00:14:57.440
templates in our application. and I
00:15:00.240
created pull requests to remove them.
00:15:01.760
But I knew that finding these individual
00:15:03.360
files wasn't really a scalable solution
00:15:05.440
to the problem. What I really wanted was
00:15:07.920
a tool that I could could identify all
00:15:10.240
of the dead templates used in the entire
00:15:12.639
application.
00:15:14.959
I eventually came up with an idea to
00:15:16.720
implement a mark sweep type pattern to
00:15:20.079
identify which view templates must be
00:15:21.920
dead. If you've never heard of MarkX
00:15:24.240
sweep before, it's a algorithm
00:15:26.240
frequently used by garbage collectors,
00:15:28.000
including the default garbage collector
00:15:29.839
in Ruby. And here's what that looks
00:15:32.160
like.
00:15:33.920
We first get a list of all view
00:15:35.760
templates in the application. Then we
00:15:38.240
need to create some list of root
00:15:39.839
templates that we know are used and then
00:15:42.800
we iterate through that list. First
00:15:45.120
marking a template itself as used and
00:15:47.600
then recursing to any templates rendered
00:15:49.759
by that template and marking them as
00:15:51.600
used.
00:15:52.720
And this continues until the end of the
00:15:55.040
root template list at which point every
00:15:57.279
template in the application is now
00:15:59.440
marked as being used or we can sweep
00:16:01.839
away as dead code.
00:16:05.759
I pitched this rough idea to my team and
00:16:08.079
it was enough to nerd snipe one of my
00:16:09.680
teammates into writing a big complex
00:16:12.399
regular expression that would parse the
00:16:14.240
template names out of render calls
00:16:16.000
inside our applications view templates.
00:16:19.360
And at this point, I was like, "Wait a
00:16:21.199
second. I've seen this one before.
00:16:22.880
Action view can already do this with the
00:16:24.720
ERB tracker." With the hard part of the
00:16:27.440
job already implemented in Rails, I
00:16:29.360
started working on a gem.
00:16:32.160
My goal was to create a CLI that you can
00:16:34.480
run in your own Rails application, and
00:16:36.800
it will print out the paths of any view
00:16:38.560
templates that it identifies as dead.
00:16:42.240
As I explained before, the first thing
00:16:43.839
to implement was collecting a list of
00:16:45.759
all the view templates in the
00:16:47.120
application. In the CLI, you can display
00:16:49.600
this list with the all flag. My first
00:16:52.480
approach to creating this list was to
00:16:54.399
use a directory glob so that I could
00:16:56.399
avoid initializing the Rails
00:16:57.839
application. But I quickly learned that
00:16:59.600
this wouldn't work well for finding view
00:17:01.360
templates in gems or even other Rails
00:17:04.079
engines in subfolders.
00:17:06.160
I ended up adding an environment require
00:17:08.720
so that the application is initialized,
00:17:10.640
which is unfortunately much slower than
00:17:12.400
not initializing. However, it's then
00:17:14.559
able to use action controllers view
00:17:16.240
paths, which is much more accurate than
00:17:18.319
the glob that I started with.
00:17:21.919
The next thing to implement was
00:17:23.199
recording each template's dependencies.
00:17:25.600
Template dependencies can be displayed
00:17:27.439
in the CLI along with their templates by
00:17:29.520
passing the trees flag.
00:17:32.320
For this, I created a node class that
00:17:34.320
held a reference to the template name
00:17:36.000
along with all of its children, which I
00:17:38.160
computed using action view dependency
00:17:39.919
tracker. And with this structure in
00:17:42.400
place, it became very easy to
00:17:44.000
recursively list a template's
00:17:45.679
dependencies.
00:17:47.919
If you think that sounds like I
00:17:49.520
reimplemented action view digesttor's
00:17:51.520
tree method, you would be correct. It
00:17:54.240
wasn't until a few months later that I
00:17:55.840
learned about the digtor. But once I
00:17:57.840
did, I was able to dramatically simplify
00:18:00.080
my gyms implementation by replacing all
00:18:02.160
of my custom treeb building code.
00:18:05.039
The last thing to implement is
00:18:06.559
collecting the list of roots which we
00:18:08.720
can identify for sure as not dead. And
00:18:12.160
there are a few different approaches
00:18:13.520
that can be used here. And I'll discuss
00:18:15.120
the trade-offs of each.
00:18:18.080
Throughout the talk, I've been referring
00:18:19.679
to all view templates as templates, but
00:18:22.559
really Rails makes a distinction between
00:18:24.640
templates and partials. The way I think
00:18:27.360
about it generally is that controllers
00:18:29.120
render templates, templates render
00:18:30.880
partials, and partials also render
00:18:32.799
partials.
00:18:34.799
The first and I think simplest strategy
00:18:36.960
is to assume that all templates are used
00:18:39.200
but partials may not be. And as long as
00:18:42.480
your application consistently only
00:18:44.160
renders partials from templates or other
00:18:46.640
partials, this will very accurately
00:18:48.960
identify unused partials in your
00:18:50.799
application.
00:18:52.720
However, it also has a really high
00:18:54.240
chance of false negatives, meaning that
00:18:56.080
partials could be marked as used even if
00:18:58.480
the template that renders them is not
00:19:00.480
used.
00:19:02.640
Another approach that could be used is
00:19:04.640
take inspiration from action view
00:19:06.240
pre-ompiler. Instead of assuming that
00:19:08.720
all templates are used, only mark a
00:19:10.640
template as used if it will be rendered
00:19:12.799
by a controller or helper. And this is a
00:19:15.840
more accurate approach for templates and
00:19:17.600
therefore has a chance to remove many
00:19:19.440
more dead views than if an application
00:19:22.240
has many dead templates with
00:19:23.679
dependencies.
00:19:25.840
The downside is that views marked as
00:19:27.600
dead may not actually be dead. we may
00:19:30.080
have just missed the thing that renders
00:19:31.919
them. Additionally, logic for parsing
00:19:34.720
renders in a controller is not quite the
00:19:37.200
same as the logic for parsing renders in
00:19:39.360
templates. The render parser in action
00:19:42.240
view pre-ompiler is written to handle
00:19:44.320
controller renders, but the dependency
00:19:46.320
tracker in Rails is not.
00:19:48.880
Finally, Turbo can also make this
00:19:50.559
problem a little bit harder because it
00:19:52.160
adds additional methods that can render.
00:19:54.480
So if a template is only rendered by
00:19:56.480
something like a broadcast, then this
00:19:58.080
approach would currently mark it as
00:19:59.520
dead.
00:20:02.160
With either approach for determining the
00:20:04.080
root templates, if you run the CLI with
00:20:06.240
no arguments, you'll get either a list
00:20:08.000
of dead view templates or no output at
00:20:10.720
all if there aren't any.
00:20:13.520
And while I don't think this is ready to
00:20:15.440
be a blocking step in CI, I have had a
00:20:18.400
lot of success running this against my
00:20:20.000
own application. It's correctly
00:20:22.320
identified view templates which are dead
00:20:24.240
that account for almost 5% of the
00:20:26.559
current total of view templates that we
00:20:28.320
have. So that's time saved linting ERB
00:20:31.600
files in CI and it unlocks the potential
00:20:33.840
to remove even more dead Ruby code in
00:20:36.240
the application.
00:20:39.200
Speaking of the future, I guess it's
00:20:40.880
time to address that too. With Action
00:20:43.679
View already solving all of these
00:20:45.280
different problems for us in the
00:20:46.720
present, what more is there for it to do
00:20:49.039
in the future?
00:20:52.320
What I really find so compelling about
00:20:54.880
these three wildly different topics,
00:20:57.520
cache invalidation, pre-ompilation, and
00:21:00.320
dead code identification, is that
00:21:02.400
they're all built on the same
00:21:03.679
foundation. All three of these things
00:21:06.159
depend on render parsing to function.
00:21:08.880
And while action view pre-ompiler
00:21:10.640
already defaults to using the prism
00:21:12.480
render parser when available, action
00:21:14.559
view does not. Meaning the digtor and
00:21:17.440
anything else that builds on it, like
00:21:18.799
loose ERBs, are stuck with a suboptimal
00:21:21.360
implementation.
00:21:23.520
Just this month, someone opened an issue
00:21:25.440
because the ERB tracker attempts and
00:21:27.919
fails to parse HTML attributes that
00:21:30.480
include the string render.
00:21:33.280
Maybe we can improve the regular
00:21:34.960
expressions to fix this edge case. But
00:21:37.600
as long as regular expressions are used,
00:21:39.679
there will probably still be more edge
00:21:41.440
cases to fix. By switching to a real
00:21:44.480
Ruby parser, we eliminate the
00:21:46.159
possibility of edge cases completely.
00:21:49.280
The ripper render parser was added in
00:21:51.120
Rails 7 and the Prism parser in Rails
00:21:53.760
7.2.
00:21:55.280
However, there hasn't been a way for
00:21:56.880
applications to even test out these
00:21:58.960
improvements without using undocumented
00:22:00.960
APIs.
00:22:03.039
Let's fix this in Rails 8.1. The render
00:22:05.840
parser should be configurable and Prism
00:22:08.159
should be the default for new
00:22:09.360
applications.
00:22:12.320
Something else we can improve across
00:22:13.760
these areas is making the integrations
00:22:15.600
tighter. As I mentioned while discussing
00:22:18.159
cache invalidation, Rails magic is just
00:22:20.640
doing the correct thing so that
00:22:22.480
developers don't have to think about
00:22:24.080
certain problems. While I talked about
00:22:26.720
action view pre-ompiler today, I don't
00:22:28.880
really think that you should be adding
00:22:30.240
it to your gym file. You shouldn't even
00:22:32.640
have to know it exists. It should just
00:22:34.799
be a part of Rails.
00:22:37.200
The benefits of upstreaming don't stop
00:22:38.880
at increased performance. Action view
00:22:41.280
pre-ompiler's more advanced render
00:22:42.960
parser would also benefit loose ERBs as
00:22:45.679
it would enable parsing those explicit
00:22:47.520
renders in controllers.
00:22:50.000
Additionally, I'd also love to see loose
00:22:52.080
ERBs upstreamed in some form as well.
00:22:55.760
Action view already provides some
00:22:57.280
similar rig tasks that print the
00:22:59.039
dependencies of a template and the Rails
00:23:01.520
CLI also includes a command for listing
00:23:03.679
unused routes. Lucbs would fit right in
00:23:06.960
with these existing tasks to help
00:23:09.120
developers understand their templates
00:23:10.960
dependencies and keep their view paths
00:23:12.960
nice and tidy.
00:23:14.960
In addition to making loose ERBs better
00:23:17.120
by upstreaming action view pre-ompiler,
00:23:19.520
action view pre-ompiler also benefits
00:23:21.600
from loose ERBs being upstreamed.
00:23:24.400
Template pre-ompilation means that all
00:23:26.559
templates get compiled whereas
00:23:28.400
previously templates would only be
00:23:29.760
compiled on use. Therefore, cleaning up
00:23:32.720
unused view templates in your
00:23:34.240
application means you can have even less
00:23:36.480
memory usage than pre-ompiling templates
00:23:38.880
alone.
00:23:41.440
Finally, I want to reemphasize how cool
00:23:43.520
it is that Rails has these foundational
00:23:45.760
classes that compose to solve such
00:23:48.240
interesting problems. In this talk, I
00:23:50.960
showed how the dependency tracker, which
00:23:52.880
was first added to Rails back in 2012
00:23:55.280
for calculating cache digests, has now
00:23:58.000
been repurposed 10 years later for a
00:24:00.400
very different problem space.
00:24:02.880
The more I reflect on this idea, the
00:24:04.799
more I wonder, what else can we build?
00:24:08.159
What other building blocks does Rails
00:24:10.000
contain that could enable developers to
00:24:12.400
solve some yetto-be identified problem?
00:24:15.360
So, I challenge you the next time you
00:24:17.360
have a problem in your own Rails
00:24:18.880
applications to try building a solution
00:24:21.200
with Rails. Learn more about all the
00:24:24.000
different building blocks it provides.
00:24:26.159
And if you're looking for a great place
00:24:27.760
to start exploring, use Action View.
00:24:30.799
Thank you.
00:24:39.600
I think we have time for questions.
00:24:42.080
Thank you. Sorry. Um, is action view
00:24:44.960
pre-ompiler able to work with other
00:24:47.679
templating languages like slim?
00:24:49.919
That's a great question. It can if it
00:24:52.320
uses the prism render parser or the
00:24:54.640
ripper parser because it's operating on
00:24:57.200
the compiled template like the actual
00:24:59.039
Ruby code. Um, the ERB tracker cannot
00:25:02.720
because it's looking specifically for
00:25:04.480
ERB format things.
00:25:07.200
Um, yeah. Yeah. So if we make if we make
00:25:09.679
prism the default then it will it will
00:25:11.919
work with all templating languages.
00:25:15.360
Any other questions?
00:25:18.720
All right. Thank you for real.