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
Related
I have a basic model called Page and have many STI models based on Page such as
Drawing
Article
Story
etc...
I have separate controller and view for each of these STI models because I needed to customized the view layer based on the model type and have different logic for controller and hence separate controllers. However, I need the author field of all models to be set to current user. How do I do this in a single place?
For example, if I use before_action on Page controller and set the author, it affects the #page instance variable whereas my DrawingsController is using #drawing so it wont save the author of my #drawing unless I repeat the same code in DrawingsController.
Edit:
My controller hierarchy is
DrawingsController < PagesController
PagesController < ApplicationController
Both PagesController and DrawingsController have all the 7 restful actions. However, the actions on PagesController doesn't serve any purpose as I dont want any of my users to create Pages. I only want them to create the inherited STI classes like Drawings.
you could do this using some convention and meta programming in your controller hierarchy:
def add_author
model = instance_variable_get(:"##{controller_name.singularize}")
model.author = current_user
end
I'll be honest, I make no guarantees about the "best-practice"-ness of this answer, but I'll propose it anyways in case it helps to some degree. Also note that after rethinking the problem, I realized my first suggested solution in the comments was wrong, and the second is also not quite right either. So I'm posting a modified version of the second suggestion only:
Short answer: Let the PagesController handle most of the work, and delegate only to the subcontroller for model-specific things if needed. As phoet said, you can use a bit of meta programming (in a different way) to accomplish this.
class PagesController < ApplicationController
# pages controller stuff here
def create
#page = controller_name.classify.constantize.new(params[:page_params]) # I love Rails, don't you?
#page.author = current_user
handle_additional_create_actions
# For completeness of this example...
if #page.save
# render / redirect on success
else
# render errors
end
end
protected
# This method should be overwritten by sub controllers if needed
# Also, name this whatever you like, this is merely a verbose example for illustration purposes
def handle_additional_create_actions
# in the pages controller, this method does nothing
end
end
And, if there are additional things that need to be done by the model-specific controller:
class DrawingsController < PagesController
# drawing controller stuff here
protected
def handle_additional_create_actions
#page.some_other_field = some_other_data
end
end
A quick note: Note that in my suggestion, you're eliminating the model-specific variable names, meaning we don't have an #drawing and an #article, etc, anymore. Your models are all, essentially, types of Page objects, and so we're going to call it by its general name as a convention. That way, when you ask the DrawingsController to do something specific for the Drawing class, it knows that instance can be accessed via our generically named #page object.
So ultimately, the PagesController does the heavy lifting, regardless of which concrete model type you're dealing with. That way, only general page stuff is found in the pages controller, and drawing, article or story-specific stuff is found in their respective concrete controllers.
I'm doing a bit of metaprogramming in Ruby. I'm writing a library to meta-define some methods for me, specifically in the controller (automate some find_by methods that I have to write for my applications).
Currently I generate these methods by having to pass the name of the model for a particular controller into my meta-programming method. Is there a method in a controller that is tied to an ActiveRecord model.
So, here is a poor example
module AwesomeGem
module ClassMethods
def write_some_methods_for(model)
raise "Class #{model.class} does not inherit ActiveRecord::Base" unless model < ActiveRecord::Base
define_method "money_remaining" do |id=nil|
moolah = id ? model.find(id).money : model.find(params[:id]).money
render text: moolah
end
define_method "money_remaining_poller" do |id=nil|
obj = id ? model.find(id) : model.find(params[:id])
# composes some ajax
render js: moneyjs
moneyjs
end
end
end
end
So, to use this method, I plan to
GamblerController < ApplicationController
write_some_methods_for Gambler
end
Again, how could I make it so I don't have to pass the Gambler class to my method? Is there some sort of method or attribute that I could just call the model directly? eg. self.send(:model)
A simple question with a complex explanation.
Controllers are not tied to a particular model by default. You can have a controller playing with several different models, or even a controller using no model at all.
If you still want your code to work automatically in "classic" cases, you could look at the controller's name, and look for a model with the same name (following rails naming conventions).
I have an app that has users whose profiles are accessible via site.com/username. When choosing a username, I make an AJAX call to a method in my UsersController to make sure the username is available (and check on the back end as well when submitted). I now want to add groups that will also be accessible through site.com/groupname. Since group and user names cannot collide, whatever controller method that responds to the AJAX call will need to check both so the check_username_available and check_groupname_available methods will do the exact same thing. What's the best practice / Rails way to handle this since I don't want to replicate code in both UsersController and GroupsController?
Having a method for each controller seems a bit redundant, even if the functionality is pulled out to a helper, since there will still be two routes that do the same thing. Having a separate controller solves the problem too but not sure this is good Rails practice.
code that is reused can be shared via a module
class UsersController < ActionController::Base
include NameUniqueness
end
class GroupsController < ActionController::Base
include NameUniqueness
end
module NameUniqueness
protected
def check_name
# implementation here
end
end
both controllers will now have access the check_name instance method.
DanPickett's answer is great.
Another choice is to make a class method in the user model and just call it from each controller. Since this name checking seems like a job for the model, that's what I would do.
class User
def self.check(stuff) ...
As the application grows, I'm starting to use a Presenter Pattern similar to what's outlined here:
http://blog.jayfields.com/2007/03/rails-presenter-pattern.html
I would like the presenter to be able to access the same scope of the controller it's in, in a way that minimally impacts application performance, and that has a clear API. Something like this:
class UsersController < ApplicationController
def index
#view = UsersPresenter.new(self)
end
end
class Presenter
def initialize(controller)
end
end
class UsersPresenter < Presenter
end
I could use method_missing to access methods on the controller but that comes at a performance cost, and would probably be confusing to debug:
class Presenter
attr_reader :controller
def initialize(controller)
#controller = controller
end
def method_missing(method, *args, &block)
controller.send(method, *args, &block)
end
end
I would like it to work just like a module, but without being a module so I don't clutter the global controller namespace.
Any ideas what's best here? Maybe the delegate method?
Thanks for the tips.
An approach to work around method_missing: Enumerate all instance methods of the controller and define methods in the eigenclass of your presenter (in the initializer). However I doubt that it's better performance-wise when compared to method_missing.
Also this won't work for methods that were added to the controller via method_missing (like polymorphic routes).
I don't think that you can work around the method_missing completely if you want access to all the (missing) methods of the controller without explicitly writing controller.some_method in your presenter.
railstutorial.org has a suggestion which strikes me as a little odd.
It suggests this code:
class ApplicationController < ActionController::Base
protect_from_forgery
include SessionsHelper
end
The include SessionsHelper makes the methods available from ApplicationController, yes, but it makes them available in any view, as well. I understand that authentication/authorization is cross-cutting, but is this really the best place?
That seems to me to be potentially too broad of a scope. Putting code which implements, say, a before_filter which conditionally redirects (as the railstutorial.org example does) in a module which more commonly contains view helpers seems surprising.
Would functionality not strictly needed in views be better placed in ApplicationController or elsewhere?
Or am I just thinking too much about this?
Indeed, your feeling is correct.
I would implement this the other way around: add the functions sign_in and current_user to ApplicationController (or if you really want to: in a separate module defined in lib and include it), and then make sure that the current_user method is available in the view.
In short:
class ApplicationController
helper_method :current_user
def sign_in
end
def current_user
#current_user ||= user_from_remember_token
end
end
Of course, if you have a lot of code to place into your ApplicationController it can get messy. In that case I would create a file lib\session_management.rb:
module SessionManagement
def self.included(base)
base.helper_method :current_user
end
def sign_in
..
end
def current_user
..
end
end
and inside your controller you can then just write:
class ApplicationController
include SessionManagement
end
They seem to be taking (sneaky) advantage of the fact that, in Rails, Helpers are just ruby Modules.
Placing behavior that is shared across Controllers in a Module is, in my opinion, good practice. Putting it in a Helper, on the other hand, is potentially misleading and I would avoid it. Place it in a “standard” Module.
This is a philosophical question on the same level as the argument that questions the REST method provided in scaffolding and if A scaffold is worth having at all. You have to consider the fact that the tutorial book in RailsTutorial.org is a get-up-and-go Rails instructive guide. So for the purpose which it serves, I think it does the job.
However, is there a better place to put code needed across controllers and views? Yes, there is.
Some may follow Michael Hartl form Railstutorial and include the entire SessionHelper into the ApplicationController
Others may decide to expose only the essential helpers needed for the view i.e. sign_in, sign_out, current_user and the like.
I see a suggestion to put such code in the /lib directory and include it where needed.
All are viable options. Whichever you take may not matter that much in performance because Ruby would have to parse the file which you want to call (or include) a class, module or method from. What happens is, before any code is executed in a class, Ruby goes through the whole class once to know what's in it. It all depends on what one needs and the design of their app
FWIW, I store the current user in the User class:
class User < ActiveRecord::Base
cattr_accessor :current
...
end
This can be referenced in all 3 MVC tiers; it is set in the controller like so (and likewise on login, of course):
def set_current_user
User.current = (session[:user_id]) ? User.find_by_id(session[:user_id]) : nil
end
Among other things, this allows me to have audit logs at the ActiveRecord level that capture the current user (when applicable).