Introduction of Rails Engine with basic example.
Tags: ruby, rails, railsengine
Published: 2023-02-25 11:46:11

## 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:

```ruby
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:

```ruby
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`:

```ruby
<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:

```ruby
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:

```ruby
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:`

```ruby
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`:

```ruby
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`:

```ruby
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 i`ndex` 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`:

```ruby
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`:

```ruby
<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`

```ruby
<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`:

```ruby
<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`:

```ruby
<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`:

```ruby
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:

```ruby
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:

```ruby
<!-- 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.