Talks

Rails Engines Patterns

Rails Engines Patterns

by Andy Maleh

In the talk 'Rails Engines Patterns', Andy Maleh discusses the utilization of Rails Engines to enhance productivity and the flexibility of Ruby on Rails applications. The presentation addresses common challenges faced by developers when managing large, monolithic applications, particularly the redundancy and duplication of code. Maleh advocates for the use of Rails Engines to share features across multiple applications, ensuring that developers can maintain common functionality without sacrificing customization.

Key points covered in the talk include:
- Problems with Monolithic Apps: Many applications become large and unwieldy as businesses expand their requirements, leading to repetitive coding across multiple platforms.
- Introduction to Rails Engines: Rails Engines allow developers to package shared features (like models, views, and controllers) into a gem structure, promoting code reuse.
- Technical Insights: Maleh explains the structure and implementation of Rails Engines, emphasizing that they can simplify testing and reduce redundancy in code management.
- Key Patterns: The talk elaborates on various patterns for effective use of Rails Engines:
- Common Domain Pattern: Extracting shared domain logic into its own engine, reducing redundancy.
- Expose Helper Pattern: Customizing shared presentation logic through helpers in the engine.
- Expose Partial Pattern: Facilitating partial overrides for shared views while maintaining a clean base.
- Extension Points Pattern: Allowing different applications to insert components into a shared view dynamically, avoiding duplication.
- Configurable Features: Providing engines with configurable options to customize behavior across different applications.
- Trade-offs and Considerations: Maleh discusses the benefits of increased maintainability and productivity against the overhead of managing additional dependencies and complexity in project structure.
- Engines vs. Services: He highlights the differences between using engines for MVC components versus services that might require cross-application consumption and scaling considerations.

In conclusion, the talk equips attendees with a comprehensive overview of Rails Engines, practical guidelines for leveraging them effectively, and best practices for maintaining clean and modular Ruby on Rails applications.

00:00:24.640 All right, everybody. My name is Andy Maleh. I'm a software engineer at Groupon, and I'll be talking today about Rails Engines patterns.
00:00:31.810 So, what were the inspirations for this talk? There's a big problem in the Rails community: the problem of giant monolithic applications.
00:00:39.710 A lot of the time, people start with a small Ruby on Rails application. They're happy, they're chugging along delivering features.
00:00:47.660 But then, suddenly, the business decides to spin off multiple sites based on the same information: one for administration, one for the main users, and one for special section users.
00:00:53.540 And then, all of a sudden, they're rewriting features multiple times, or they're just building one monolithic app that really encompasses multiple apps addressing different parts of their business.
00:01:06.440 So, who here has experienced a monolithic Rails app? Okay, a lot of us have.
00:01:13.160 Last year, I worked for a company called Activa before I joined Groupon. I had a chance to consult on a Rails project that was brand new. However, the client was already aware that they had about three problems to tackle with their websites.
00:01:26.810 So, we wanted to prevent those problems, and what helped us with that was Rails Engines, which is the focus of this talk.
00:01:40.850 One of the key issues was the difficulty of reusing functionality that cuts across models, views, and controllers.
00:01:47.060 Whenever you're building multiple Rails applications that share some of the same features, you quickly run into redundancy.
00:01:52.610 For example, if you break it into multiple apps, you might find yourself repeating the same name and address forms in all three apps. Then, you'll be repeating the same search maps, and it gets really painful after a while.
00:02:12.920 So, you end up with a lot of duplication.
00:02:18.840 Here is an example: the client I consulted for was a college sports recruiting company.
00:02:31.410 This feature enable visitors of the site to conduct a college search via a map. They could select the states they wanted to search for colleges in, hit a button, and view a list of colleges along with additional information.
00:02:44.760 Each app was going to have its own customizations: one would let people select multiple states at once, while another wanted to allow them to select one state at a time. Each app had its own customization for how the results would look.
00:03:07.709 The typical thing a Rails developer would do, and I have done this in the past, is to duplicate features across applications. You might think, 'Forget software engineering, forget DRY (Don't Repeat Yourself),' because these ideals only seem applicable to small problems.
00:03:31.980 However, it turns out I was wrong. Rails Engines solve those problems.
00:03:44.700 With Rails Engines, you can break common behavior into reusable Rails Engines and still customize the models, controllers, and helpers in each project exactly where needed, as well as the views.
00:04:01.980 So, you get the best of both worlds: you cut down on redundancy, which means better maintainability for your code while still allowing applications to customize behavior as they need.
00:04:13.109 I will go over more technical details on how to use them in later slides. The example domain we were working with was a high school recruiting Rails app.
00:04:30.400 The three applications—an athlete recruiting app, a public student profiles app, and a search map feature—all needed to call a shared search map.
00:04:43.240 We were able to put the search map functionality in a Rails Engine so all three apps could reuse it.
00:04:55.120 The three of them had similar common domain elements, so we also put those in another Rails Engine.
00:05:01.390 This is an example of the college search map in one of the apps. This is how it looks in another app, utilizing the same exact code.
00:05:12.100 The engine and the logic are shared, and the common logic and presentation elements are part of the engine, including the map. One app had the map displayed at full size, while another app had it at 70%.
00:05:25.390 We were still able to achieve that level of customization without duplicating data. Each app could also choose whether or not to list the state names.
00:05:39.700 We customized the styling of the search bar for different applications as well.
00:05:47.140 Now, before I continue, who here knows what a Rails Engine is? Okay, that's a relatively good number. Who here is actively using Rails Engines? Good!
00:05:54.340 Not the same hands, but that's good. Some people may have looked into it and not fully understood the mathematics and logic, but I was once like that myself until I tried it on one of my projects.
00:06:11.230 So, let's dive into the details. A Rails Engine is simply a Ruby gem plus MVC stack elements. Many people fear that Rails Engines are complicated technologies that complicate their projects; however, people already use Ruby gems.
00:06:25.540 A lot of Rails developers have experience working on projects where they used Ruby gems to extract common logic. Engines are just a step further that allows you to extract not only shared logic but also MVC elements, such as models, views, and controllers.
00:06:49.630 This allows you to reuse models, controllers, helpers, and even assets, which is very convenient.
00:07:05.110 We were able to reuse the college search map image, routes, rake tasks, and generators.
00:07:11.040 You can also separate your tests from the main app and push them to the engine, which means you don't have to run the entire test suite on that app every time you modify the engine.
00:07:23.110 When you're not modifying the engine, you simply run the app's smaller test suite, excluding the engine tests.
00:07:35.710 This applies to libraries and other components as well.
00:07:42.580 Now, I want to give a brief tutorial on how to set this up, but since the focus of the talk is on Rails Engine patterns, I won’t go into too much detail.
00:07:55.120 The engine structure is similar to a Rails app; it has app, config, lib, and spec directories. The app directory simulates the engine being an app, allowing you to run RSpec.
00:08:10.300 The config directory is there for configuration elements, while the lib directory houses the engine's specific files.
00:08:17.830 In the lib, there’s an engine name file, much like any gem would have, which defines what libraries it needs.
00:08:23.800 You can use tools like Jeweler to package an engine into a gem for reuse. Just specify the engine's name and the necessary libraries within the engine file.
00:08:36.490 When the engine is declared as a gem, you can consume it as a regular gem or use Git with Bundler.
00:08:49.250 For my project, we found that using Git for internal projects that had multiple websites was the fastest technique, without needing to maintain a gem server.
00:09:05.290 This method also allows you to maintain version control and utilize revision numbers efficiently.
00:09:18.360 It's important to be cautious with load order.
00:09:23.110 Historically, Rails app files loaded before engine files, which was counterintuitive because the engine is a library to be customized in the app.
00:09:35.710 I recommend reversing this order, ensuring the engine loads first, allowing for proper customizations.
00:09:42.580 This can be done either by monkey patching an older Rails version or utilizing a new feature called config.reloading in later versions.
00:09:55.120 Now, Ruby code customization is straightforward. If you declare a model, helper, or controller with the same file name and path in the app, it gets mixed into the engine.
00:10:05.290 This allows for adding new behavior, replacing existing methods, or extending them without duplication of code.
00:10:18.700 For example, if you have a common ActiveRecord model with associations you don't want to redefine across multiple applications, you can define it once in the engine.
00:10:37.799 Then, in each individual app, you can open the model and add necessary methods specific to that app.
00:10:51.420 This prevents code duplication and keeps the engine clean, maintaining only the common minimum.
00:11:05.840 Now, let's discuss view and asset customization. Engine views and assets can be customized by redefining files in the Rails app, which completely override those in the engine.
00:11:18.800 This means that unlike Ruby files, which merge, if you redefine those in the app, it replaces what's in the engine.
00:11:32.720 One of the reasons I presented Rails Engine patterns today is to navigate around this limitation.
00:11:52.410 You can override any files necessary, like ERB or Haml files, JavaScript files, and CSS files, to customize JavaScript functionality.
00:12:03.720 For example, one app might customize an HTML view to include extra information. That requires temporarily duplicating the first line, for instance.
00:12:19.400 With the right patterns, we’ll later explore techniques to reduce that duplication.
00:12:34.550 So, how do we manage development with engines? Recommendation is to have each engine live in its own repository, independent of the Rails application.
00:12:47.140 This prevents a cluttered codebase and ensures each engine can manage its own gem dependencies. Engines should share the same Ruby version as the Rails app when possible.
00:13:04.000 Our daily development process involved making changes to an engine, running tests, and then committing those changes. We kept working in pairs, allowing for thorough testing.
00:13:17.310 After we finalized our changes and passed the tests in the engine, we committed them, updated the gem version in the main app, and ran 'bundle install' again.
00:13:32.280 We could then commit changes in the app, preparing it for deployment. However, this often became cumbersome as we'd frequently have to go back, modify the engine and update the app.
00:13:47.120 Making multiple trips to install bundles can become quite annoying, especially when everything needs to be re-committed and bonded together.
00:14:04.950 We realized that instead of always going back to the engine repository for slight changes, symlinking the directories was a beneficial strategy.
00:14:17.280 By creating a rake command, we were able to standardize our symlinking process, making it easier to work between the app and engine seamlessly.
00:14:34.920 We trimmed the busywork down to a minimum, focusing only on the required final commits.
00:14:42.170 If you utilize this technique for development, make sure to not run 'bundle install' within the engine's context until you're done.
00:14:50.370 This can produce trouble; keep your workflow in mind and streamline your processes.
00:15:06.790 This way, you can switch between working in the app and engine efficiently without losing a track of what needs to be committed.
00:15:23.080 Now, back to the patterns we're discussing.
00:15:39.613 One of the most important practices for working with engines is maintaining clean separation between different functionalities.
00:15:46.239 For instance, if two projects define the same methods but with different meanings, each project gets their method... this is just standard Ruby behavior.
00:15:55.560 On top of that, it's essential to write engines that allow for extensibility. It's essential to integrate parts with minimal coupling for maximum effectiveness.
00:16:14.560 Now that we’re familiar with the benefits, let’s explore some Rails engine patterns.
00:16:25.210 The goals behind creating these patterns were to keep the engine code agnostic and avoid bidirectional coupling.
00:16:41.650 It’s also focused on maintaining a linear structure for reasoning about the code, where each app’s logic remains specific to that app.
00:16:56.170 The first pattern is the common domain pattern, which suggests extracting shared domain logic into its own modular component.
00:17:08.180 Each engine can maintain common models and view elements without duplicating them in every application.
00:17:20.350 For example, you might implement common CRUD operations for an address model for various applications without redundant definitions.
00:17:36.610 The second pattern is exposed helpers, which provide presentation logic. Should specific logic need customization in certain apps, defining the logic shared in a helper in the engine is ideal.
00:17:55.840 This enables you to avoid duplication while allowing customization for different cases.
00:18:07.390 Next is the expose partial pattern, which allows different applications to customize parts of a common view without having to replicate entire views.
00:18:22.660 This reduces redundancy because you only redefine a part and leave the rest untouched.
00:18:36.470 Then, we have the extension points pattern. This is essential for multiple apps needing to contribute different pieces of data or UI elements to the same view.
00:18:46.920 With this pattern, apps define extension points rather than reimplementing all code, allowing for simpler extensibility.
00:18:59.950 Lastly, configurable features provide a level of customizability where different apps can toggle feature availability depending on what they specifically need.
00:19:16.750 This allows each application to customize the engine to their unique requirements without affecting others.
00:19:35.240 In summary, while there are many benefits to engines, it's essential to weigh the trade-offs.
00:19:51.270 The primary benefits include code reuse across all application layers, better maintainability with cleanly defined boundaries, and improved productivity.
00:20:05.440 However, there are costs associated with adopting engines, such as the overhead in establishing a Rails engine and the need for careful management of gem versions and dependencies as you develop.
00:20:18.240 Engines are often compared to services. Engines are more beneficial for small MVC features and maintain reusable logic across applications.
00:20:32.950 They are less costly and can be built incrementally, compared to extracting features as services which can involve greater infrastructural work.
00:20:49.780 This flexibility and productivity that comes with engines helps bridge gaps between monolithic applications and larger services.
00:21:03.180 So remember: start simple with engines, scale up as needed.
00:21:13.040 Thank you for your time, everyone. If you have any questions, I’d be happy to answer them!