0

I am currently working on a Rails engine that will duplicate the host app's routings under a certain scope. So if a route get '/posts', to: 'posts#index', as: 'posts' exists in the original application, this route should also be available under the engine mount get '/mount/posts', but should point to the same Contoller#Action in the host application. However, I don't have access to the final application, so the routes have to be generated dynamically.

The following is my current approach, but maybe it works better in another way?

# lib/embed_me.rb
require "embed_me/engine"
module EmbedMe
  # defines a scoped route under which the embedded content can be found
  mattr_accessor :scope_name, default: :embed
end


# lib/embed_me/engine.rb
module EmbedMe
  class Engine < ::Rails::Engine
    isolate_namespace EmbedMe

    initializer "embed_me", before: :load_config_initializers do |app|
      Rails.application.routes.append do
        mount EmbedMe::Engine, at: EmbedMe.scope_name
      end
    end
  end
end


# config/routes.rb
EmbedMe::Engine.routes.draw do
  match '/*path', to: 'application#index', via: :all
end


# app/controllers/embed_me/application_controller.rb
module EmbedMe
  class ApplicationController < ActionController::Base
    protect_from_forgery with: :exception

    def index
      # will validate existance of route, or raise ActionController::RoutingError
      path = Rails.application.routes.recognize_path(params[:path])
      controller = path[:controller]
      action = path[:action]

      # how to call original action?
    end
  end
end

I can't use redirect_to("/#{params[:path]}") because it would change the URL to the unscoped URL. I can't use render(action: action, controller: controller), because only the corresponding views will be rendered, but the logic in the controller will be skipped. (or I have done something fundamentally wrong?) I can't use main_app.resource_path because the routes are not static and vary from application to application.

I have also looked at Link and Link, but couldn't really get that to work either

tobiasbhn
  • 46
  • 4
  • 1
    This is not a viable path forward. Rails is not built to do internal "forwarding" from one controller to another and it would make debugging into true hell. I guess what you could possibly do is have your engine read the routes file just like rails does and create a modified routes collection and somehow insert that back into the host application. It would probally require a huge amount of work and digging into the internals. I would recommend that you start by reading https://medium.com/rubyinside/a-deep-dive-into-routing-and-controller-dispatch-in-rails-8bf58c2cf3b5. – max Dec 15 '20 at 00:16
  • Thank you. I will have a look at the link – tobiasbhn Dec 15 '20 at 00:48

1 Answers1

0

So I got it to work more or less. I didn't find a solution to this exact problem, so I made small trade-offs. Namely, I decided that the administrator of the host application has to make a small change in the routes. Basically, I now use a scope like in the following example.

scope path: "/#{EmbedMe.scope_name}", as: EmbedMe.scope_name, is_embedded: true

To make it as easy as possible for the user of the engine, I have integrated a custom route function, which handles the scoping of the routes.

# lib/embed_me/rails/routes.rb
module ActionDispatch::Routing
  class Mapper
    def embeddable
      # note that I call the yield function twice, see describtion below
      yield
      scope path: "/#{EmbedMe.scope_name}", as: EmbedMe.scope_name, is_embedded: true do
        yield
      end
    end
  end
end


# [Host Application]/config/routes.rb
Rails.application.routes.draw do
  # not embeddable
  get '/private', to: 'application#private'

  # is embeddable
  embeddable do
    get '/embeddable', to: 'application#embeddable'
  end
end


# output of $rails routes
...
private           GET   /private(.:format)            application#private
embeddable        GET   /embeddable(.:format)         application#embeddable
embed_embeddable  GET   /embed/embeddable(.:format)   application#embeddable {:is_embedded=>true}
...

Note that I call the yield function twice, once to get the routes normally, and once under the scope. This way I also get multiple path helpers for the normal and the embedded link.

Creating the custom route method is handled similarly to Devise. See Source

tobiasbhn
  • 46
  • 4