Where to put business logic that requires the current_user to be known? (Rails)
I have a model (say Car
) in which a method needs access to the current_user to determine if the user is allowed to perform the things that the method does.
For example, a method might want to do these things:
- Check that current_user owns this object
- Check that the object status == 1 (Active)
- Check th开发者_如何转开发at a related object exists and it's X field is not NULL
I need this business logic to be in the model, not in the controller, so that it's the one place where my business logic will be. The method might get called from places other than a controller.
I know that there are gems like cancan, declarative_authorization etc. but they seem to be overkill for what I need to do. And also, accessing current_user in a model is not considered the "right way".
Then, how do I make that check in the model but still feel "clean"?
I have experienced a situation where "current_user" needs be tightly connected to a model, but I handled it all in the Controller and it works pretty well. Here are some examples:
My model is "Photos". Photos are owned by users, and how people interact with photos is obviously tightly related to whether or not they own the photo.
In the show action I need to load either the existing rating a user has given to a photo (so they can edit it) or allow them to create a new one:
def show
@photo = Photo.find(params[:id])
if user_signed_in?
if @rating = current_user.ratings.find_by_photo_id(params[:id])
@rating
@current_user_rating = @rating.value
else
@rating = current_user.ratings.new
@current_user_rating = "n/a"
end
end
end
When people create photos I want them to be automatically assigned to the current user.
def new
@photo = Photo.new
end
def create
@photo = current_user.photos.create(params[:photo])
if @photo.save
redirect_to user_path(current_user), :notice => t('photo.notice.created')
else
render 'new'
end
end
Only the owners of a photo can change them:
def edit
@photo = Photo.find(params[:id])
if @photo.user == current_user
render 'edit'
else
redirect_to user_path(current_user), :alert => t('application.error.unauthorized')
end
end
def update
@photo = current_user.photos.find_by_id(params[:id])
@photo.update_attributes(params[:photo])
if @photo.save
redirect_to user_path(current_user), :notice => t('photo.notice.updated')
else
render 'edit'
end
end
This approach is based on the constraints that a "current_user" object is tied to the session, which only the controller knows about. So, in short, I have yet to find a good way to integrate "current_user" into a model, but I've been able to find (I think) pretty clean ways to tie the model and controller together so that this can be provided by the controller.
One fairly simple solution to most problems, if your controller is starting to get messy, would be to take a chunk of logic and define as a method in the model, but require one argument = a user object. Then you can just feed "current_user" to that method from your controller and the model handles the rest.
Good luck! Also, if anyone else has any better ideas for this, I'd love to hear them!
Handle auth'ing in a Controller.
Example: Putting auth logic in parent ApplicationController.
class ApplicationController < ActionController::Base
protect_from_forgery
protected
# Returns the currently logged in user or nil if there isn't one
def current_user
return unless session[:user_id]
@current_user ||= User.find_by_id(session[:user_id])
end
# Make current_user available in templates as a helper
helper_method :current_user
# Filter method to enforce a login requirement
# Apply as a before_filter on any controller you want to protect
def authenticate
logged_in? ? true : access_denied
end
# Predicate method to test for a logged in user
def logged_in?
current_user.is_a? User
end
# Make logged_in? available in templates as a helper
helper_method :logged_in?
def access_denied
redirect_to login_path, :notice => "Please log in to continue" and return false
end
end
Now that current_user is an accessor to the logged in user and you can access it in any controller, you can do your authorization logic in the appropriate controller before you do anything with the model.
Your right, though. Models don't care about authorization or who is accessing them.
精彩评论