Where to put user access controls? The controller or the model? - ruby-on-rails

I understand that we're supposed to avoid putting logic in the controller. So what is the proper way to implement things like user access controls. Let's suppose I have User, where each instance has a flag method admin? that determines whether the user can access information from other users.
Option 1: Put access controls in custom model
Model:
class User < ActiveRecord::Base
def self.get_list(accessor)
return [] unless accessor.admin?
self.all
end
end
Controller:
class UsersController < ApplicationController
def index
#users = User.get_list(current_user)
end
end
current_user would probably be defined somewhere in the application controller.
Option 2: Put access controls in the controller
Model:
class User < ActiveRecord::Base
end
Controller:
class UsersController < ApplicationController
def index
#users = current_user.admin? User.all : []
end
end
There are also some peripheral consequences such as where tests go and how they're implemented.
My instinct is that the first of the two options is preferable, but I've only ever used option 2 in the past. Also, it seems like the generally accepted (as far as I can tell) practice of putting on action-wide access filters is done at the controller level as in:
class UsersController < ApplicationController
before_filter :verify_logged_in
end

Any logic controlling routing of your application belongs in your controller. As for your example, the first is preferable but really there's not much in it.
It's easy for opinionated frameworks make us almost obsessive about doing things in a perceived correct way. In your example such a tiny amount of logic would, in my opinion, be perfectly fine to leave in your controller. If you were to abstract it, give it a descriptive name that described better what the method is doing otherwise you're simply making your code needlessly difficult to read.

As a rails rookie, the second option to me seems far easier to read and comphrehend. I prefer how I can read the single line in the controller and see exactly what you are doing.
In the first example, this logic is hidden someplace else (obviously the model, but I'm a rookie remember!) and seems slightly verbose (for this particular example).
I don't mean to suggest keeping it easier for new kids is A Good Thing, just pointing out a preference.

Related

what is the best way to set author field on STI model

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.

Rails: How to POST internally to another controller action?

This is going to sound strange, but hear me out...I need to be able to make the equivalent of a POST request to one of my other controllers. The SimpleController is basically a simplified version of a more verbose controller. How can I do this appropriately?
class VerboseController < ApplicationController
def create
# lots of required params
end
end
class SimpleController < ApplicationController
def create
# prepare the params required for VerboseController.create
# now call the VerboseController.create with the new params
end
end
Maybe I am over-thinking this, but I don't know how to do this.
Inter-controller communication in a Rails app (or any web app following the same model-adapter-view pattern for that matter) is something you should actively avoid. When you are tempted to do so consider it a sign that you are fighting the patterns and framework your app is built on and that you are relying on logic has been implemented at the wrong layer of your application.
As #ismaelga suggested in a comment; both controllers should invoke some common component to handle this shared behavior and keep your controllers "skinny". In Rails that's often a method on a model object, especially for the sort of creation behavior you seem to be worried about in this case.
You shouldn't be doing this. Are you creating a model? Then having two class methods on the model would be much better. It also separates the code much better. Then you can use the methods not only in controllers but also background jobs (etc.) in the future.
For example if you're creating a Person:
class VerboseController < ApplicationController
def create
Person.verbose_create(params)
end
end
class SimpleController < ApplicationController
def create
Person.simple_create(params)
end
end
Then in the Person-model you could go like this:
class Person
def self.verbose_create(options)
# ... do the creating stuff here
end
def self.simple_create(options)
# Prepare the options as you were trying to do in the controller...
prepared_options = options.merge(some: "option")
# ... and pass them to the verbose_create method
verbose_create(prepared_options)
end
end
I hope this can help a little. :-)

Rails convention for method in multiple controllers

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) ...

Ruby on Rails - Access controller variable from model

I am trying to access an instance variable which is set in the controller in the model. The controller is the products controller and the model is the products model. The instance variable is a instance of another model called account.
The instance variable is #current_account
When I run the code nothing happens, I do not get an error. Does anyone know where I can find something read about access instance variables set in the controller from the model?
Thanks
Eef
You shouldn't generally try to access the controller from the model for high-minded issues I won't go into.
I solved a similar problem like so:
class Account < ActiveRecord::Base
cattr_accessor :current
end
class ApplicationController < ActionController::Base
before_filter :set_current_account
def set_current_account
# set #current_account from session data here
Account.current = #current_account
end
end
Then just access the current account with Account.current
DISCLAIMER: The following code breaks MVC conventions, that said...
Using class attributes can probably lead to thread safety issues. I would use Thread.current + around_filter to store controller related data at thread level, and ensure it gets cleared
just before the request finishes:
class ApplicationController < ActionController::Base
around_filter :wrap_with_hack
def wrap_with_hack
# We could do this (greener solution):
# http://coderrr.wordpress.com/2008/04/10/lets-stop-polluting-the-threadcurrent-hash/
# ... but for simplicity sake:
Thread.current[:controller] = self
begin
yield
ensure
# Prevent cross request access if thread is reused later
Thread.current[:controller] = nil
end
end
end
Now the current controller instance will be avaliable globaly during the request processing through Thread.current[:controller]
If you need to access a controller variable from a model it generally means your design is wrong because a controller serves as bridge between view and model (at least in Rails), controller gets info from models, models shouldn't know anything about controllers, but if you want to do it anyway you can do it just as jeem said, but I'd rather do:
class << self
attr_accessor :current
end
instead of cattr_accessor :current
you can see why here => cattr_accessor doesn't work as it should
I can't comment directly so I'll post here: the accepted answer does not seem to be right. As #vise notes, class variables are shared across requests. So unless there's just one current account for the entire app, this won't behave as expected.
For more, see the accepted answer by #molf here: Is Rails shared-nothing or can separate requests access the same runtime variables?
I'm not sure if I understand the question exactly, but I'll take a stab.
I think if you need to access a controller instance variable from the model then you either need to make it an attribute in the model, or move your logic to the other class controller, not model.

Tracking model changes in Rails, automatically

In my rails app I would like to track who changes my model and update a field on the model's table to reflect.
So, for example we have:
class Foo < ActiveRecord::Base
before_create :set_creator
belongs_to :creator, :class_name => "User"
protected
def set_creator
# no access to session[:user_id] here...
end
end
What's a good testable way for me to get at the user_id from my model? Should I be wacking this data in Thread.current ?
Is it a better practice to hand this information from the controller?
Best practice in MVC is to have your Models be stateless, the controller gets to handle state. If you want the information to get to your models, you need to pass it from the controller. Using a creation hook here isn't really the right way to go, because you are trying to add stateful data, and those hooks are really for stateless behavior.
You can pass the info in from the controller:
Foo.new(params[:foo].merge {:creator_id => current_user.id})
Or you can create methods on User to handle these operations:
class User
def create_foo(params)
Foo.new(params.merge! {:creator_id => self.id})
end
end
If you find yourself writing a lot of permissions code in the controller, I'd go with option 2, since it will let you refactor that code to the model. Otherwise option 1 is cleaner.
Omar points out that it's trickier to automate, but it can still be done. Here's one way, using the create_something instance method on user:
def method_missing(method_sym, *arguments, &block)
meth = method_sym.to_s
if meth[0..6] == "create_"
obj = meth[7..-1].classify.constantize.new(*arguments)
obj.creator_id = self.id
else
super
end
end
You could also override the constructor to require user_ids on construction, or create a method inside ApplicationController that wraps new.
There's probably a more elegant way to do things, but I definitely don't like trying to read state from inside Model code, it breaks MVC encapsulation. I much prefer to pass it in explicitly, one way or another.
Yeah, something like that would work, or having a class variable on your User model
cattr_accessor :current_user
Then in your controller you could have something like:
User.current_user = current_user
inside a before filter (assuming current_user is the logged in user).
You could then extend AR:Base's create/update methods to check for the existence of a created_by/updated_by field on models and set the value to User.current_user.
I'd create new save, update, etc methods that take the user_id from everything that calls them (mainly the controller).
I'd probably extend ActiveRecord:Base into a new class that handles this for all the models that need this behaviour.
I wouldn't trust Thread.current, seems a bit hackish. I would always call a custom method which takes an argument:
def create_with_creator(creator, attributes={})
r = new(attributes)
r.creator = creator
r.save
end
As it follows the MVC pattern. The obviously inherient problem with this is that you're going to be calling create_with_creator everywhere.
You might find PaperTrail useful.
Probably you could check out usertamp plugins, found two in github
http://github.com/delynn/userstamp/tree/master
http://github.com/jnunemaker/user_stamp/tree/master

Resources