What is RailsEngine?
A Rails engine is a self-contained piece of functionality that can be added to an existing Rails application, or used as the foundation for a new Rails application. Essentially, it's a miniature Rails application that can be packaged and reused across multiple projects.
Rails engines provide a modular way to organize and reuse code in a Rails application. They allow you to encapsulate related functionality (such as authentication, payments, or notifications) in a separate namespace, with its own models, views, controllers, routes, and assets.
An engine can include all the same features as a regular Rails application, including generators, migrations, tests, and configuration options. It can also have its own dependencies, such as gems or other engines.
Rails engines are typically developed as separate gems that can be installed and managed using Bundler. This makes it easy to share engines across multiple projects or with the wider community.
Rails engines are a powerful tool for building modular and reusable code in a Rails application, and can help developers to build more maintainable and scalable applications. They are used extensively in the Rails ecosystem, and many popular gems (such as Devise, ActiveAdmin, and Spree) are implemented as engines.
Here are the steps to create a Rails engine with error handling:
Open your terminal and navigate to the directory where you want to create your engine.
Run the following command to create a new Rails engine:
rails plugin new my_engine --mountable
This will create a new directory called my_engine with the basic structure of a Rails engine.
Open the my_engine.gemspec file and update the metadata to reflect the details of your engine, such as its name, version, and description.
Open the lib/my_engine/engine.rb file and add any required dependencies or configuration options.
Create a sample method in lib/my_engine/my_engine.rb that raises an error:
module MyEngine class MyEngine def self.my_method raise StandardError, "An error occurred in My Engine" end end end
Create a sample controller in app/controllers/my_engine/my_controller.rb
that calls the my_method
method:
module MyEngine class MyController < ApplicationController def index begin MyEngine.my_method rescue StandardError => e render plain: "Error: #{e.message}" end end end end
This controller defines an index action that calls the my_method method and handles any exceptions that are raised.
Create a sample view in app/views/my_engine/my/index.html.erb
:
<h1>Welcome to My Engine</h1> <p>This is a sample view in My Engine.</p>
Update the config/routes.rb file to define a route for your engine:
MyEngine::Engine.routes.draw do root to: "my#index" end
Finally, mount your engine into a Rails application by adding the following line to the config/routes.rb
file:
mount MyEngine::Engine, at: "/my_engine"
That's it! You've now created a simple Rails engine with error handling. When you navigate to the root path of your engine (/my_engine), you should see the error message that was raised by the my_method method.
To call an engine method in a Rails application, you can simply require the engine and call its methods as you would with any other Ruby class. For example, you could add the following code to a controller in your Rails application:
require "my_engine" class MyController < ApplicationController def index MyEngine::MyEngine.my_method end end
This code requires the my_engine
gem and calls the my_method
method on the MyEngine
class. Any errors that are raised by the method will be handled by the controller's exception handling code, as shown in the previous example.
Rails engines are a powerful tool for building modular and reusable code in a Rails application, and can help developers to build more maintainable and scalable applications. They can be used to encapsulate related functionality in a separate namespace, or to develop standalone applications that can be mounted into a larger Rails application. With error handling, you can ensure that your engine provides robust and reliable functionality to its users.
Here's an example of how to create a Rails engine with notification and payment functionality.
Open your terminal and navigate to the directory where you want to create your engine.
Run the following command to create a new Rails engine:
rails plugin new my_engine --mountable
This will create a new directory called my_engine with the basic structure of a Rails engine.
Open the my_engine.gemspec
file and update the metadata to reflect the details of your engine, such as its name, version, and description.
Open the lib/my_engine/engine.rb
file and add any required dependencies or configuration options.
Create a Notification model in app/models/my_engine/notification.rb:
module MyEngine class Notification < ApplicationRecord validates :message, presence: true enum status: { unread: 0, read: 1 } end end
This model defines a Notification
class with a message
attribute and a status
enum.
Create a Payment model in app/models/my_engine/payment.rb
:
module MyEngine class Payment < ApplicationRecord validates :amount, presence: true, numericality: true validates :currency, presence: true, inclusion: { in: %w[USD EUR GBP] } enum status: { pending: 0, paid: 1, failed: 2 } end end
This model defines a Payment class with an amount, currency, and status attribute.
Create a NotificationsController
in app/controllers/my_engine/notifications_controller.rb
:
module MyEngine class NotificationsController < ApplicationController def index @notifications = Notification.all end def mark_as_read @notification = Notification.find(params[:id]) @notification.update(status: :read) redirect_to my_engine_notifications_path end end end
This controller defines an index
action that retrieves all notifications and a mark_as_read
action that updates a notification's status to "read".
Create a PaymentsController
in app/controllers/my_engine/payments_controller.rb
:
module MyEngine class PaymentsController < ApplicationController def new @payment = Payment.new end def create @payment = Payment.new(payment_params) if @payment.save redirect_to my_engine_payments_path, notice: "Payment created successfully." else render :new end end private def payment_params params.require(:payment).permit(:amount, :currency) end end end
This controller defines a new action that creates a new payment and a create action that saves the payment to the database. It also includes a payment_params method to sanitize the payment parameters.
Create views for the NotificationsController
in app/views/my_engine/notifications/
:
index.html.erb
:
<h1>Notifications</h1> <table> <thead> <tr> <th>Message</th> <th>Status</th> <th>Actions</th> </tr> </thead> <tbody> <% @notifications.each do |notification| %> <tr> <td><%= notification.message %></td> <td><%= notification.status.capitalize %></td> <td><%= link_to "Mark as Read", my_engine_mark_as_read_notification_path(notification), method: :put %></td> </tr> <% end %> </tbody> </table>
mark_as_read.html.erb
<p>Notification has been marked as read.</p> <%= link_to "Back to Notifications", my_engine_notifications_path %>
Create views for the PaymentsController
in app/views/my_engine/payments/
:
new.html.erb
:
<h1>New Payment</h1> <%= form_with(model: @payment, url: my_engine_payments_path) do |f| %> <% if @payment.errors.any? %> <div id="error_explanation"> <h2><%= pluralize(@payment.errors.count, "error") %> prohibited this payment from being saved:</h2> <ul> <% @payment.errors.full_messages.each do |message| %> <li><%= message %></li> <% end %> </ul> </div> <% end %> <div> <%= f.label :amount %> <%= f.text_field :amount %> </div> <div> <%= f.label :currency %> <%= f.select :currency, options_for_select([["USD", "USD"], ["EUR", "EUR"], ["GBP", "GBP"]]) %> </div> <div> <%= f.submit "Create Payment" %> </div> <% end %>
index.html.erb
:
<h1>Payments</h1> <% flash.each do |type, message| %> <div class="<%= type %>"><%= message %></div> <% end %> <table> <thead> <tr> <th>Amount</th> <th>Currency</th> <th>Status</th> </tr> </thead> <tbody> <% @payments.each do |payment| %> <tr> <td><%= payment.amount %></td> <td><%= payment.currency %></td> <td><%= payment.status.capitalize %></td> </tr> <% end %> </tbody> </table> <%= link_to "New Payment", my_engine_new_payment_path %>
Finally, add routes for the NotificationsController
and PaymentsController
in config/routes.rb
:
Rails.application.routes.draw do mount MyEngine::Engine => "/my_engine" namespace :my_engine do resources :notifications do put :mark_as_read, on: :member end resources :payments, only: [:new, :create, :index] end end
This will mount the engine at the /my_engine
URL and define routes for the NotificationsController
and PaymentsController
.
With these steps, you now have a functional Rails engine with notification and payment functionality. To call these engine methods in a Rails app, you can mount the engine in the config/routes.rb
file of your app and use the generated engine routes to access the engine's controllers and models. For example:
Rails.application.routes.draw do mount MyEngine::Engine => "/my_engine" get "/notifications", to: "my_engine/notifications#index" post "/notifications/mark_as_read/:id", to: "my_engine/notifications#mark_as_read", as: :mark_as_read_notification get "/payments/new", to: "my_engine/payments#new" post "/payments", to: "my_engine/payments#create" get "/payments", to: "my_engine/payments#index" end
This will allow you to access the notifications and payments functionality provided by the engine in your Rails app by visiting the appropriate URLs. For example, you can create a link to the MyEngine
notifications page using the following code in one of your views:
<%= link_to "My Notifications", my_engine_notifications_path %>
This will create a link to the notifications index page provided by the engine, which you can style and modify as desired to fit your app's design.
To call the notification and payment functionality provided by the Rails engine in a Rails application, you can use the routes and helper methods generated by the engine.
Here's an example of how you could use these helper methods in a view to display a list of notifications and a link to the payment page:
<!-- app/views/my_app/index.html.erb --> <h1>Welcome to My App</h1> <h2>Notifications</h2> <ul> <% MyEngine::Notification.all.each do |notification| %> <li> <%= notification.message %> <% unless notification.read %> <%= link_to "Mark as read", mark_as_read_notification_path(notification) %> <% end %> </li> <% end %> </ul> <h2>Payments</h2> <%= link_to "Make a payment", my_engine_payments_path %>
In this example, we're using the MyEngine::Notification
model provided by the engine to display a list of notifications, along with a link to mark each notification as read. We're also using the mark_as_read_notification_path
helper method provided by the engine to generate the appropriate URL for marking a notification as read.
Finally, we're using the my_engine_payments_path
helper method provided by the engine to generate a link to the payment page. When the user clicks this link, they'll be taken to the payment page provided by the engine, where they can make a payment using the payment functionality provided by the engine.
Overall, integrating a Rails engine into a Rails application is similar to using any other external library or gem in a Rails app. By using the routes and helper methods provided by the engine, you can easily access the functionality provided by the engine and integrate it into your application as needed.
Top comments (4)
Excellent article. Thanks for writing this!
Thanks
Excellent article.
I needed an article like this with tests using RSpec, but it was already very useful.
Thanks @nemuba