Using a Model Provided by the Application inside Rails Engine

Rail engine

Sometimes when creating a Engine in Rails, we would like to associate a Model inside the engine with A model in our Main Application.

Engine mounted inside a Rails Application

When an engine is created, it may want to use specific classes from an application to provide links between the pieces of the engine and the pieces of the application.

In the case of the engine in the image above, making articles have authors would make a lot of sense.

A typical application might have a User class that would be used to represent authors for an article .

But there could be a case where the application calls this class something different, such as Person. For this reason, the engine should not hardcode associations specifically for a Userclass.

First Thing we need inside the Engine is a field in the form to get Attributes of Article’s author.

<div class="field">
<%= f.label :author_name %><br>
<%= f.text_field :author_name %>
</div>

Next, we need to update our EngineName::ArticleController#article_params method to permit the new form parameter:

def article_params
params.require(:article).permit(:title, :text, :author_name)
end

The Engine::Article model should then have some code to convert the author_name field into an actual User object and associate it as that article's author before the article is saved. It will also need to have an attr_accessor set up for this field, so that the setter and getter methods are defined for it.

So we make the necesaary changes in our Articles model models/engine_name/article.rb

attr_accessor :author_name
belongs_to :author, class_name: EngineName.author_class.to_s
before_validation :set_author
private
def set_author
self.author = EngineName.author_class.find_or_create_by(name: author_name)
end

The method set_author is used to find or create a new entry by name in the User’s table and reference it with the article being created.

By representing the author association's object with the User class, a link is established between the engine and the application. There needs to be a way of associating the records in the engine_name_articles table with the records in the users table. Because the association is called author, there should be an author_id column added to the engine_name_articles table.

To generate this new column, run this command within the engine:

$ bin/rails g migration add_author_id_to_engine_name_articles author_id:integer

To make this author customizable, the engine will have a configuration setting called author_class that will be used to specify which class represents users inside the application.

To define this configuration setting, you should use a mattr_accessor inside the engine. Add this line to lib/engine_name.rb inside the engine:

mattr_accessor :author_class

This provides a setter and getter method on the module with the specified name. To use it, it must be referenced using EngineName.author_class.

We need to add this code below mattr-accessor inside lib/engine_name.rb

def self.author_class
@@author_class.constantize
end

constantize tries to find a declared constant with the name specified in the string.

Because Rails controllers generally share code for things like authentication and accessing session variables, they inherit from ApplicationController by default. Rails engines, however are scoped to run independently from the main application, so each engine gets a scoped ApplicationController. This namespace prevents code collisions, but often engine controllers need to access methods in the main application's ApplicationController. We would need access methods in the main application to make a link between Article and the User.

So changing app/controllers/engine_name/application_controller.rb to looks like:

module EngineName
class ApplicationController < ::ApplicationController
end
end

To set this configuration setting within the application, an initializer should be used. By using an initializer, the configuration will be set up before the application starts and calls the engine’s models, which may depend on this configuration setting existing.

Create a new initializer at config/initializers/engine_name.rb inside the application where the engine is mounted and put this content in it:

EngineName.author_class = "User"

User could be the model inside your application which you want to link with articles.

Now inside you engine you can access your user like this

<p>
<b>Author:</b>
<%=
@article.author.name %>
</p>

But what if we wanted to do something like this in our Application

<p>
<strong>Projects:</strong><br>
<% @user.project.each do |u| %>
<%= u.title %><br>
<%= u.description %><br>
<% end %>
</p>

For this we’ll have to add a has_many association in our Users Model in Main Application. app/models/user.rb

has_many :project, :class_name => EngineName::Article , :foreign_key => :author_id