Talks

Rails 5 Features You Haven't Heard About

We've all heard about Action Cable, Turbolinks 5, and Rails::API. But Rails 5 was almost a thousand commits! They included dozens of minor features, many of which will be huge quality of life improvements if you aren't using WebSockets or Turbolinks.

This will be a deep look at several of the "minor" features of Rails 5. You won't just learn about the features, but you'll learn about why they were added, the reasoning behind them, and the difficulties of adding them from someone directly involved in many of them.

Ancient City Ruby 2016

00:00:05.390 Hello everyone, hopefully people aren't in too much of a food coma. This is about Rails 5 features you haven't heard about, also known as 'Sean dramatically reads the changelog for you on stage.' Not really! My name is Sean Griffin, and I am a committer on Ruby on Rails. I'm the maintainer of Active Record. Recently, I have been doing a lot of work in Rust and I also run a podcast. What I actually want to share with you isn't so much about the features, but the stories behind their implementation: why they exist, why it took so long to add them, and the challenges faced in the process.
00:00:13.920 The first feature I want to talk about is called the Typed Attributes API. This feature holds a special place in my heart because it is what got me involved in Rails, and I've been working on it for about two years. I previously gave a more in-depth talk about it at RailsConf 2015, so if you're interested in the finer details of how it works, you can watch that talk. However, I've never really shared the story of how this API came into existence. It all started with a project I was working on for Thoughtbot at the time. We observed some recurring themes in applications that led to the need for the Attributes API, but one specific requirement stood out as the catalyst: all data had to be encrypted in the database at rest, even if the database itself was compromised.
00:01:07.680 We were initially using a gem called `attr_encrypted`, which generated code that looked like this: you would call the `attr_encrypted` class macro on your Active Record subclass. It assumed the database had a column named `encrypted_secret_thing`, and it created an accessor for the unencrypted version. This process handled all the encryption and decryption required. You could set `secret_thing = something`, and when you saved, it would encrypt it, resulting in nonsense in the `encrypted_secret_thing` field. The problem was that this method didn't work with querying methods like `where` or `find_by`. Thus, you could not create a query that worked correctly, which led to errors.
00:02:04.920 While it did provide an escape hatch with dynamic finder methods (which we used to see a lot of before Rails 3), this was not how we typically wrote Rails code anymore, and there were many other ways people wanted to query beyond simply finding by that specific column. Moreover, that feature of `attr_encrypted` was deprecated and was going to go away. I learned this while reviewing it again for this talk. In that particular project, we actually needed to use Ransack to create a more complex search form, which involved doing things like `LIKE` queries. As a workaround, we ended up monkey-patching Rails, which is not something I would typically recommend.
00:03:07.250 Shortly after that project, I attended a talk by Ernie Miller. He discussed some of the horrors of Active Record's internals, emphasizing how beneficial it would be if Active Record could infer the structure of your model from the database schema by calling a public API. This resonated with me, reflecting my own thoughts at the time, so I decided to implement this idea. However, it required significantly more work than anticipated. Adding the actual feature was quick, but reaching an implementation we were satisfied with took rewriting a substantial part of Rails internals. In fact, everything except for associations was impacted by this.
00:04:04.000 The final outcome of this project looks much cleaner if it were using the Attributes API today. The Attributes API is a class macro that takes two arguments: the name of the attribute and a type object. The type objects have a relatively simple API governing the transitions between user input, Ruby land, and the database. This approach allows for more straightforward and testable implementations since you can inject constructor arguments, such as a key for encryption.
00:05:02.229 The impact of this feature is extensive; any method that performs type coercion will go through the Attributes API. However, the more challenging aspect was ensuring that the internal code looked appropriate. The result is that Rails now infers your model structure from the database schema by looping through the database columns right after the schema is loaded. The process allows us to figure out the correct type object based on the column data. This streamlined coding made the Active Record process more efficient, replacing previous cumbersome methods.
00:06:03.990 Though I won’t delve deeply into the Attributes API now as it's a topic I've already covered extensively, it’s worth noting that another eagerly awaited feature finally arrived in Rails 5: the ability to add 'or' expressions to your where clauses in Active Record. This feature has been a long time coming, and it raises the question: why did it take so long? The core challenge was that the correct API wasn’t immediately evident. When new features are added to open-source projects, it can be incredibly difficult to change or remove them later on.
00:07:06.220 There were many proposals for this 'or' feature, but we ultimately landed on a method for the relation that takes another relation as an argument. This means you can easily compose queries using named scopes, which allows for efficient reuse of query components. While it can be verbose when invoked from outside the model (e.g., from a controller), it encourages best practices in code abstraction and compositional design.
00:08:02.120 Another common issue discussed in the community is accidentally pointing your database URL at production and running extensive migrations there. This frequently leads to unintended consequences, particularly when employing tools like Database Cleaner, which clears data from the database at the end of each test. As a precaution, Rails 5 introduces a helpful safeguard for this situation. Now, Active Record maintains metadata to track which environment commands were run against. When a potentially destructive action is undertaken, Rails checks if the DATABASE_URL environment variable is set to a different environment than what the current command is running in. If so, it presents a warning, helping prevent catastrophic mistakes.
00:09:30.100 Rails also offers migrations that need to be updated and maintained over time. The challenge arises as migrations are often written against one version of Rails but run later, which can lead to unexpected problems if breaking changes occur. To address this issue, Rails now tracks the version of Rails against which a migration was generated, offering a different API depending on that version. The aim is to mitigate hazards for users down the road, despite this also increasing the maintenance burden for open-source developers.
00:11:12.400 Rails 5 introduces a new method called `accessed_fields`, which reveals all fields ever accessed on a model instance. For example, if you create a user and then access the name field, calling `accessed_fields` later will yield an array that includes 'name'. This utility aims to help developers identify cases where selective loading might improve performance, allowing them to call `select` based on strictly necessary fields.
00:12:05.490 In conjunction with the revamping of our type system for the Attributes API, Rails now includes straightforward definitions within its Attribute class. This boasts an efficient design to minimize recalculating values in case they are nil or false, contributing to performance optimization within Active Record.
00:13:06.480 To conclude, I hope these features will enhance your experience while using Rails. I would like to take a moment to share a project I've been working on called Diesel; it's an ORM for Rust. You can find us on the internet, and if you're interested in stickers, feel free to ask. Now, I'd be happy to take any questions. Thank you very much!