RailsConf 2020 CE

Advanced ActionText: Attaching any Model in rich text

Advanced ActionText: Attaching any Model in rich text

by Chris Oliver

In this video presentation at RailsConf 2020, Chris Oliver discusses advanced usage of ActionText, a rich text editor integrated into Rails. The session focuses on how developers can attach any model to rich text fields, enhancing their applications' interactivity and flexibility.

Key points covered include:

- Introduction to ActionText: ActionText serves as a rich text editor, allowing users to format text, add images, and utilize HTML rather than markdown. This integration simplifies complex text features like user mentions and file uploads.

- File Upload Integration: Unlike markdown, ActionText handles file uploads smoothly by integrating with Active Storage, ensuring multimedia elements are embedded correctly and stored as HTML.

- Resolving User Mentions: Chris highlights how traditional methods of managing user mentions can lead to outdated references. ActionText dynamically links mentions to current database records using signed global IDs, ensuring fresh data is always displayed.

- Adding Rich Text Fields: The speaker explains how to add rich text fields easily through Rails generators, demonstrating how the has_rich_text method works by internally managing associations without the need for additional database columns.

- Embedding External Content: Oliver describes the procedure for embedding content from external sources like YouTube. By customizing the Trix editor's behavior using JavaScript, developers can detect embedded video URLs, process them, and render them appropriately.

- User Mentions with Tribute.js: The presentation delves into utilizing Tribute.js to enhance user mention features. By detecting specific text patterns, Tribute.js allows for autocompletion of user names while preserving dynamic references to their latest data.

- Complexity and Flexibility of ActionText: Although ActionText offers robust features for managing rich content, Chris emphasizes the added complexity of using signed global IDs. Encoding and rendering dynamic links and attachments require careful management within the backend and editor's interface.

In conclusion, the implementation of ActionText offers a powerful way to enrich user interactions in Rails applications by allowing attachments to real-time models and data, thereby presenting users with the most accurate and engaging experience. Chris encourages developers to explore the source code available on GitHub, highlighting the practical examples and features introduced in his talk.

00:00:09.040 Hey guys, I'm going to be giving a talk on the advanced usage of ActionText and how you can use it to attach any model in your Rails application to your rich text fields. I'm Chris Oliver, and you can find me on Twitter at @excid3. I run a site called GoRails and record screencasts talking about exactly this kind of stuff.
00:00:20.960 We are going to dive in. If you aren't familiar with ActionText, it's basically a rich text editor for Rails. You have probably used WYSIWYG editors or Markdown in the past, but this is now built into Rails. It uses the Trix editor, and you can do things like make your fonts bold or italic, add links, or create bullets.
00:00:28.400 It is also connected to your Active Storage out of the box, so you can upload files. You will see that in the example here; we upload the Ruby logo to our rich text editor. Everything is connected, uploaded behind the scenes, and inserted into our rich text.
00:00:40.719 All of this is powered by the Trix editor, which was created by Basecamp. They use it in Basecamp, and all of this is stored as HTML in the backend. This is a rich text editor that generates HTML, and when it saves server-side, it saves that HTML. Unlike Markdown, where you're saving Markdown and converting it to HTML, you have actual HTML in your database.
00:00:53.360 File uploads come out of the box with ActionText. That’s something that can be a bit tricky in Markdown, where you would have to generate some Markdown to insert your files. You can see an example of that if you've ever uploaded a file to GitHub, as they use Markdown for their comment system.
00:01:08.960 One of the significant advantages of using HTML over Markdown is the ease of creating user mentions, such as @ mentions. In Markdown, if you want to mention a user, you would have to search for a username and replace that with a link to the user, then render that out in your HTML. GitHub has a library called HTML Pipeline, which helps process that Markdown and replace user mentions with links.
00:01:30.680 However, a challenge arises when a user changes their username. In this screenshot here, we have an app mentioning a user that doesn't exist anymore. It's just regular text, and you can’t click on it, which makes it complicated to update the username in comments. You would have to search every single comment to find a reference to the user and update that information manually across the entire database.
00:01:43.680 This is one of the problems ActionText aims to solve. When you're linking to a record in your Rails app, it will reflect up-to-date information, such as the current username or full name. Moreover, another issue with @ mentions in Markdown is mentioning users with spaces in their names. Some users may have names like 'John Doe' or include suffixes like 'Jr.' or 'Sr.' This presents difficulties in auto-completing or searching for names effectively.
00:02:07.680 The HTML approach allows for flexibility in searching and linking user mentions correctly. In this discussion, we're going to explore various examples of how to tackle this with ActionText. If you haven't set up ActionText before, it's very straightforward; you just run 'rails action_text:install'. This command will copy over migrations for ActionText and Active Storage because it uses Active Storage to store things like image uploads.
00:02:36.239 Additionally, it will install the necessary JavaScript for the Trix editor, and you’ll also want to include the image processing gem to handle image previews and thumbnails. To add a rich text field to any model in your app, simply add 'has_rich_text' and give it a name. The 'has_rich_text' method generates methods and establishes an association that allows referencing the content.
00:02:57.680 This 'has_one' association connects to an ActionText rich text record, typically named 'body'. This structure facilitates accessing rich text content conveniently. Additionally, there's an approach to seamlessly integrate a rich text field when creating a model or scaffold. This particular feature is something I added to Rails, which is quite cool. These are alongside Active Storage file uploads—three fields in Rails generators that don’t map to database columns, as ActionText is polymorphic.
00:03:31.039 Because ActionText doesn't require adding fields to your models for rich text fields, this setup simplifies your scaffolding. It allows you to access the body by referencing '.body' on your post, which will give you a rich text database record, thereby providing an instance of the ActionText content class wrapping all your ActionText HTML.
00:03:44.240 We can load models and eager load their rich text content efficiently. When we say 'with_rich_text_content' and 'embeds', it ensures all of that information is included in the temporary rendering, avoiding any N+1 queries during access.
00:04:00.879 To understand how ActionText operates, we need to consider the ActionText rich text table. When we say 'has_rich_text_body', it creates a record in the ActionText rich text table. The text column—referred to as 'body'—stores our HTML, which is then managed by the ActionText content class, adding useful methods around it.
00:04:18.840 Here’s an example of the raw data you might encounter in there. Just as you saw earlier, when you add an image, it generates a div for the image plus an action text attachment. The ActionText attachment tag will have some attributes but no content on its own. Very importantly, the ActionText HTML stored in your database will reference attachments but avoid storing actual HTML.
00:04:39.360 When ActionText processes the HTML for rendering, it retrieves the database records for Active Storage blobs corresponding to images and renders those appropriately in the browser. The way this is done involves a signed global ID or 'sgid' as an attribute, which helps benefit from the ActionText attachment and securely links back to the database.
00:05:02.640 The sgid structure looks like this: 'gid://your_app/ClassName/lookup_id'. For instance, if the entry references user number one, it can just call 'User.find(1)'. Signed global IDs work similarly but are server-signed to manage their purpose and can include expirations. ActionText's sgids do not have expirations but are marked as attachable, serving as pointers for attachment lookups.
00:05:29.680 So when ActionText processes each attachment, if it has a signed global ID, it runs through the global id locator to ensure that the signed global ID is accurate and retrieves the relevant records. In our example with file uploads, this will return an active storage blob that finds the image. This setup prevents tampering, ensuring that users cannot change user IDs to reference other profiles fraudulently.
00:06:02.720 This robust design works across different classes as long as they support global ID lookup. The most complex part of ActionText involves acquiring the signed global IDs, which requires a server request to sign the attachment and return it back to JavaScript for rendering. Once the server locates the record, it will render a specific template when inserting it into the Trix editor, which has two possible templates: one for rendering it out and another for when the content is edited.
00:06:39.920 The templates might differ; for example, one could display a field for input captions while the other would simply show the caption in the final rendering. The process allows you to add various HTML elements as necessary, like figure tags, image tags, and captions, depending on how you structure your attachments.
00:06:58.760 Because of the necessity for these different templates, it does not make sense to store static HTML in the database, especially when working with Action Text. The same goes for action text attachments used in posts, where the active storage references will drive the interaction. Understanding how to manage this will prove vital as we prepare to add features such as YouTube embeds.
00:07:19.520 To implement a YouTube embed, we need to customize Trix to detect when a user initiates the embedding of a YouTube video. We will do this by monitoring the URL link. When the link is clicked, a modal popup appears that lets the user paste in a YouTube URL. If identified via regex, it will display a link confirming whether the user wants to embed the URL, with a button to confirm the action.
00:07:59.520 Next, we write a little JavaScript that imports Trix to customize their toolbar, including the language configuration for internationalization. We will override the getDefaultHTML function to suit our needs, simplifying templates to display content matching our embed regulations for YouTube videos.
00:08:11.760 In the implementation, we'll need to add user interface elements like buttons for confirming the embed and ensure that our regex works efficiently to capture all variants of YouTube URLs. If a match occurs, we ensure a container for the embed becomes visible, and if not, we hide the option altogether.
00:08:48.000 Once the user clicks the embed button, we want another layer of verification to ensure that the link is valid by reapplying our regex check. If it matches, we extract the video ID, then make an AJAX request to our Rails app to embed the corresponding YouTube video.
00:09:17.440 The Rails app will have a dedicated YouTube controller to handle this. Instead of making external API requests, we can utilize in-memory data storage because the thumbnail URL remains consistent. Thus, we keep things efficient and effectively cache URLs to enhance performance.
00:09:41.440 This YouTube instance we create will reference our unique ID and form the basis of the global ID later on. Rendering out HTML for the YouTube thumbnail straightforwardly will allow us to include this in our JSON response when embedding a YouTube video.
00:10:00.640 In the YouTube class, we need to set up ActiveModel attributes alongside the action text attachable to wrap this up effectively. The critical aspect of our class will involve using this module to create a signed global ID linking the ID to our class name.
00:10:36.480 While creating instances in memory can be efficient, it’s crucial to ensure any necessary data points get cached to the database. Once our YouTube video ID is correctly referenced and rendered into HTML, we will include it in our active storage response.
00:11:31.040 Casting our presentation approach into two rendering methods allows us to specify what appears in the Trix editor and the final version of the content. For instance, in the editor, we may show only the thumbnail, while the final display could utilize an iframe to render the playability of the YouTube video.
00:12:18.080 The Trix editor provides the flexibility to construct templates that showcase how items display. With considerations to user interactions—from simply viewing video thumbnails to actual playback—it's key to create a coherent process that maintains user experience.
00:12:46.560 As we begin using our method to add YouTube embeds, we will notice how the rendered output influences the script used to manage user mentions, initiating another layer of JavaScript setup. To handle user mentions, we will utilize a library called Zurb Tribute, chosen for its ability to handle names with spaces, visually featuring our user suggestions.
00:13:32.840 When users type an '@' symbol followed by a name or username, we create an AJAX setup that transforms this by returning matching names from our database in JSON format, allowing users to select from valid options. This requirement establishes a flexible way to build a dynamic application.
00:14:19.760 Integrating Tribute with our controller will enhance our rich text fields effectively. By importing CSS and JavaScript for Tribute and establishing an instance within our fields, we can manage user mentions seamlessly while tracking that every mention is a dynamic element linked to its data.
00:14:51.680 Upon connection with our Stimulus controller, we will need further action to integrate Tribute; however, we can detach it as easily as it was created should we need to circumvent mentioning features in particular fields. Our user mentions will extract relevant data as avatars and names for enhanced visuals and interaction.
00:15:20.320 Thereafter, when calling the server-side process to look for matching usernames, we'll utilize SQL to accomplish this efficiently, filtering the results to offer a responsive experience. From here, we also dynamically generate JSON response codes, linking usernames to their IDs while maintaining visual and data integrity within the editor.
00:15:56.640 Using the @ mentions, it’s easy for users to know if their desired entry exists and if not, we will guide them with clear messaging. Moreover, the ability for users to deal with existing mentions when updating or changing their names provides an elegant solution.
00:16:41.440 The rendering must also ensure user mentions follow that exact style output—namely the relevant associations with each entry—and thus easily integrates avatars if required. The unified experience enables users to access real-time features in line with their entries.
00:17:18.800 Now, while the use of current technology introduces some complexity, managing updates or injections of new user data follows streamlined processes in Trix combined with the ActionText framework. Ensuring that attachments are secured with signed global IDs becomes an essential best practice for maintaining integrity within our handling of rich text.
00:17:57.760 With all these mechanisms in place, the user experience remains coherent, and updating user data continually reflects changes in comments or mentions directly on the application. Refreshing the areas tied to rich text is only a matter of calling proper methods coupled with these frameworks.
00:18:30.560 In conclusion, if you want to see the source code for these implementations, you'll find it posted on GitHub. Moreover, for continuous learning and development, you can access more videos on GoRails.com, featuring various ActionText topics on a weekly basis.
00:19:09.440 This wraps up our discussion on advanced usages of ActionText. I sincerely hope you found this insightful, and I regret that we aren't in person at RailsConf to chat directly, but feel free to engage with me in the comments.