This video contains 6 individual talks

Lightning Talks: New and Improved Features in Rails 8.1
+5
See all speakers
See all 5 speakers

Sections in this recording




Summarized using AI

Lightning Talks: New and Improved Features in Rails 8.1

Andrew Novoselac, Jenny Shen, George Ma, Jill Klang, and Nikita Vasilevsky • July 09, 2025 • Philadelphia, PA • Lightning Talk

Introduction

This lightning talk session from RailsConf 2025, delivered by members of Shopify's Ruby & Rails Infrastructure team, introduces and explains several new features and enhancements being contributed to Rails 8.1. The focus is on practical improvements to facilitate developer productivity, security, and maintainability, based on real challenges encountered at Shopify and broader Rails ecosystem needs.

Key Points

  • Eliminating Postgres Not Null Violations (Jenny Shen):

    • Explains challenges encountered when uncoupling Shopify’s monolith from MySQL-specific constructs to support PostgreSQL.
    • Highlights an issue where inserting/upserting records with ‘nil’ primary keys causes not-null violations in Postgres, unlike MySQL.
    • Describes a Rails PR that automates handling of ‘nil’ primary keys by using SQL’s default value, improving compatibility and reducing manual intervention.
  • Structured Event Reporter for Rails (George Ma):

    • Introduces a new structured event logging API (Rails.events), addressing the limitations of unstructured logs.
    • Structured logs feature clear event names and keyword argument payloads, making logs more queryable and analyzable by observability tools.
    • Shopify’s implementation routes logs through Kafka to data stores, supporting efficient query and analysis.
  • Model-level Log Filtering for Sensitive Data (Jill Klang):

    • Discusses the risk of leaking sensitive data (e.g., credit card digits) via Rails logs and parameters.
    • Outlines existing approaches (configuration-based parameter filtering and model-level filter_attributes).
    • Introduces an improvement enabling easier model-level marking of sensitive attributes, improving visibility and reducing risk of accidental data exposure in logs.
  • Ensuring Tests Make Assertions (Nikita Vasilevsky):

    • Highlights that tests with no assertions can provide false confidence.
    • Rails 7.1 introduced a warning mechanism to detect such tests in MiniTest, promoting better test reliability.
    • Offers examples of assertionless tests and suggestions for improvement.
  • Error Reporter Middleware (Andrew Novoselac):

    • Discusses the Rails error reporter, a mechanism for unified error reporting via subscribers (e.g., log, metric, error tracking subscribers).
    • Addresses code sharing between subscribers and introduces a new middleware layer that can inject shared metadata/context before error reporting.
    • Middleware enables cleaner, DRY error tracking architectures and is extensible for various concerns.
    • Notes the caveat of context mutability and the need for care with sensitive data within error contexts.

Examples and Anecdotes

  • Real-world issues at Shopify provide the context for many changes, including database incompatibilities, observability scale, and sensitive data handling in large apps.
  • The talks include code snippets and pseudo-code to illustrate technical issues and solutions for each discussed feature.

Conclusion

These lightning talks highlight several significant enhancements and tooling improvements contributed by Shopify’s Ruby & Rails Infrastructure team for Rails 8.1. The focus is on making Rails applications more database-agnostic, observable, secure, and maintainable. Attendees are encouraged to adopt these features as they become available, provide feedback via PRs, and contribute further to open source.

Lightning Talks: New and Improved Features in Rails 8.1
Andrew Novoselac, Jenny Shen, George Ma, Jill Klang, and Nikita Vasilevsky • Philadelphia, PA • Lightning Talk

Date: July 09, 2025
Published: July 23, 2025
Announced: unknown

The Ruby & Rails Infrastructure team at Shopify are stewards of Ruby and Rails within the company, and our mission is to ensure they remain Shopify's tools of choice for building great software. Sometimes that means building big new features into Rails. But often it means finding gaps and making improvements to existing features in the framework.

Join us for a series of lightning talks about new features that we have added to Rails over the last year, to be released as part of Rails 8.1.

RailsConf 2025

00:00:16.720 My name is Andrew Noslac. I am the
00:00:19.359 manager of the Rails infrastructure team
00:00:20.800 at Shopify and we are here to do some
00:00:23.600 lightning talks for you today. Uh the
00:00:25.680 theme is new stuff we've built for Rails
00:00:28.400 8.1. Uh so but first just to introduce
00:00:31.119 us this is our team. A nice moody lit
00:00:34.559 photo taken by Aaron Patterson recently.
00:00:37.040 Uh so there we are. Uh what do we do? We
00:00:39.600 work on Ruby and Rails at Shopify. So we
00:00:42.480 help other developers use Ruby and Rails
00:00:44.559 better. We help improve the framework
00:00:46.079 and the language and the other tooling
00:00:47.360 in the ecosystem to help developers at
00:00:49.200 Shopify and developers in the ecosystem
00:00:51.920 like yourselves. That's the mandate we
00:00:54.239 have. Uh I'm going to keep the intro
00:00:56.399 short and sweet. So, there's some uh QR
00:00:58.480 codes you can scan if you want to look
00:00:59.680 at our blog or the Shopy Careers page.
00:01:02.399 Uh, and I'll put this back up at the
00:01:03.920 end. So, yeah, today we're going to talk
00:01:06.080 to you about some new stuff that's in
00:01:07.439 Rails 81 courtesy of us. Uh, this is
00:01:10.400 everything from some like big new
00:01:11.840 features to some small quality of life
00:01:13.680 improvements and bug fixes and uh, yeah,
00:01:16.159 stuff we're really excited to share. So,
00:01:18.240 without any further ado, I'm going to
00:01:19.600 call up our first speaker, uh, Jenny.
00:01:30.880 Whoa, the music is very nice. Um, thanks
00:01:35.040 Andrew. Hey everyone, I'm Jenny and
00:01:37.360 today I'll be sharing a story about
00:01:39.759 eliminating Postgress not null
00:01:42.640 violations. So this story begins many
00:01:45.759 years ago, some say to the origins of
00:01:48.320 Shopify. Um the Shopify monolith is
00:01:51.680 built on my SQL and over um the years
00:01:56.880 we've been doubling down on uh the
00:01:59.360 features it provides and there's a lot
00:02:01.759 of queries that um use my SQL specific
00:02:06.960 uh SQL
00:02:08.959 um we don't necessarily want to be tied
00:02:11.200 down to one database technology and over
00:02:14.640 the last couple of months some people on
00:02:17.760 uh my team like Hartley Nikita, Mikey,
00:02:20.959 some others, and myself have been
00:02:23.120 uncoupling our monolith application code
00:02:25.840 to support multiple databases um and
00:02:29.280 leverage active record more uh for our
00:02:31.840 queries.
00:02:33.760 Uh we've been running tests on Postgress
00:02:36.560 to identify uh my SQL specific behavior
00:02:40.560 and um fix them. So when we ran tests on
00:02:44.720 Postgress, we noticed a lot of um not
00:02:48.000 null violations with the id column. Uh
00:02:51.200 looking at a lot of these cases um kind
00:02:55.040 the code kind of looked like this where
00:02:57.280 there was a insert all and uh we're
00:02:59.920 building an array of records through
00:03:01.760 their attributes. So looking at this
00:03:04.640 closer, um if we have uh a list of
00:03:09.120 objects and they're new, the ID will be
00:03:11.840 nil. And when you take the attributes,
00:03:14.080 there's going to be a hash with the ID
00:03:16.239 key and um the value will be nil. Uh
00:03:19.519 when this is put through an insert all
00:03:22.080 uh my SQL it it works with my SQL
00:03:24.879 because my SQL would just use the auto
00:03:28.080 increment value to determine the ID
00:03:30.560 value. Well, for Postgress, it thinks
00:03:33.120 that you're actually trying to put nil
00:03:35.920 as the ID and this will fail cuz um ID
00:03:40.239 is a not null column.
00:03:43.120 A simple fix to this is just to avoid uh
00:03:47.040 just to remove the ID column for the
00:03:50.080 insert all. However, it's not super uh
00:03:53.840 that simple. Um in this uh example um
00:03:58.480 with customers uh we are um using an
00:04:02.560 upsert all. Uh so in this case there are
00:04:06.080 some records that we want to update
00:04:09.120 while some are um new records and the ID
00:04:13.280 column is nil.
00:04:15.599 To use our previous fix, you could just
00:04:18.720 remove the ID um from all the new
00:04:21.759 records. But this won't work with rails
00:04:24.080 cuz all objects being inserted must have
00:04:27.280 the same keys. Uh another solution a
00:04:31.120 solution to that is that you can split
00:04:33.520 into two queries. One just to insert new
00:04:36.960 records um and another query to update
00:04:40.720 existing records. But we shouldn't
00:04:42.560 really need to have two queries. A
00:04:45.759 better solution is that if there is an
00:04:49.280 ID um that is nil, we can just set that
00:04:53.040 to the SQL default. So tell Postgress to
00:04:57.520 use the default function or the auto
00:05:00.080 increment.
00:05:01.680 Um but why do us developers need to um
00:05:06.639 uh handle these nil values? Um couldn't
00:05:10.320 uh wouldn't it be better if Rails does
00:05:12.400 that? Well, I did put a PR to do exactly
00:05:16.880 that. So the core bulk of this PR is
00:05:20.880 that um when Rails is parsing the uh
00:05:24.720 values list it will um it will if uh the
00:05:30.160 key is a primary key column and the
00:05:33.440 value is nil uh Rails will automatically
00:05:36.160 use the connections default insert value
00:05:38.880 which in most cases is the uh SQL
00:05:42.560 default keyword. So um for Shopify this
00:05:46.960 eliminates the incompatibility between
00:05:49.280 my SQL and Postgress but for all of you
00:05:51.840 guys if you have a Rails app running on
00:05:54.880 Postgress this is just a simple change
00:05:57.440 that um so that you don't need to worry
00:06:00.080 about handling nil values uh for primary
00:06:04.319 key columns when insert and in insert
00:06:08.240 and upsert all. Um, thanks for listening
00:06:11.919 and next up is my awesome colleague
00:06:14.720 George that will talk a little bit about
00:06:17.360 logging.
00:06:28.800 Hello everyone.
00:06:30.720 Hello everyone. I'm George on the Rails
00:06:32.880 infra team and I'm a developer. Uh, so
00:06:35.120 today I want to give you a sneak peek on
00:06:36.960 the new structured event reporter in
00:06:38.479 Rails. giving you an overview about its
00:06:40.639 capabilities and why you should get
00:06:41.919 excited about about its release.
00:06:44.800 So in this quote, DHH gets right to the
00:06:46.720 heart of the problem. As he put it, when
00:06:48.639 you want to aggregate the data from your
00:06:50.479 Rails app into an observability
00:06:52.240 platform, it's a real pain trying to
00:06:54.240 parse the developer focused lines log
00:06:56.479 lines that Rails spits out by default.
00:07:00.479 Uh so the quote perfectly captures the
00:07:02.720 problem. On one hand, we have
00:07:04.000 unstructured human readable log lines
00:07:06.160 which are great in development and for
00:07:07.599 manual inspection. But what Rails is
00:07:09.840 currently missing out of the box is
00:07:11.680 structured logging that can be easily
00:07:13.280 digested by observability tooling. So
00:07:16.240 for years, Rails developers have had to
00:07:18.000 choose either transform unstructured
00:07:20.000 logs to make them queryable or build a
00:07:22.080 custom structured logging solution from
00:07:23.599 scratch.
00:07:25.199 So quickly taking a step back for some
00:07:27.280 additional context. Um as mentioned,
00:07:29.520 Rails provides developers with the
00:07:31.360 ability to create unstructured logs like
00:07:33.440 the one you see here. These logs are
00:07:35.440 generally generally one long
00:07:37.280 unstructured string and with the right
00:07:39.520 tools data platforms like Splunk can
00:07:41.840 parse these logs and make them
00:07:43.120 queryable. For years, our monolith had a
00:07:45.599 custom logging solution built on a
00:07:47.120 system just like this. Splunk is a
00:07:49.120 powerful tool and great text search. It
00:07:51.199 can take a long unstructured string,
00:07:53.360 parse and extract its fields into key
00:07:55.520 value pairs and allow us to quer query
00:07:57.520 previously unstructured logs.
00:08:00.240 So while this worked for a while with
00:08:01.919 the increasing size of our monolith, we
00:08:03.599 recognized a need for a better solution
00:08:05.360 to reduce both our observability costs
00:08:07.599 and query speeds. And with that with
00:08:10.479 that, this let us develop a new
00:08:12.000 structured logger API into Rails
00:08:13.759 directly named Rails.events.
00:08:16.800 So compared to unstructured logs, you
00:08:18.639 can quickly see the difference as
00:08:20.080 instead of a long payload string,
00:08:22.639 structured logs have a clearly defined
00:08:24.319 event name and payload data defined as
00:08:26.400 keyword arguments, enabling data
00:08:28.400 platforms to easily send these logs to a
00:08:30.240 database and make them instantly
00:08:31.680 queryable.
00:08:33.839 We set out to standardize a structured
00:08:35.680 logging system that wasn't yet another
00:08:37.360 custom solution that would only work for
00:08:39.200 Shopify's needs, uh, but something more
00:08:41.440 flexible and upstreamable to Rails. We
00:08:44.159 wanted an opinionated library that helps
00:08:46.000 developers do the right thing and log
00:08:48.000 structured events to as many upstream
00:08:49.680 platforms they wish to send logs to for
00:08:51.440 analysis.
00:08:53.519 In our case of Shopify, our logs first
00:08:55.600 get sent to CFKA as a stream of events
00:08:58.000 which are then processed and stored into
00:08:59.680 a ClickHose database. Our in-house
00:09:01.760 version of data dog observe sits
00:09:03.760 directly on top of this database
00:09:05.440 allowing developers to query structured
00:09:07.120 data efficiently in real time.
00:09:10.320 So setting this all up on your end is
00:09:12.080 straightforward. In your Rails app, you
00:09:13.680 just need to subscribe to your consumer
00:09:15.600 and then within the consumer, you want
00:09:17.120 to you'll want to define the emit
00:09:18.800 method. When an event is created, the
00:09:21.200 event reporter API in Rails will call
00:09:23.120 the emit method on all consumers. So in
00:09:25.519 this pseudo code example, we build the
00:09:26.959 log data, serialize it, and then send
00:09:29.120 lastly send it to CFKA.
00:09:33.440 Coming soon to a Rails near you will be
00:09:35.440 a new first class structured event
00:09:37.279 reporter. It's designed to live
00:09:38.959 alongside rails.logger, not replace it,
00:09:41.279 allowing you to keep your developer
00:09:42.640 focused log lines for development or for
00:09:44.640 debugging while enabling you to use the
00:09:46.720 new event reporter for queryable
00:09:48.320 production grade events. As shown,
00:09:50.560 structured logging provides a handful of
00:09:52.240 benefits, including but not limited to
00:09:54.320 being faster to query, costing less
00:09:56.399 long-term for storage, being type safe,
00:09:58.480 and of course being uniformly formatted.
00:10:02.160 And that's pretty much it. The event
00:10:03.600 reporter will soon be open as a PR
00:10:05.839 upstream to Rails in the coming week
00:10:07.600 where you can leave your review and
00:10:08.880 thoughts. So thanks to these folks
00:10:11.200 Adriana, Jill, Robert, and Francis who
00:10:14.560 are instrumental in the structure
00:10:15.760 structured events work at Shopify. And
00:10:17.760 thank you for listening. So next up,
00:10:19.360 I'll be handing it over to Jill who will
00:10:21.120 also talk about some cool filtered
00:10:22.880 logging work who that she's planning on
00:10:24.959 shipping into Rails.
00:10:36.959 Hi there. I'm Jill and I'm here to talk
00:10:39.519 to you about uh sometimes logs may
00:10:42.320 betray some of your sensitive secrets.
00:10:44.480 Um and the reason that I know about this
00:10:46.079 is because I've found this to be the
00:10:48.640 case in two large Rails applications.
00:10:51.920 and I will give you a little bit of
00:10:53.440 background and talk to you about uh what
00:10:56.000 I found and then uh what I'm adding. Uh
00:10:59.040 Rails is a so quick to get up a new
00:11:02.720 application and that's that's the beauty
00:11:04.480 of it. It's it's so helpful to just
00:11:06.640 scaffold something really quickly. So I
00:11:08.800 scaffolded this in all of like one
00:11:11.040 command in 10 seconds. Uh this is just
00:11:13.760 an application that has credit cards in
00:11:16.800 it. Uh, the only line that I added is on
00:11:19.519 line 28 where I just added one of these
00:11:22.240 unstructured logs that George mentioned
00:11:24.640 here too. Uh, super si, super quick,
00:11:27.440 super simple to get in here. Um, and
00:11:29.920 it's really helpful to get all this
00:11:31.360 stuff in and if you want to see like
00:11:34.000 what's happening, it's really easy to
00:11:35.440 debug. In this case, we are uh looking
00:11:38.959 and rails by default automatically logs
00:11:42.079 all the request parameters that you that
00:11:44.240 you uh that are sent to a controller.
00:11:46.720 So, it's really great for debugging that
00:11:49.279 your request parameters are in here and
00:11:51.360 that the credit card digits are in your
00:11:53.200 request parameters, but doesn't this get
00:11:56.079 sent to the third party data analysis
00:11:58.079 platforms that are all over people's uh
00:12:01.600 production applications? Um, they are.
00:12:05.040 Uh, is that HIPPA approved to send your
00:12:07.680 customers sensitive data? like there
00:12:09.519 there's definitely some data that you
00:12:10.959 want to see in there, but there's other
00:12:12.880 pieces of data that you really probably
00:12:14.399 shouldn't be sending to your thirdparty
00:12:15.839 analysis platforms. Um, is that going to
00:12:18.639 be secure if they get a if they get a
00:12:20.880 leak? It's not the the uh the the nicest
00:12:24.079 thing to do to uh your customers.
00:12:26.959 Luckily, Rails does have a filter
00:12:30.000 parameters configuration that you can
00:12:31.839 add and that you can make sure that
00:12:33.680 anything that's sensitive that you want
00:12:35.360 to keep out of these logs can be
00:12:37.920 filtered. It's in the configuration
00:12:39.839 file. Uh lines 6 through 8 have like new
00:12:43.200 Rails applications. These are the
00:12:44.880 default things that Rails by default
00:12:46.480 filters out of the logs. Um and then in
00:12:49.040 my application, I just added digits in
00:12:51.600 here because that was the credit card
00:12:52.800 digits thing. So from if you have just a
00:12:55.920 newly generated REST application, you
00:12:57.920 can see this view from before where it
00:12:59.600 has all the credit card information in
00:13:01.200 here. If you add it to filter
00:13:02.959 parameters, it has this lovely look in
00:13:04.800 here that says it's filtered, which is
00:13:07.519 great, super helpful. But when you start
00:13:10.800 scaling your Rails application and you
00:13:12.639 start getting hundreds of models, start
00:13:14.720 doing things like that,
00:13:17.279 you don't need to read this. I mean,
00:13:18.720 this is what ends up happening in
00:13:20.240 applications when you get a whole bunch
00:13:21.519 of sensitive information in there. then
00:13:23.200 you have this configuration file that's
00:13:25.200 not in the MVC. It's kind of hidden in
00:13:27.279 configuration and you're not necessarily
00:13:30.240 thinking about this every time that
00:13:31.839 you're adding a new attribute, every
00:13:33.279 time that you're adding something new,
00:13:34.959 you're not looking at the configuration
00:13:37.040 to remember to keep it out of the logs.
00:13:39.279 So, as things get bigger, like how can
00:13:42.560 we make this a little bit easier for
00:13:44.480 Rails developers?
00:13:46.399 Well, we do have something called filter
00:13:48.800 attributes which can live on the model
00:13:50.959 and you can just define that on credit
00:13:52.800 cards digits is a sensitive attribute
00:13:55.279 and you don't have to worry about like
00:13:57.199 finding it in your configuration and
00:13:59.360 because every day you're working in the
00:14:00.959 MVC structure you're looking at that
00:14:02.880 model and remembering oh hey like this
00:14:05.120 is sensitive information I can keep it
00:14:06.800 out of my logs. Uh so that's a solved
00:14:09.680 problem right?
00:14:12.320 Well, the issue with filter attributes
00:14:15.440 is that it only filters some things and
00:14:18.480 not others. So, here's filter parameters
00:14:21.279 filters both the parameters that are
00:14:23.440 coming into your application and the
00:14:25.120 manually created logs.
00:14:27.920 Not so much with filter attributes. It
00:14:30.160 only filters the things that are man in
00:14:32.480 manually created log messages and not
00:14:34.560 the parameters that are coming in. So,
00:14:38.720 if we're talking about the pre-existing
00:14:40.480 stuff that exists in Rails, can you
00:14:42.399 guess what my addition to Rails may have
00:14:44.639 be maybe for Rails 8.1? Um, this is just
00:14:48.399 a quick PR that I put up. Uh, you can
00:14:50.800 scan the QR code or check it out here.
00:14:52.639 It is not yet merged. It is in in the
00:14:55.199 process of being reviewed. You can check
00:14:57.360 it out. You can kind of see how the
00:14:58.959 sausage is made here. Um, but it
00:15:00.800 addresses the problem. So coming up in
00:15:02.959 Rails 8.1, really simply you can go into
00:15:05.760 all of your models and define what is a
00:15:08.000 sensitive attribute in Rails and make
00:15:10.000 sure that when you're in the development
00:15:12.399 process in the day-to-day, it's a little
00:15:14.000 bit more visible for you and a little
00:15:15.680 bit more manageable than a big list in
00:15:17.519 configuration. So uh there's more work
00:15:20.320 to do here. There's lots of things like
00:15:22.480 iterating in this whole process to make
00:15:24.079 it better. Um, I'm going to be around at
00:15:25.839 the Shopify booth if you want to chat
00:15:27.279 about it, but I'm really excited to uh
00:15:29.680 help everybody make their applications a
00:15:31.680 little bit more secure, filtered data a
00:15:33.600 little bit less. Um, next up, we're
00:15:36.079 going to hear from Nikita, who is a
00:15:38.320 fantastic person and secret karaoke
00:15:40.959 machine.
00:15:49.600 Hi everyone. Um today we're going to
00:15:52.399 talk and ask a simple but important
00:15:54.880 question. Do you test your tests? When
00:15:57.279 we write tests, the goal is to catch
00:15:59.519 bugs in our code. But sometimes we have
00:16:01.680 tests that don't actually check
00:16:03.360 anything. They just run. This is where
00:16:05.360 Rails helps us. Rail Scal warn us if
00:16:08.079 there we have tests that don't make any
00:16:09.839 assertions ensuring our tests are truly
00:16:12.079 verifying our code. Let's take a quick
00:16:15.040 look at how that works. Uh yes, I know
00:16:17.839 it's not Rails uh 8.1. It's been
00:16:20.720 introduced a while ago. Hopefully, many
00:16:22.399 of you have the had the opportunity to
00:16:25.279 use this feature. But Rail 7.1
00:16:27.680 introduced a handy feature that warns
00:16:29.519 developers when a test case doesn't
00:16:31.440 perform any assertions. The feature is
00:16:34.000 an after tear down hook. It finds a test
00:16:36.240 with zero assertion and that and if the
00:16:39.120 test that wasn't skipped or errored, it
00:16:41.360 will warn you with the test name and
00:16:43.360 location. This way you can catch and fix
00:16:45.759 tests that aren't actually verifying
00:16:47.440 anything which will keep your test suit
00:16:50.000 reliable. Uh while the rails feature is
00:16:53.519 specifically designed for miniest uh the
00:16:56.000 principle is still universal whether you
00:16:58.639 using miniest or another testing
00:17:00.399 framework or even a different
00:17:02.000 programming language. Uh the intention
00:17:03.920 is the same. Ensure your test actually
00:17:06.240 check what they're supposed to. Now
00:17:08.880 let's have a look at this mini test
00:17:10.880 output. We've got eight tests that run
00:17:13.919 but notice something important. Zero
00:17:15.839 assertions with no skips or failures. Uh
00:17:19.280 this is a perfect example of while why
00:17:22.160 the new Rails feature is so helpful. On
00:17:24.959 the surface, seeing zero failures might
00:17:27.199 make it seem like everything is fine,
00:17:29.120 but zero assertion tells a different
00:17:30.880 story. there is a high chance that these
00:17:33.120 tests aren't checking anything any
00:17:35.520 useful condition or and thus they're not
00:17:38.880 actually verifying our code behavior.
00:17:41.919 This is why testing frameworks emphasize
00:17:44.320 the import the importance of assertions.
00:17:46.720 A passing test suit doesn't necessarily
00:17:48.720 mean that your tests are validating the
00:17:51.360 expected outcomes.
00:17:53.440 Uh with the mini test output showing
00:17:55.520 zero assertions, it's time to dive
00:17:57.200 deeper into the types of tests that
00:17:58.880 might pass without any assertions. Uh
00:18:01.440 these are the tests that execute code
00:18:02.960 but often fail to validate any outcomes.
00:18:06.799 We'll explore a few of common examples.
00:18:10.240 The first one is a test that passes if
00:18:12.320 nothing raised. Uh this test runs a
00:18:14.880 method doesn't raise and it and if it
00:18:17.200 passes uh it passes if no exceptions are
00:18:20.799 thrown. While it does ensure the code
00:18:23.520 execute without any error, it doesn't
00:18:25.919 explicitly verify any specific behavior
00:18:28.240 or outcome. it's relying on the absence
00:18:30.799 of errors as the validation rather than
00:18:33.039 checking for expected results. So to
00:18:35.919 address this issue at the very least we
00:18:38.160 can make an improvement and explicitly
00:18:40.400 check for uh any error not being raised.
00:18:44.240 This test will still rely on the absence
00:18:46.640 of exceptions. At least it ensures the
00:18:48.799 intention is clear and the test
00:18:50.240 framework is actively verifying that no
00:18:52.559 errors occur within the block.
00:18:55.360 The next type of test is an
00:18:56.880 environmentbased test. uh this test is
00:18:59.840 will only run uh the assertion if a
00:19:02.400 certain condition in this case a
00:19:04.080 database adapter check is true. If the
00:19:06.799 condition is false the test effectively
00:19:08.559 does nothing resulting in no assertion
00:19:10.799 being made. Uh the test like this can be
00:19:13.360 problematic because they depend heavily
00:19:15.039 on the runtime environment potentially
00:19:17.360 causing them to pass without actually
00:19:19.120 checking any behavior if the condition
00:19:20.880 isn't met. At the very least, you can
00:19:23.360 use skip to make the to make it explicit
00:19:26.080 when the test condition isn't meant
00:19:28.160 isn't met. Using skip ensures
00:19:30.799 transparency, but comes with a caveat.
00:19:33.200 Uh even when the tests are skipped,
00:19:35.039 their setup block still execute
00:19:36.559 potentially consumer resources and
00:19:38.640 environment like Shopify. We use a
00:19:40.320 distributed execution model which means
00:19:42.559 the an empty test will still be
00:19:44.160 allocated to a worker and execute. uh a
00:19:47.360 more effective solution especially
00:19:49.440 environments like Shopify where we treat
00:19:52.320 skip test as a
00:19:54.960 uh as something we need as a temporary
00:19:57.600 solution as something that needs uh
00:19:59.600 attention in the future is to not define
00:20:02.559 the test at all under the condition. By
00:20:05.760 conditionally defining the test we avoid
00:20:08.080 cluttering our test suit um with test
00:20:11.200 cases that irrelevant to the current
00:20:14.559 environment.
00:20:17.120 Uh our next example will focus on
00:20:19.520 iterating over over the collection like
00:20:21.840 in the following test. Uh the issue here
00:20:24.400 may arise when the collection the
00:20:26.320 published post is empty. In this case
00:20:28.480 the l the loop won't be executed which
00:20:31.200 result in no assertions being made. The
00:20:33.600 test then passes by default potentially
00:20:35.840 missing important validation.
00:20:38.159 To tackle this issue of potentially
00:20:40.159 collections, we modify our test to
00:20:42.320 ensure it allows it always performs
00:20:44.799 meaningful check. Uh the revised test
00:20:47.679 has a uh published post empty check with
00:20:52.080 a flank which will fail the test if the
00:20:54.799 collection if the collection suddenly
00:20:56.720 becomes empty due to changes to our
00:20:58.960 fixture files or the any kind of
00:21:01.919 database setup mechanism.
00:21:04.320 uh it will signal that the expected data
00:21:06.720 wasn't available for validation
00:21:08.799 prompting developer further
00:21:10.400 investigation.
00:21:12.559 The next one is a generalized version of
00:21:15.280 a test that would rely heavily on
00:21:17.520 mocking or stubbing. Um in this case we
00:21:20.640 are using mini test mock to verify that
00:21:22.480 my method is called uh with the expected
00:21:24.799 arguments and returns the expected
00:21:26.640 value. Uh the key issue here or the
00:21:29.600 potential issue here is arises when uh
00:21:32.240 we don't do anything to verify that the
00:21:35.440 method is being called in this example
00:21:37.200 we use mock.verify verify which at the
00:21:39.760 very least will fail the test. If no u
00:21:42.720 if the my method wasn't called but a
00:21:44.880 better alternative for mini test would
00:21:46.960 be to use an assert mock helper which is
00:21:49.679 an actual assertion and does increment
00:21:51.760 the assertion counter. But as I
00:21:53.679 mentioned overall this is a bad example
00:21:55.520 of the test that uh relies heavily on
00:21:58.960 implementation details and doesn't
00:22:01.120 assert or test any kind of application
00:22:03.520 behavior.
00:22:05.520 uh for those of you working with RSpec
00:22:07.600 while it currently lacks uh counter for
00:22:11.200 expectations there is an open issue for
00:22:13.520 this feature pins and implementation. So
00:22:15.360 if you're interested this could be great
00:22:17.679 opportunity to contribute to our spec uh
00:22:22.080 finding tests overall finding test
00:22:24.159 without assertion is a great chance for
00:22:26.240 open source contribution. This is a
00:22:28.320 great reason to explore an open source
00:22:30.640 application or a library uh and make
00:22:32.799 your first contribution. Whether it's
00:22:34.880 going to be enhancing test tools to
00:22:36.559 catch assertionless tests or adding new
00:22:38.400 feature, this is your chance to dive in
00:22:41.360 the open source world and make an
00:22:43.280 impact. Personally, relying on this
00:22:46.080 feature, I was able to make a few
00:22:47.440 contributions to Rails removing or
00:22:49.600 improving some uh assertionless tests.
00:22:52.799 Uh so I welcome you doing similar thing
00:22:56.240 and thanks. Uh next I invite uh the host
00:23:00.559 of this lightning talk show and our team
00:23:03.120 lead Andrew.
00:23:12.400 All right. Who is just about ready for
00:23:15.280 lunch?
00:23:17.039 Yeah, I am. Uh so we have one more I
00:23:19.840 have one more uh lightning talk to share
00:23:22.320 with you uh and then I'm gonna go get a
00:23:24.240 Philly cheese steak personally. So I
00:23:25.919 invite anyone to join me. Um yeah I'm
00:23:28.480 going to talk about the Rails error
00:23:29.679 reporter middleware which is new. It's
00:23:31.360 coming in 8.1. Uh so who knows about the
00:23:34.480 Rails error reporter or uses it in their
00:23:36.559 app? Anyone? You should use it. It's
00:23:39.360 great. So the Rails error reporter is a
00:23:41.679 unified way to report errors in an app.
00:23:43.840 Uh it works with a subscriber model. Uh
00:23:46.240 this is pseudo code. It's not the code
00:23:48.080 in Rails. Uh actually, sorry, this is a
00:23:50.320 demonstration. So, uh yeah, you can add
00:23:52.559 some subscribers. So, here we have a log
00:23:54.320 subscriber and a metric subscriber. Uh
00:23:56.799 and then there's a method for like
00:23:58.159 handling an error. So, if an error is
00:23:59.520 raised or if we just pass an error to
00:24:01.039 the report method, then it will fire off
00:24:03.120 to those subscribers. Um so, this is
00:24:05.280 what it looks like under the hood in
00:24:06.559 Rails. This is like kind of a pseudo
00:24:09.600 code version, but basically it iterates
00:24:11.120 through each of the subscribers and
00:24:12.480 sends a report to them with the error
00:24:14.080 and some of the other metadata. uh and
00:24:16.480 then each of the subscribers implements
00:24:18.080 a report method. So uh the log subri
00:24:20.559 subscriber is sending uh our error to
00:24:22.720 the logs and the metric subscriber is
00:24:24.880 sending them to stats d in our case
00:24:26.480 which is what we use for metrics at
00:24:28.159 shopify. So yeah this was in implemented
00:24:30.720 back in rail 7 uh and to be quite honest
00:24:33.360 at Shopify we did not do a very good job
00:24:35.919 of actually implementing it in our app.
00:24:38.320 We had just one subscriber uh which was
00:24:40.799 called public exception.log. Uh this was
00:24:43.279 a god class that had a lot of reporting
00:24:45.840 concerns all mixed together. Logging
00:24:47.679 metrics, bug tracking stuff, other
00:24:49.600 stuff, whatever that is. Uh it was very
00:24:52.240 hard to reason about what this thing
00:24:53.520 does. And we found that often when like
00:24:55.760 changing code for one of the subs or one
00:24:57.520 of the concerns would like accidentally
00:24:59.440 break or affect uh some of the logs
00:25:01.919 coming for the other cons like for one
00:25:03.440 of like changing the logging concern
00:25:06.640 would like accidentally break something
00:25:07.919 in the error tracker which would like
00:25:09.520 drive everyone crazy. Um, last year at
00:25:11.760 Shopify, we had this big push to
00:25:13.279 standardize our error tracking. And as
00:25:15.760 part of that, I tried to do a big
00:25:17.600 refactor of this uh, and use the Rails
00:25:20.640 error reporter more how it's meant to be
00:25:22.960 used with multiple subscribers. So, my
00:25:24.880 goal was to go from public exception to
00:25:27.520 four subscribers, error tracker, metric
00:25:29.440 subscriber, log subscriber, and a query
00:25:31.200 timeout subscriber. That way, they could
00:25:32.720 all have their own concerns and like be
00:25:34.320 tested individually and isolated from
00:25:36.000 each other, just like uh, Rails and
00:25:38.000 Tens. Uh but I ran into kind of an issue
00:25:40.240 which is uh how do I share code between
00:25:42.559 the subscribers? So they actually have
00:25:44.400 some overlapping concerns that they all
00:25:46.159 want to do. We have uh an exception
00:25:48.480 classification system. Uh we also walk
00:25:50.799 the back trace to find out who like
00:25:52.799 caused the exception and that lets us
00:25:54.400 like tag the team. Uh and all the
00:25:56.880 subscribers care about that. So the
00:25:58.880 problem is like we would end up with
00:26:00.080 some duplicate code in each of the
00:26:01.440 subscribers and some of this stuff is
00:26:03.279 maybe a little timeconuming. So we could
00:26:04.880 also be doing duplicate work if we're
00:26:06.400 not careful. Uh so yeah, that's a
00:26:09.120 picture of me. Uh I started thinking
00:26:10.640 about how I can solve this. Um you know,
00:26:13.520 I thought of some like really naive and
00:26:15.600 not very good solutions. Like what if I
00:26:17.440 just like stuff this on an instance
00:26:18.960 variable on the error itself. So okay,
00:26:21.120 I'm going to do all the work to get the
00:26:22.880 uh error classification and then I'll
00:26:24.400 just like put it on in an instance
00:26:25.600 variable and then one of the other
00:26:26.720 subscribers can like pull it off. Um
00:26:29.120 obviously, I mean maybe not obviously.
00:26:30.960 Obviously to me this code is really not
00:26:33.279 nice. Like here's an example. maybe I
00:26:34.799 put it into a uh concern that they can
00:26:37.200 all share. Um, but I don't really like
00:26:39.520 um messing around with the instance
00:26:41.520 variables on the error. So, I thought,
00:26:43.120 you know, it seems like Rails is like
00:26:44.640 missing something for this case where I
00:26:46.159 want to share code. Uh, and it was uh I
00:26:50.080 added this uh with my friend Sam, my
00:26:52.240 co-orker Sam, we added this middleware.
00:26:54.400 Um, so the middleware is now sitting
00:26:56.960 between when you call the error report
00:26:58.960 and when we uh actually call all the
00:27:00.880 subscribers and it lets you add some
00:27:02.559 extra metadata to your error report um
00:27:05.120 that you can then fire off to all the
00:27:06.799 subscribers. So what that looks like is
00:27:09.039 again this is like pseudo code. The code
00:27:10.640 in Rails is a little more complex but
00:27:12.240 this is roughly what it looks like. So
00:27:14.640 uh we have two middlewares here. For
00:27:16.320 example, there's an error attribution
00:27:17.840 middleware and an error classification
00:27:19.600 middleware. And what happens when we
00:27:21.360 call reports uh or handle as well? Uh we
00:27:24.400 go through each of the middlewares and
00:27:26.000 we inject them uh we inject them with
00:27:27.919 the context. So the middleware uh we
00:27:30.080 call the middleware and then the
00:27:31.679 middleware is able to update the error
00:27:33.679 context with any extra metadata that it
00:27:35.919 has. Then that's the context that we
00:27:37.600 pass to all the subscribers. So the
00:27:39.840 middleware themselves have a very simple
00:27:41.679 API. They just have this call method. Uh
00:27:43.760 and what call does is it takes the error
00:27:45.760 and the context. We can do any code that
00:27:47.840 we like. Uh so here we're using our
00:27:49.679 error classifier to classify the error
00:27:51.520 and then we're getting that
00:27:52.400 classification and we're putting it on
00:27:54.000 the context. Uh and then we return the
00:27:56.000 context and then that context hash is
00:27:57.919 passed along to each of the subscribers.
00:28:00.080 Um and yeah, this was a big success. It
00:28:02.159 led to us having a very nice like clean
00:28:04.000 architecture for all our reporting. So
00:28:05.600 now we have three or four subscribers.
00:28:07.520 We actually ended up with quite a few
00:28:08.880 middleares with different concerns. Um
00:28:10.720 and we were able to extract those into a
00:28:12.399 gem. So now all of Shopify has the same
00:28:14.559 middlewares and all the different Rails
00:28:16.000 apps that we have and some of the same
00:28:17.679 subscribers. Um so yeah, this was a very
00:28:20.320 nice success taking one gigantic like
00:28:22.880 500 line class basically and kind of
00:28:25.120 composing it in all these little parts.
00:28:27.440 Um so yeah, highly recommend using both
00:28:29.440 the error reporter and uh once you're on
00:28:31.440 Rails 8.1 uh the middleares as well. Uh
00:28:35.039 yeah.
00:28:36.559 Oh, one gotcha. Yes, sorry. One gotcha I
00:28:39.360 wanted to call out about this is that
00:28:40.720 the context is getting mutated. That's
00:28:42.480 something to really remember. So, uh,
00:28:44.559 we're passing this context between each
00:28:46.159 of the subscribers. And so, we need to
00:28:47.760 know or you need to know that, uh, if
00:28:50.799 your logs are, uh, sorry, if in this
00:28:54.720 case I have a PII middleware, which for
00:28:56.720 some reason is adding someone's personal
00:28:58.399 identifying information to, uh, the
00:29:00.559 context, which I don't know why you
00:29:01.919 would do, but maybe it's something you
00:29:03.039 would do. If later on in one of our
00:29:05.120 subscribers, we're splatting out that
00:29:06.799 context, then we're actually going to be
00:29:08.399 logging some of that PII. So the one
00:29:10.799 call out is that this is now like a
00:29:12.320 mutable context that's coming through
00:29:13.840 like all the middleares to your
00:29:15.760 subscriber. Uh so yeah that's uh that's
00:29:18.559 one caveat. Um yeah, happy error
00:29:21.440 reporting. That's it for me. Uh and
00:29:23.440 that's it for Shopify. So here is all of
00:29:25.600 our uh blog and our careers page.
00:29:30.799 Thank you. Uh enjoy lunch and come find
00:29:33.279 us at the booth if you have questions.
Explore all talks recorded at RailsConf 2025
Ben Sheldon
Sam Poder
Rhiannon Payne
Joe Masilotti
Josh Puetz
Wade Winningham
Irina Nazarova
Tess Griffin
+77