Controlling routes loading order from Engines

前端 未结 3 394
轻奢々
轻奢々 2020-12-28 08:32

So in Rails3 Engines come with their own models/controllers/views and of course routes. Now the question is: How do you ensure that Engine routes will be loaded before (or a

3条回答
  •  时光说笑
    2020-12-28 08:50

    What you're trying to do is kinda difficult. As has been mentioned engine routes are loaded after the app routes and overriding this behaviour may be problematic. I can think of several things that you can try.

    Use An Initializer After Routing Paths Initializer

    There is an initializer in engine.rb inside the rails source, one way to accomplish what you're after is to try to hook into the functionality that it deals with. The initializer looks like this by default:

    initializer :add_routing_paths do |app|
      paths.config.routes.to_a.each do |route|
        app.routes_reloader.paths.unshift(route) if File.exists?(route)
      end
    end
    

    Essentially, this should take the paths to all the routes files that Rails knows about and try and add them to the routes reloader (the thing that reloades your routes file automagically for you if it is changed). You can define another initializer to be executed right after this one, you will then inspect the paths stored in the routes reloader, pull out the path that belongs to your engine, remove it from the paths array and insert it back, but at the end of the paths array. So, in your config/application.rb:

    class Application < Rails::Application
      initializer :munge_routing_paths, :after => :add_routing_paths do |app|
        engine_routes_path = app.routes_reloader.paths.select{|path| path =~ //}.first
        app.routes_reloader.paths.delete(engine_routes_path)
        app.routes_reloader.paths << engine_routes_path
      end
    end
    

    This may or may not work, either way I don't really recommend it, it's not particularly elegant (i.e. ugly hack playing with the guts of rails).

    Use Rails 3.1

    This may not be an option, but if it is, I'd probably go with this one. In Rails 3.1 you can have 2 different types of Engines, full and mountable (here is an SO question talking about some of the differences). But in essence you would alter your Engine to be a mountable engine, the routes in a mountable engine are namespaced and you can explicitly include them in the routes file of your main app e.g.:

    Rails.application.routes.draw do
      mount MyEngine::Engine => "/news"
    end
    

    You can also scope your mounted engine routes and do all sorts of other fancy routy things (more info here). Long story short, if you can go to 3.1 then this is the approach to use.

    Dynamically Insert The Routes From Your Engine Into Your Main App

    One of the most well known Rails engines around at the moment is Devise. Now, devise is an engine that will potentially add quite a number of routes to your app, but if you'll look at the devise source you'll see that it doesn't actually have a config/routes.rb file at all! This is because devise dynamically adds its routing goodness to your main app's routes.rb file.

    When you run the model generator that comes with devise, one of the things the generator will do is add a line such as devise_for :model at the top of your routes.rb file, right after the Rails.application.routes.draw do line. So your route.rb looks similar to this after you execute a generator to create a User model:

    Rails.application.routes.draw do
      devise_for :users
      ...
    end
    

    Now, devise_for is a magical method that comes as part of devise (in lib/devise/rails/routes.rb), but in essence it will create a bunch of regular routes that we all know based on the model that you generated.

    The thing we need to know, is how devise insert this line in the apps routes.rb file, then we can write a generator in our engine that will insert any of our routes at the top of the main apps routes.rb file. For this we look at lib/generators/devise/devise_generator.rb. In the add_devise_routes method the last line is route devise_route. Route is a Thor action which inserts the string passed to it into the main app's routes.rb file. So we can write a generator of our own and do something similar e.g.:

    class MyCrazyGenerator < Rails::Generators::NamedBase
      ...
      def add_my_crazy_routes
        my_route  = "match '/news', :to => 'bar_controller#foo_action'"
        route my_route
      end
    end
    

    Of course you would need to make sure all the generator infrastructure is in place but that's the essence of it. Devise is written by some very smart rails dudes and used by quite a lot of people, emulating what they do is likely a pretty good way to go. Out of the 3 things that I suggested this one would be the way I would handle your issue (given that moving to rails 3.1 is probably not an option).

提交回复
热议问题