質問

I'm trying to implement gem devise to my app. Before that my top model was Album but now it belongs_to :user (from devise).

Then I added to the albums_controller.rb:

before_action :authenticate_user!

It works great - user has to log in from now on. And now I wish him to do everything with albums in his scope. I found that insted of that method:

def index
  @albums = Album.all
end

I could use:

@albums = current_user.albums

and so on for every method I have. I was wondering if there's a better way - to set current_user as default scope for every action/method in the albums controller. Then I found something interesting here. I could add it to Album's model but I'm not sure how best costruct where clause for the current_user. Maybe something like this:

class Album < ActiveRecord::Base
  default_scope where(:user_id => current_user.id)
end

I'm not even sure if it's right direction. I would appreciate your advice.

役に立ちましたか?

解決

I'm not sure why you want to do this at all. The best approach is to use the controller to scope your models. This type of thing doesn't belong to the model.

def index
  @albums = current_user.albums
end

If you want to avoid the repetition, create methods to retrieve the object. So instead of this:

def show
  @album = current_user.albums.find(params[:id])
end
def edit
  @album = current_user.albums.find(params[:id])
end
# etc...

You can do this:

def index
  albums
end
def show
  album
end
def update
  if album.update(album_params)
end
# etc...

private
def albums
  @albums ||= current_user.albums
end
def album
  @album ||= current_user.albums.find(params[:id)
end

You can even avoid calling the album method from the action by using a before_filter, but this is not a good way. You always tend to forget to add and remove actions from the filter.

before_action :set_album, only: [:show, :edit, :update, :destroy]
def set_album
  @album ||= current_user.albums.find(params[:id])
end

Then your instance variables are created in one place. As @wacaw suggested, if this appeals to you, you can take it further and use the decent_exposure gem. Personally, I am happy to stop at the controller and use instance methods in my views.

If you have more complex authorisation needs I suggest you use pundit or cancan, although the latter does not appear to be actively maintained.

There is more on decent_exposure on Rails Casts. If you really fancy this type of scoping, look at this Rails Cast on Multitenancy with Scopes. But that is meant for organisations that have many of users, not a single user.

他のヒント

You should just use a regular named scope instead:

scope :for_user, lambda{ |user| where(:user_id => user.id) }

and then in your controller replace

@albums = Album.all with @albums = Album.for_user(current_user)

While you can theoretically pass arguments to your default scope, the above is the preferred approach.

Use helper for creating declarative interfaces in controllers

https://github.com/voxdolo/decent_exposure

example for devise:

http://railscasts.com/episodes/259-decent-exposure?view=comments#comment_151102

You could use the inherited_resources gem to keep your controllers clean of boilerplate code. The following code is everything you need to build a standard CRUD-Controller that automatically scopes to the current_user and checks authentication.

class AlbumsController < InheritedResources::Base
  before_action :authenticate_user!

  protected

  def begin_of_association_chain
    current_user
  end
end
ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top