Skip to content

Instantly share code, notes, and snippets.

@mtanzi
Forked from maxivak/readme.md
Created February 1, 2021 22:36
Show Gist options
  • Save mtanzi/fab4ee08d54a4a051fa7ad640d968c3a to your computer and use it in GitHub Desktop.
Save mtanzi/fab4ee08d54a4a051fa7ad640d968c3a to your computer and use it in GitHub Desktop.
Integrating Gem/Engine and Main Rails App

Integrate Gem/Engine and main Rails app

Overview

## Routes Routes inside an engine are isolated from the application by default. The application and its engines can have routes with the same names.

Defined routes in engine:

# /myengine/config/routes.rb

Myengine::Engine.routes do
  resources :articles
  
end

Mount engine to the main app:

# config/routes.rb

Rails.application.routes.draw do
  # app routes
  
  #
  mount Myengine::Engine => "/myengine", :as => "myengine"

end

Engine's pages will have URL prefix 'myengine/'. For example, URL for articles page from Engine:

http://mysite.com/myengine/articles

Access engine's route from the main app view:

# use the name specified in mount in 'as':  mount Myengine::Engine => "/myenginepath", :as => "myengine"
= link_to 'Articles from Engine', myengine.articles_path


# this will try to find route defined in the main app, NOT from engine
= link_to 'Articles', articles_path

from any place in app (in controller, or in a class in lib/ )

Myengine::Engine.routes.url_helpers.articles_path

Access main app's routes in Engine

routes defined in app:

# config/routes.rb

Rails.application.routes.draw do
  resources :users

  # mount engine
  mount Myengine::Engine => "/", :as => "myengine"

end

Access app's routes from engine's view:

# myengine/app/views/somedir/someview.html.haml

= link_to 'Users', main_app.users_path

Merge engine and app routes

If you want the two sets of routes to be merged, you can use a non-isolated engine.

Removing the isolated_namespace in the engine definition:

# myengine/lib/myengine/engine.rb

module Myengine
  class Engine < Rails::Engine
    #isolate_namespace Myengine # remove this line
    ...
  end
end

Define routes in Engine with Rails.application.routes.draw, NOT Myengine::Engine.routes.draw:

# myengine/config/routes.rb

Rails.application.routes.draw do
  resources :articles
  
end

Remove mount in the main app:

App::Application.routes.draw do
  #mount Myengine::Engine => "/myengine" # remove this line
end

Now you can access routes from app and engine just using:

= link_to 'Articles', articles_path

Find more in discussion on stackoverflow.

## Improving (Extending or overriding) Engine functionality

A common task after including Engine in your Rails app is extending some classes (models, controllers, other classes) defined in the Engine.

It can be done using Decorator pattern.

There are two options of extending a class defined in Engine:

  • use Class@class_eval
  • use ActiveSupport::Concern

For simple class modifications, use Class#class_eval.

For complex class modifications, consider using ActiveSupport::Concern.

Read more in Rails guides.

use Class#class_eval to override class defined in Gem/Engine

# lib/myengine/engine.rb
module Myengine
  class Engine < ::Rails::Engine
    isolate_namespace Myengine
 
    config.to_prepare do
      Dir.glob(Rails.root + "app/decorators/**/*_decorator*.rb").each do |c|
        require_dependency(c)
      end
    end
  end
end

in the Engine:

# Blorgh/app/models/article.rb
 
class Article < ActiveRecord::Base
  has_many :comments
  
  def summary
    "#{title}"
  end
end

in the main app:

# MyApp/app/decorators/models/blorgh/article_decorator.rb
 
Myengine::Article.class_eval do
  # add new method
  def time_since_created
    Time.current - created_at
  end
  
  # override the method
  def summary
    "#{title} - #{truncate(text)}"
  end
  
end

Extend Engine class using ActiveSupport::Concern

We have a class defined in Engine:

# myengine/lib/myengine/mymodule/myclass.rb
module Myengine
  module Mymodule
    class Myclass
      include Myengine::Concerns::Mymodule::Myclass


    end
  end
end

Add concern to Engine:

# myengine/lib/concerns/mymodule/myclass.rb

module Myengine::Concerns::Mymodule::Myclass
  extend ActiveSupport::Concern

  included do

  end

  def my_object_method
    'it is engine'
  end
  
  module ClassMethods
    # will be overridden in the main app
    def my_class_method
      []
    end

    
  end
end

Use concern in the main app:

# myapp/lib/myengine/mymodule/myclass.rb

module Myengine
  module Mymodule
    class Myclass
      include Myengine::Concerns::Mymodule::Myclass

      # override class method
      def self.my_class_method
        ['1', '2', '3']
      end
      
      # override object method
      def my_object_method
        'this is app'
      end

    end
  end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment