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 after) application routes and all other Engines that are present?
Here's an example of my Rails app routes:
match '*(path)', :to => 'foo_controller#bar_action'
And my Engine:
match '/news', :to => 'bar_controller#foo_action'
So by default Engines routes will be loaded after the application ones. This means that Engine routes are inaccessible due to that catch-all route in my app. How can force Engine routes to be loaded first (or last)?
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 =~ /<regex that matches path to my engine>/}.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).
Having the same issue right now. One other not particularly elegant but safe solution I came up with is adding another engine gem at the end of your gem file, just containing the catch all route, nothing else.
Edit: Actually, counterintuitively, the routing gem needs to be listed before all other engine gems for its routes to be loaded last.
You can write a method in your engine that can be called to configure your engine's routes:
module MyEngine
class Engine < Rails::Engine
def self.configure_routes(mapper)
mapper.match '/news', :to => 'news_controller#index'
end
end
end
This allows you to insert your engine's routes before a catch-all in my_application/config/routes.rb:
Rails.application.routes.draw do
# routes
MyEngine::Engine.configure_routes(self)
# more routes
# catch-all route
end
Then in the engine's my_engine/config/routes.rb file:
unless Rails.application.routes.named_routes.key?(:news)
Rails.application.routes.draw do
MyEngine::Engine.configure_routes self
end
end
If you're mounting the engine and don't include the explicit route configuration method, the engine routes will be added as usual to the end of the application routes.
I'm not an expert, but I was playing with sferik/rails_admin yesterday when I came across their routing solution. With their rails_admin:install rake task, they directly modify your config/routes.rb file by adding mount RailsAdmin::Engine => '/admin', :as => 'rails_admin' at the beginning of the file so they are sure their routes always have the highest priority. That mount method refers to their own routes.rb :
RailsAdmin::Engine.routes.draw do
# Prefix route urls with "admin" and route names with "rails_admin_"
scope "history", :as => "history" do
controller "history" do
[...]
end
end
end
Could this be your solution?
Related
I'm building an application that has a mountable but not isolated rails engine located at APP/sites/my_engine/. My understanding of a typical request with this setup looks like this:
A request goes through the Main App's middleware stack.
The request hit's the Main App's router, and matches the mounted path. It places the mounted part in: env["SCRIPT_NAME"] and sends the request on through the Engine.
The request goes through the Engine's middleware stack.
The request hit's the Engine's router and matches the remaining portion of the route.
Let's say that the route matches in the engine's routes and dispatches to a pages#show action. A controller will be called with the following priority:
If a PagesController exists with the show action in the main app it will call this first.
If a PagesController exists with the show action in the engine it will call this second only if the first did not exist.
If you look at MyEngine::Engine.paths['app/controllers'] it is the default ["app/controllers"]..which in this context is the engine. So why does it find the App's controller first?
Where is this priority of paths for controllers controlled?
Ok so this took some time to figure out. What happens when you boot rails in production mode is that towards the end of initialization rails runs (see Initialization Order):
Rails::Engine#eager_load!
Here Rails grabs all the eager_load_paths, through:
config.eager_load_paths
for example:
[
APP/controllers/pages_controller.rb,
APP/sites/my_engine/controllers/pages_controller.rb
]
For each eager_load_path it calls a method
ActiveSupport::Dependencies#require_dependency.
In require_dependency, rails calls:
Dependencies#depend_on
which strips each path to just the ending (i.e. "pages_controller") and calls:
Dependencies#search_for_file
This adds back on the .rb so you have "pages_controller.rb" and looks for the file in autoload_paths. Since the Rails paths are first in autoload_paths, Rails will find the App version of the file before it finds the engine version and loads it every time if they share the same name. The rails engine version never gets loaded.
I have a routing like that:
namespace :folio do
resources :portfolios do
resources :portfolio_items do
resources :images
end
end
end
Now please donĀ“t flame me because of the deep stacking. This is a mongo db persisted tree like object and those levels are all persisted in the root object.
What puzzles me is the fact that the generated routings read something like
folio_portfolio_portfolio_item
But when I ask for a url from urlhelper
url_for [#portfolio, #portfolio_item]
I get a nice exception telling me
undefined method `hash_for_folio_portfolio_folio_portfolio_item_path' for #<Module:0x0000000492fc30>
See the second "folio" in there? Any idea how I can get rid of that? Providing an :url => is not an option, unfortunately, because that would triplicate my form views and before that I'd rather ditch the namespace altogether. But unwillingly so: this is a rails engine and I would want to avoid clashes.
So, in other words...
I want
= form_for [#portfolio, #portfolio_item] do |form|
to "just" work :). Is this too much to ask?
Observation
a routing like that brings me a bit forward:
resources :folio_portfolio_items, :controller=>Folio::PortfolioItemsController do
while ugly as hell it generates good urls. Problem is, when I want to to visit one of them I get
ActionController::RoutingError (uninitialized constant Folio::Folio):
Whatever this means...
As a sidenote I think it is very odd that this happens at all. I think my mapping controllers to domains is the expected one...
I created a sample app that illustrates this on https://github.com/janlimpens/testroutes
I am trying to route a user to a custom welcome page after they confirm their account via devise's confirmable. The custom page is located at /districts/setup/, reachable by districts_setup_path.
To do this I added the custom route,
devise_for :users, :controllers => { :registrations => 'registrations', :confirmations => 'confirmations' }
and created my own controller. Then I had to overload the confirmations_controller.rb and now have:
(app/controllers/confirmations_controller.rb)
class ConfirmationsController | Devise::ConfirmationsController
# GET /resource/confirmation?confirmation_token=abcdef
def show
self.resource = resource_class.confirm_by_token(params[:confirmation_token])
if resource.errors.empty?
set_flash_message(:notice, :confirmed) if is_navigational_format?
sign_in(resource_name, resource)
redirect_to districts_setup_path
else
render_with_scope :new
# not:
# respond_with_navigational(resource.errors, :status => :unprocessable_entity){
end
end
end
This works well, but I am nervous that I am not doing this in the mostideal and robust way. In particular, I just deleted the respond_with_navigational( ... ) lines which I really don't understand.
I am hoping to write this all up in a how-to for the Devise wiki, and am just looking for feedback being fairly new to rails and even newer to Devise/engines/warden.
Taking a look on Devise's ConfirmationsController you can spot the protected method after_confirmation_path_for(resource_name, resource). Overriding it (rather than the whole action) will produce the same results with less effort.
Generally speaking, there is no problem with overriding Devise's controller since they represent the default behavior which doesn't always suit the application needs. That being said you must take a few things before overriding Devise's code:
Devise is not just another component of the system - it handles user authentication, which is a very sensitive issue. Make sure you don't break anything important before you commit it. You can do so by forking the Devise project from github, making your changes, and running the tests.
As you make changes to devise and override its code, it will become harder to upgrade to newer version, which might be incompatible with your changes.
If you do decide to make a change, look for the smallest change possible to achieve your goal. In most of the cases Devise's team has already foreseen the need for customization in certain places and left methods (like the one above) which are dedicated just for it. Again, going over the file's code on Devise's GitHub would give you a good idea as for what is the best way to customize its behavior to your needs.
I am using the following gem:
https://github.com/kjvarga/sitemap_generator
I have a posts model and have used post_path(post) in other parts of the application as it is based on Enki.
However in the sitemap file:
Post.all.each do |post|
sitemap.add post_path(post), :lastmod => post.updated_at
end
This returns the error when running the rake task rake sitemap:refresh:
rake aborted!
undefined method `post_path' for #<SitemapGenerator::Interpreter:0x279efd0>
And:
Post.all.each do |post|
sitemap.add posts_path(post), :lastmod => post.updated_at
end
Returns no errors. Can anyone shed any light on this or do I need to provide more of the code?
sitemap_generator actually includes all the helper methods in the create block, so you should be able to access posts_path.
I had a similar problem, and my answer also made me feel stupid. I was storing sitemap.rb in config/initializers, when it should be stored in the config/ directory. That meant it was running on startup and failing because the url helpers weren't properly loaded (and, incidentally, when I called rake sitemap:refresh, the sitemap was being generated twice - once in the initializer and again as the rake task!)
I feel a bit stupid on this one, it was simply because post_path was a helper and not accessible, so I simply had to move it into the method directly.
I am attempting to secure a Rails3 controller using declarative_authorization.
The controller has the 7, RESTful actions, three custom member actions (activate, deactivate, copy), and one custom collection action (public). The 'public' action only returns one record, however.
Only the custom collection action (public) should be available to authenticated users; the remainder are only available to the current_user.
has_permission_on :foos, :to => :public
has_permission_on :foos, :to => [:full_control, :copy, :activate, :deactivate] do
if_attribute :user => is {user}
end
privilege :full_control, :includes => [:index, :show, :new, :create, :edit, :update, :destroy]
The 4 custom actions are defined in the routes.rb file:
resources :users do
resources :foos do
collection do
get :public
end
member do
post :activate, :copy, :deactivate
end
end
end
A User :has_many Foos; A Foo :belongs_to a User.
The 'standard' access control (filter_resource_access :nested_in => :user), as defined in the FoosController seems to control access to the 7, RESTful actions, but fails to control access to the other 4 (as expected).
When I change the FooController to:
filter_access_to :all, :nested_in => :users, :attribute_check => true
I get an error that reads "Couldn't find Foo without an ID".
Questions:
The documentation seems to suggest that a :before_filter will be called automatically to load the Foo model when filter_access_to is used. Am I mistaken? Do I need additional configuration of the filter_access_to? Do I need to manually configure a :before_filter?
Do I also need to add using_access_control to the model for my purposes? I'm a little unclear when one needs to add access control to the model when there is access control in the controller.
The documentation describes a privilege named 'create'--it is defined as: privilege :create, :includes => :new. In addition, to the :new action, does this privilege automatically include the :create action as a consequence of its name?
If the authentication_rules.rb file is changed, does the server need to be restarted for the new rules to be applied?
I think that automatic before filter, if it exists, is pretty limited. I've always had to write my own before filters as appropriate to the context. Eg--for the the index view unless permission is the same for all instances of the model, you'll need to have a model instance appropriate the the current context. Like a user viewing an index of posts, you would want to make a dummy new post for the user, or load their first but dummy is safer since first might not exist. Generally I have a dummy constructor for index and everything else can test whatever is actually to be seen (or touched).
Controller has been good enough for me so far, so model level is certainly not REQUIRED. That's not to say it wouldn't add some extra safety, but I'm not expert in when exactly that would become important. I'd hypothesize it would be when you have a model touched by many controllers (eg modeless controllers) and you want to be sure nothing sneaks by.
I haven't used privileges so I'm not sure, but I would guess that magic inheritance you describe doesn't happen. Creating permissions that aren't specifically requested seems like it would be a very sloppy approach.
No, no restart required--at least not in development mode.