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!