开发者

Where to put business logic that requires the current_user to be known? (Rails)

开发者 https://www.devze.com 2023-01-31 12:51 出处:网络
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.

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:

  1. Check that current_user owns this object
  2. Check that the object status == 1 (Active)
  3. 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.

0

精彩评论

暂无评论...
验证码 换一张
取 消