I have five controllers that share common code. Is it best to let them inherit from a parent controller, or use concerns? For example:
class PostsController < ApplicationController
before_action :authenticate, :set_project
layout 'projects'
end
class CommentsController < ApplicationController
before_action :authenticate, :set_project
layout 'projects'
end
# three other controllers, etc...
I could let the controllers inherit from one controller that declares the before_actions and the layout, or I could stuff the common code into a concern.
What's the criteria from one choice or the other? Is it defined?
My rule of thumb is:
If the controllers share the same namespace in the URL (for example /projects/... or /admin/...), than I use inheritance from an Projects::BaseController or Admin::BaseController.
If they just share methods or declarations and do not share a namespace, than I use mixins.
And sometimes I prefer duplicated code. Because code in place is easier to understand than a mixin with a meaningless name. Do you have a good name for a concern that covers authentication and layout?
In most cases I use mixins for adding features and inheritance for customizing features.
e.g.:
If I need to override current_user I choose inheritance. If I got only some shared methods, then I choose mixins.
Related
Lets say that we have a rails app which has a sidebar on every page that shows some data, like post archives, post categories etc. Which is the best way to share the same data on each of our controllers?
Iguess the most easy fix is to use the same before_actions on each controller, but this doesn't DRY up much our code, or maybe move all these into a parent class/cotrnoller that all controllers will inhert from, but is there a better way of doing this?
Simple Solution:
Normally, I just put these into application_controller.rb as a before_action.
Example
# app/controllers/application_controller.rb
before_action :set_sidebar_resources
# ...
private
def set_sidebar_resources
#sidebar_archives = Archive.all
#sidebar_categories = Category.all
end
Modular Solution:
Simple solution above works great until you define more and more methods and other global controller logic into ApplicationController, and then the file becomes too big to manage. The following is a less conventional approach favouring more of manageability rather than simplicity.
Example
# app/controllers/application_controller.rb
include WithSidebar
# app/controllers/concerns/with_sidebar.rb
module WithSidebar
extend ActiveModel::Concern
included do
before_action :set_sidebar_resources
private
def set_sidebar_resources
#sidebar_archives = Archive.all
#sidebar_categories = Category.all
end
end
end
Rails controllers inherit from application controller.
ex.
class ClientsController < ApplicationController
You could add before_actoion to ApplicationController that pulls the data you want into #variable and then use that variable in other controllers.
Just don't over-do it and keep your controllers skinny :)
Current I have three controllers that uses the exact same authorize_journey method (located in the controllers). Each controller also calls the exact same before_filter :authorize_journey. What is the best way to reduce this kind of redundancy through best-practice?
Also, how can I, if possible, stick to the fat-model-skinny-controller practice?
If the authorize_journey methods are all identical, then you can move a copy to '~/app/controllers/application_controller.rb' and remove it from all the individual controllers.
The before_filters can all remain as they are.
Of course, if the methods are not identical, you may require some refactoring to generalize it further. If you post the code, we can comment further.
You can always use it in the application_controller.rb.
Move the authorize_journey method to application_controller.
Say if you have 4 controllers and you need the before_filter in 3 controller. Then you can define the before_filter :authorize_journey in the the application_controller and use:
skip_before_filter :authorize_journey
in the 4th controller where you don't want the before_filter
I would suggest adding the method to a concern and include them in the controllers that requires the authorize_journey before_filter.
Code for concern will be:
# controllers/concerns/authorize_journery.rb
module AuthorizeJourney
extend ActiveSupport::Concern
included do
before_filter :authorize_journey
end
protected
def authorize_journey
# Code goes here
end
end
Now in the controllers, include the AuthorizeJourney module.
# controllers/examples_controller.rb
class ExamplesController < ApplicationController
include AuthorizeJourney
# Code goes here
end
I'm writing a rails application, and in this one controller I need to specify the layout to render due to inheriting from ActionController::Base. In some of my other controllers I just have ActionController and it automatically uses the application layout. Whenever I remove the ::Base, I get the following message when accessing the page superclass must be a Class (Module given).
Why does inheriting from ActionController::Base in this controller matter, but not in the others?
To directly answer your question ActionController is not a controller class, it's a namespacing module that powers the entire controller stack. You would not interact with the ActionController module during typical Rails development. ActionController::Base is actually the class that controllers inherit from. This is why you can't inherit from ActionController.
But I think there are two controllers in play here. ActionController::Base and ApplicationController. I think you might be mistaking ApplicationController for being ActionController without the ::Base.
ActionController::Base is the main controller class where all of your Rails functionality comes from. ApplicationController is a generalized controller that you can add methods to and have all of your other Rails controllers inherit from.
You could do the following to use a different layout in one of your controllers:
class AuthenticationController < ApplicationController
layout 'authentication'
end
You can either use the AuthenticationController directly, or have new controllers inherit from AuthenticationController.
Your controllers should inherit from ApplicationController. This will allow them to automatically render the application layout. ApplicationController extends ActionController::Base.
There are many questions about accessing Rails helpers from a model, all of which rightfully state that the answer is not to do that. This is not that question.
I have some fairly complex controller and view code that I want to abstract into a presenter class. But that class isn't a descendant of ApplicationController. How can I get access to, say, devise's current_user?
It appears there's no "official right" way to do this at the moment. Two possibilities:
It's hacky, but you can store the current controller in ApplicationController, and reference it in your presenters to get at the helpers:
class ApplicationController < ActionController::Base
prepend_before_filter { ##current_controller = self }
end
class YourPresenter
def current_user
ApplicationController.current_controller.current_user
end
end
Jeff Casimir is working on a great Decorator/Presenter gem called draper that encapsulates the whole idea:
https://github.com/jcasimir/draper
I've created a number of controllers & views under an 'admin' namespace, but they are still pulling from the application layout. how can I make a layout that applies to all the views in the namespaced routes, and still use the current layout for the other pages?
I usually have a Base controller class in my namespace, and then have all controllers in that namespace inherit from it. That allows me to put common, namespace specific code in Base and all the controllers in that namespace can take advantage. For example:
class Admin::BaseController < ApplicationController
layout 'admin'
before_filter :require_admin_user
end
class Admin::WidgetsController < Admin::BaseController
# inherits the 'admin' layout and requires an admin user
end
Generally speaking, Rails will use the application layout if there isn't a layout that matches the controller. For example, if you had a PeopleController, Rails would look for layouts/people.html.erb and if it didn't find that, application.html.erb.
You can explicitly specify a specific layout if you want to override this convention.
class Admin::PeopleController
layout 'some_layout'
end
That controller will then use some_layout.html.erb rather than looking for people.html.erb and application.html.erb.
But this might be a better way if you're looking to group things: If you have a base AdminController that inherits from ApplicationController, you can have your, say, Admin::PersonController inherit from the AdminController and it will inherit the admin layout.
I don't know the specifics of your code, but you might have:
class AdminController
def show
#render a template linking to all the admin stuff
end
end
app/controllers/admin/people_controller.rb:
class Admin::PeopleController < AdminController
#your awesome restful actions in here!
end
views/layouts/admin.html.erb:
Hello from the Admin!
<%= yield %>
The one thing to realize is that Admin::PeopleController will inherit any actions that AdminController has defined (just as anything defined in ApplicationController becomes available in all sub-classes). This isn't generally a problem since you'll likely be overwriting the methods anyway, but just to be aware of it. If you don't have an AdminController, you can make one with no actions just for the purposes of the layout.