DEV Community

Cover image for Introduction of Rails Engine with basic example.
Harsh patel
Harsh patel

Posted on

Introduction of Rails Engine with basic example.

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

Update the config/routes.rb file to define a route for your engine:

MyEngine::Engine.routes.draw do
  root to: "my#index"
end
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

mark_as_read.html.erb

<p>Notification has been marked as read.</p>
<%= link_to "Back to Notifications", my_engine_notifications_path %>
Enter fullscreen mode Exit fullscreen mode

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 %>
Enter fullscreen mode Exit fullscreen mode

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 %>
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 %>
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
nemuba profile image
Alef Ojeda de Oliveira

Excellent article.

I needed an article like this with tests using RSpec, but it was already very useful.

Collapse
 
harsh_u115 profile image
Harsh patel

Thanks @nemuba

Collapse
 
bunnahabhain profile image
David Lewis

Excellent article. Thanks for writing this!

Collapse
 
harsh_u115 profile image
Harsh patel

Thanks