Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails enable specify middlewares for specify route just like Phoenix pipeline

In phoenix framework with pipeline we can enable specify middlewares for some route, for example:

defmodule HelloPhoenix.Router do
  use HelloPhoenix.Web, :router

  pipeline :browser do
    plug :accepts, ["html"]
    plug :fetch_session
    plug :fetch_flash
    plug :protect_from_forgery
    plug :put_secure_browser_headers
  end

  pipeline :api do
    plug :accepts, ["json"]
  end

  scope "/", HelloPhoenix do
    pipe_through :browser # Use the default browser stack

    get "/", PageController, :index
  end

  scope "/api", HelloPhoenix do
    pipe_through :api
  end
end

if request from /api, will only trigger plug :accepts, ["json"] middleware

if request from /, will trigger session, flash, ...etc middlewares

how to achieve this on rails, If I am using grape for build api and rails for build web page and enable difference middleware for each other?

like image 516
TangMonk Avatar asked Oct 20 '25 20:10

TangMonk


2 Answers

Unlike Phoenix applications, you cannot (easily) change what middleware is used for a request within Rails. The best way to get this kind of behaviour in a Rails application would be to make the controllers for a particular route inherit from a common base controller, and then define the behaviour for these particular controllers in that base controller.

Using the above example of the /api routes going through different "middleware", you can have a controller like this:

module API
  class BaseController < ActionController::API
    # things unique to API routes go here
  end
end

Inside this controller, you could write some before_action callbacks that ensure things like:

  • Inbound requests are JSON only
  • Requests are authenticated with a particular token

Then you can inherit from this controller for all API controllers:

module API
  class PostsController < API::BaseController
  end
end

With your regular controllers you can do all the regular things:

class ApplicationController < ActionController::Base
  # things unique to non-api routes go here
end

And then:

class PostsController < ApplicationController
end

You could of course segment it more; maybe you might have another route like /accounts/1/posts where you have to be authenticated as the user of that account to see the posts. You can take the same approach:

module Accounts
  class BaseController < ActionController::Base
     # find account, check if the user is authenticated, etc.
  end
end

And:

module Accounts
  class PostsController < Accounts::BaseController
  end
end

So in summary: Rails doesn't let you do this on a routing level (easily), but you can accomplish the same thing at the controller level.

like image 121
Ryan Bigg Avatar answered Oct 23 '25 11:10

Ryan Bigg


The solution I was looking for was something more in this direction:

application.rb:

# after Bundler.require(...)
require_relative '../lib/engines/website/lib/website'

lib/engines/website/lib/website.rb:

require_relative "website/engine"

module Website; end

lib/engines/website/lib/website/engine.rb:

module Website
  class Engine < ::Rails::Engine
    middleware.use ActionDispatch::Cookies
    middleware.use ActionDispatch::Session::CookieStore
    middleware.use ActionDispatch::Flash
  end
end

config/routes.rb:

mount Website::Engine => "/website"

And everything for the website goes in the typical directory structure under the engine directory:

lib
  engines
    website
      app
        assets
          ...
        controllers
          ...
        views
          ...
      config
        routes.rb
      lib
        website
        website.rb

Reference: Build 2 middleware stacks in Rails app

like image 41
Rafael Garcia Avatar answered Oct 23 '25 09:10

Rafael Garcia



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!