RailsConf 2018

Inside Active Storage: a code review of Rails' new framework

Inside Active Storage: a code review of Rails' new framework

by Claudio Baccigalupo

In this video presentation titled "Inside Active Storage: a code review of Rails' new framework," Claudio Baccigalupo discusses the newly introduced file upload library in Rails 5.2 known as Active Storage. The session, conducted at RailsConf 2018, provides insights into both the usage and internal workings of Active Storage, showcasing how it integrates with Rails components like Active Record and Action Pack. Baccigalupo invites attendees to explore the code, emphasizing its elegance and the potential for community contributions.

Key points from the presentation include:

  • Introduction to Active Storage: Active Storage simplifies file uploads in web applications, bundled within Rails 5.2, replacing previous third-party options like Paperclip.
  • Basic Usage: Demonstrating through a scaffold application, users can easily integrate file uploads by simply adding a file field in HTML, whitelisting parameters in the controller, and using the has_one_attached method in the model.
  • Functionality and Features: Besides basic uploads, Active Storage supports various functionalities like generating image variants, dealing with PDFs, and handling multiple attachments with methods such as has_many_attached.
  • Understanding Class Architecture: The presentation covers three main classes within Active Storage:
    • ActiveStorage::Service: Manages the movement of bytes and abstracted storage solutions (e.g., local disk or cloud storage). Different implementations are provided, including local disk and cloud services like AWS S3.
    • ActiveStorage::Blob: Stores metadata about the uploaded file and its location in the database. Installation and migration commands are pointed out for setting up necessary database tables.
    • ActiveStorage::Attachment: Connects blobs to the relevant records in models, establishing associations between uploaded files and database entries.
  • Metaprogramming and Extending Rails: Baccigalupo highlights how Active Storage adds functionality to Active Record through metaprogramming, demonstrating how method definitions can create a seamless user experience with minimal code.
  • Conclusion and Encouragement to Contribute: The talk concludes with an invitation for developers to experiment with Active Storage, dive into the codebase, and contribute, as it is a young framework with room for documentation and feature improvements.

Overall, the session serves as both a guide to utilizing Active Storage effectively and an encouragement for greater community engagement in Rails development.

00:00:10.759 Hello, thanks for coming! This is awesome. I see a very full room. If you guys have an empty seat next to you, can you raise your hand? Okay, guys around, if you want to sit down, there are like 20 empty seats. Well, thanks for coming to RailsConf! It's been amazing so far, with a lot of great talks, and I had a lot of fun. I hope you had a lot of fun as well. We are also here to celebrate that Rails 5.2 was released just a week ago.
00:00:37.860 If you have made any contributions to the Rails codebase, can you raise your hand? Can everybody else give a clap to all those who made it possible? I'm not going to go through everything new in Rails 5.2; I'm just going to focus on one really exciting and interesting thing called Active Storage. Since this is RailsConf, I'm not just going to tell you what it is; instead, I'll go deep inside the code without scaring you too much. Hopefully, you'll also learn how things work.
00:01:04.378 This is a perfect segue to the previous talk, inviting you all to become contributors to the Rails codebase. Once you go past the stage of thinking it's just magic, and you look at the code, you might realize that it's Ruby with classes and objects, and you can contribute to that. In fact, Active Storage is the newest framework, and it might still lack some documentation, or maybe you can find some issues. Therefore, it's the perfect place for everybody to start.
00:01:30.659 My name is Claudio, and English is not my first language. You can download the slides if you want; they're already available. It might help you follow along. You can find the slides at speakerdeck.com/claudio-b.
00:01:52.770 Before I get started, I just want to mention that this talk is about Active Storage. Interestingly, I work for a storage company, but it's not cloud storage; we actually come to your place, take the stuff you don't need, wrap it up, take pictures, barcode everything, and store it in a warehouse. You can then request your items back through a Rails app called Commander. We're busy and hiring, but today, we are going to talk about Active Storage instead.
00:02:18.120 There is, however, a great resource to start with after this talk, and that is the official Rails Guides at guides.rubyonrails.org. There is a section about Active Storage that explains everything. It's really well-written, so I invite you to check it out. By the way, all the URLs that you see are also in my Speaker Deck, so you don’t have to write them down.
00:02:34.200 Now, let’s discuss what Active Storage is. Maybe you've heard about it, or perhaps you haven’t, and you're just curious. I'll give you an introduction to what it is and how to use it, which you’ll find is really easy. You might wonder how it works and what it consists of; that will be the second part of the talk. I'll go through the main classes of the library, and finally, I'll explain how everything works together. One of the reasons I'm giving this talk is that when I looked at the code, I found that Active Storage is really elegant, and it's a good code base to start with. So let’s get going with the introduction.
00:03:52.420 Okay, so what is Active Storage and how do you use it? Active Storage is a library to upload files. If you have a web application and you want users to be able to upload files through their browsers, this can be a good option. It’s not the only one; in the past, there have been other third-party libraries like Paperclip or CarrierWave. However, Active Storage ships inside Rails by default, so you already have it available. Therefore, you might want to give it a try. Now, how do you use it? I'll show you how to let users upload files in a brand-new app.
00:05:43.500 To do this, there are no dependencies. I'm just going to create a scaffold for a brand-new Rails 5.2 app and then add an upload feature. This is how you create a brand-new app in Rails: you do your `rails new` command. I'm going to call this app 'catalog' and generate a model called 'cat' because I haven't seen too many cats in this conference yet! Every cat will have a name, so we’ll create the database, run the migrations, and start the server. If you have ever used the scaffold in Rails, you know that it generates forms like the one you will see.
00:06:42.260 Now we have a form where you can add a new cat to your application, which is the baseline. Currently, we want to add a field for users to upload an image. This involves three steps. The first step is to add a file field to the HTML. We're adding a file field; I’ll name this field 'picture'—you can give it any name you like, for example, 'photo' or 'cute_cat_picture.' The second step is to tell the controller about this new field. We need to whitelist this new parameter due to the strong parameters in Rails. This means in your controller, you already have parameters defined as `params.require(:cat).permit(:name)` for all the parameters accepted from the form.
00:08:37.480 So, all you have to do is add 'picture' as another parameter that the controller will accept. Finally, in the model, the Cat model, we need to add one line of code: `has_one_attached :picture`. And that’s it! If you do all of this, you will have a form where users can upload a picture. The picture gets uploaded and attached to the cat, and if you have a show page, you can simply use an image tag like `<%= image_tag @cat.picture %>` to display the picture right there. Isn’t that awesome? A round of applause for Active Storage!
00:09:25.790 This is the basic usage, but there’s so much more you can do with Active Storage. For example, if you have a cat and a dog picture, you can display variants like a black-and-white image or a 90-degree flipped image. You will only need a library like MiniMagick in your gem file, and it can create those variants for you. Active Storage is not only for images; you can also use it with documents like PDF files. If you have a PDF, you can even display a preview of the first page as an image and resize it—say, to 100 by 100 pixels. And of course, if you have a cat, you’re likely to have many videos too!
00:12:30.660 With Active Storage, you can use `has_many_attached` as well, which works similarly. For each video, you can extract metadata such as size, angle, and duration. There's a lot more to learn about how to use Active Storage, including its experience with previewers and analyzers. I recommend reading the guides at the Rails site, and if you Google 'Active Storage how to use,' you will likely find a comprehensive blog post by Vladimir from Evil Martians. If you want to start using it, you’ll find it’s pretty easy, and if you want to implement variants and previewers, feel free to play around with a new app and see if it works in your production app.
00:16:48.090 Okay, now let’s get into how it works—what's inside the code? You know, we talked about using the routes, but you want to see how they work, so you open the codebase and look at the classes within Active Storage. To start, Rails is an open-source project, and its entire source code can be found on GitHub at github.com/rails/rails. There’s an Active Storage folder where you can access all the code. Another way to open source code is if you have the Active Storage library in your app. Just run `bundle open active_storage`, and it will open the source code in your editor—this works for any library in your app's Gemfile.
00:18:52.610 When you open the source code, you'll find a bunch of files, and it might be overwhelming to determine where to start. So, what you should remember are the three main classes of Active Storage: ActiveStorage::Service, ActiveStorage::Blob, and ActiveStorage::Attachment. I will explain what they are so that the next time you hear about a blob, you'll understand exactly what it is. First, let's look at Active Storage Service. This is the part that handles moving bytes, such as transferring a file from memory on your browser to your disk.
00:21:35.950 So, let’s say you upload a cat picture from your browser. An HTTP-uploaded file is taken by a component that stores those bytes, for instance, on your local hard disk in a specific folder under `/storage`. That is what the service does: it moves bytes from memory to disk. Active Storage service is not a real class, but it has other methods, including `upload`, `download`, and so on. Interestingly, if you call Active Storage upload, it raises an error because this is actually an interface. What it indicates is that there isn’t just one service; there are multiple subclasses, each implementing its own way to process files.
00:23:22.690 Let’s look at one of the subclasses: the Disk Service. This is the default service you will use when running your app locally, and it works well for that purpose. Its `upload` method takes an I/O file and simply calls the Ruby method `IO.copy_stream`, which copies bytes from one location to another. With this service, you store the file on your local disk, and for production, you will likely want to use a cloud solution like Amazon S3. The good thing about Active Storage is that it ships with a pre-built service for S3, so you don’t have to create it from scratch.
00:25:29.520 If you want to use Active Storage with S3, all you have to do is change a single line in the configuration file, which is called `storage.yml` by default. You will see a service option set to 'disk'; you simply change it to 's3' and ensure that you have your credentials set up for an S3 bucket. After that, it will work right out of the box. Active Storage also has support for Microsoft Azure and Google Cloud Storage, and if you have your cloud solution, you can create a store service for that by following the interface pattern of Active Storage.
00:28:36.030 Now, let’s review the role of the Blob in Active Storage. When you upload a file, the blob is responsible for storing the information regarding the file, such as its original file name, content type, byte size, and a checksum for integrity. The Active Storage blobs correspond to a table in the database, which has fields such as 'key,' 'filename,' 'content_type,' 'metadata,' 'byte_size,' and the 'checksum' for ensuring the file is not corrupted.
00:30:37.150 You might wonder where this table comes from since we didn’t create it ourselves. The first time you use Active Storage in your application and attempt to upload a file, you will see an error page saying it could not find the `active_storage_blobs` table. Active Storage will instruct you to run `bin/rails active_storage:install`, which is one of my small contributions to the library. This one command adds the necessary migrations to your app, and you just run those migrations. That's all you need to do for the table to be created.
00:31:48.930 Active Storage uses a different approach than other upload libraries that typically require you to add fields to every model. For example, if your cat model needs a picture, you have to manually add the picture field. However, in Active Storage, everything is handled internally within its tables, allowing you to keep your models clean.
00:33:41.900 Let me show you one method of the Active Storage blob, specifically the upload method. This method extracts all the necessary information from the file, calculates its checksum, examines the content type, and determines the size. It then stores this information and calls the service to save the file. Lastly, we have the Attachment class, which connects the blob with the model, thereby creating the association.
00:36:24.760 So, in summary, the three main classes in Active Storage include the Service, which is responsible for moving bytes, the Blob, which stores metadata about the file, and the Attachment, which associates the Blob with the original model. This is how everything fits together. I've shown you how easy it is to use Active Storage and how the main classes interact with each other. But how do they all work together?
00:39:39.800 At the beginning, I demonstrated that using Active Storage is straightforward, but there’s also some magic involved that isn't immediately clear. For instance, when you add a line of code like `has_one_attached :picture` to your model, it adds this entire behavior and creates a method 'picture' without Active Record initially having that method available. The reason for this is that Active Storage is designed as an engine. Active Storage includes initializers that extend Active Record to include methods such as `has_one_attached`. James Adam delivered an excellent talk on engines at this conference.
00:42:06.200 When a Rails app is loaded, and Active Record is initialized, it stops and extends Active Record with Active Storage Attached Macros, making the Active Storage methods like `has_one_attached` available. At this stage, that line of code is processed dynamically; it takes the name you provide and defines the associated methods. This uses metaprogramming techniques prevalent throughout Rails. The result is that when you use `has_one_attached`, the corresponding accessor methods are dynamically created, similar to how Active Record generates methods for associations.
00:44:51.210 As an example, when you write `cat.has_one_attached :picture`, you are effectively creating methods such as `picture`, `picture=` as well as helper methods for active storage. The backing class is called AttachedOne, which handles the logic for attaching files. The attach method is responsible for creating a blob from your attachment, calling the service to move the bytes to storage, and then creating the Active Storage Attachment itself.
00:47:39.790 So to sum it up, I hope this talk has helped you see that you can dive into the code base to learn and contribute. The library is fairly new, and there are still many potential contributions you could make, so I urge you to explore the issues on the Rails GitHub page. The code base isn’t as large as others; for instance, the Active Support library will be bigger, but do consider starting with Active Storage and also look into Active Support and Active Job.
00:50:28.880 Active Storage also offers a method called `after_destroy_commit`, which works automatically when you delete an instance of a model, such as a cat. If you delete a record that has an attached picture, the attachment will also be deleted from S3. This is managed through callbacks in Active Record that trigger after the deletion occurs. It specifies a job that will delete the image, allowing for asynchronous processing.
00:52:25.950 Lastly, while I didn’t dive into everything about Active Storage, I want to encourage you to explore additional aspects that you may find useful, like its JavaScript library. Sometimes developers want to upload files directly from their browser to S3 instead of going through the Rails app. Active Storage provides this library to handle that process seamlessly. By adding the JavaScript code in your views, it will create a blob key first, allowing your app to upload files directly to S3.
00:55:29.250 In addition, the Rails logging mechanism that reports actions like file uploads utilizes Active Support Notifications, an important feature embedded throughout Rails. Last but not least, Active Storage features a `routes.rb` file that manages URLs for displaying files in the browser. It integrates methods added in Rails 5.1, such as `direct` and `resolved`, which have been applied ingeniously. Thank you for your attention, and I encourage you to give Active Storage a try. If you have any questions, please feel free to find me here at the conference!