My team uses engines because they add functionality in a modular way, and help us avoid the issues that come with bloated models (too many associations or methods) or deeply nested namespaces. In this post, I discuss some tips around engine routes.

Suppose we have a number of engines and we want to provide links to each engine’s root (mounted) route from our home or index page. We can either hardcode each route, or we can ask the engines themselves for this information. The latter was easier with Rails 3 engines (simply call MyEngine::Engine.class.mounted_path), but for Rails 4 engines, we need to add the following code to an initializer or to application.rb:

# Rails 4 mountable engines
class Rails::Engine
  def self.mounted_path
    route = Rails.application.routes.routes.routes.detect do |route|
      route.app.app == self
    end
    route && route.path.spec.to_s
  end
end

Once the mounted_path method is available, we can now easily distinguish between our mountable engines and other engines such as Coffee and Turbolinks:

ngins = Rails::Engine.subclasses
ngins.each {|ngin puts ngin.mounted_path if ngin.mounted_path}
# => /my_engine
# => /enginetoo
# etc ..

The output of the code above should match the mount path in routes.rb, such as mount myEngine::Engine, at: "/my_engine" from the Rails guide.

As mentioned previously, we can simply hardcode these values in the views, but it is not very DRY. On the other hand, the code above unnecessarily calls all engines, mountable or not. One improvement is to create a model that holds information about our mounted engines. For example, we can have a model called Ngin that has attributes including name and mounted_path. Then, in an initializer or application.rb, we create (or find) records for our mountable engines:

# Rails 4
ngins = Rails::Engine.subclasses
ngins.each do |ngin|

 if ngin.mounted_path
  engine = Ngin.find_or_create_by(name: ngin.name)
  engine.name = ngin.name #=> MyEngine::Engine
  engine.mounted_path = ngin.mounted_path #=> /my_engine
  # other attributes here
  engine.save
 end
 
end

Our views will use the Ngin model to output links and other information about a mountable engine:

<% @ngins.each do |ngin| %>
  <%= link_to ngin.name, ngin.mounted_path %>
<% end %>

The only loose end is the proper handling of engines that we have unmounted from the Rails app. We should remove the corresponding Ngin records, and one way to accomplish this is by using a boolean attribute, called ‘available’, that gets set to true if we have ‘seen’ the engine. We can set this attribute during our iteration of the Rails::Engine.subclasses objects, but we must set the ‘available’ attribute to false for all Ngin records beforehand:

ngins = Rails::Engine.subclasses
Ngin.update_all(available: false)

ngins.each do |ngin|   
 if ngin.mounted_path
  ...
  engine.available = true
  engine.save
 end
end

Don’t forget to add error handling on both the code above and on the views that use the Ngin model.

Bonus tip If we have a record from a Rails::Engine and we want a link to the show action of its controller, add the following code in a helper of the main app:

def get_path_for_record(record) 
  record.class.parent::Engine.routes.url_for(controller:\ 
  record.class.name.underscore.pluralize,\ 
  action: 'show', id: record, only_path: true)
  # only_path: true => use relative path
end