Привет.
Сегодня посмотрим как работать с devise и cancan. Важные gem'ы, да?
и так сперва задача:
- Авторизация\регистрация\аутентификация пользователя на сайте -- это задача devise
- Потом привилегии:
- админ может всё ;)
- пользователь может создавать посты
- автор постов (владелец) может его редактировать, но удалять не может.
Не очень сложно.
и так в Gemfile добавляем
gem 'devise'
gem 'cancan'
Ok, установили теперь devise:
rails g devise:installЭта команда создаст файл инициализации, нам нужно его отредактировать:
config/initializers/devise.rb
находим строчку
config.sign_out_via = :delete
меняем на :get.
Так хорошо. Теперь делаем модель пользователя:
rails g devise userСоздаёт модель. Ну и сразу делаем миграцию, что создаёт таблицу в БД:
rake db:migrateимпортируем views
rails g devise:viewsОк. Теперь делаем для постов, тут легче:
rails g scaffold Post title:string content:text user:references rake db:migrateВсё! Что хотели, то сделали: можно регистрироваться и все дела.
Теперь время добавить привилегии с cancan:
rails g cancan:abilityСоздаёт класс возможностей\привилегий в app/models.
Ну в принципе каркас написан, осталось добавить немного кода.
Но перед этим нужны же ещё Роли (Role). У пользователя может быть тысяча ролей, и модератор и просто_пользователь в общем придумайте сами.
У меня -- это будет admin\user и всё. и так как видно это отношение has_many:through
rails g model role name:string rails g model users_role user:references role:references
Всё теперь редактируем модель User, добавляем зависимости:
class User < ActiveRecord::Base has_many :users_roles has_many :roles, :through => :users_roles end
Модель Role
class Role < ActiveRecord::Base has_many :users_roles has_many :users, :through => :users_roles end
Всего лишь строчку в PostController:
load_and_authorize_resourceи всё.
Теперь с представлением:
Ну допустим раньше выводилось вот так:
#views/index.html.erb <% @posts.each do |post| %> <tr> <td><%= post.title %></td> <td><%= post.content %></td> <td><%= post.user %></td> <td><%= link_to 'Show', post %></td> <td><%= link_to 'Edit', edit_post_path(post) %></td> <td><%= link_to 'Destroy', post, confirm: 'Are you sure?', method: :delete %></td> </tr> <% end %> <br /> <%= link_to 'New Post', new_post_path %>Теперь с помощью волшебного метода can делаем:
<h1>Listing posts</h1> <table> <tr> <th>Title</th> <th>Content</th> <th>User</th> <th></th> <th></th> <th></th> </tr> <% @posts.each do |post| %> <tr> <td><%= post.title %></td> <td><%= post.content %></td> <td><%= post.user %></td> <% if can? :read, post %> <td><%= link_to 'Show', post %></td> <% end %> <% if can? :update, post %> <td><%= link_to 'Edit', edit_post_path(post) %></td> <% end %> <% if can? :destroy, post %> <td><%= link_to 'Destroy', post, confirm: 'Are you sure?', method: :delete %></td> <% end %> </tr> <% end %> </table> <% if can? :create, Post %> <%= link_to 'New Post', new_post_path %> <% end %>Также не забывайте в контроллере PostsController в методе create устанавливать связь пользователя и нового поста:
def create @post = current_user.posts.new(params[:post]) .... end
Потом напишем немного больше и продолжим с cancan + добавим спеки (rspec)
Немного UPD
Чтобы сделать меню, где вход\выход -- стандратное меню, нужно например в views/layouts/application.html.erb
добавить что-то вроде этого:
<div id="header"> <%= render "shared/links" %> </div>Папку shared создайте сами, и в ней файл _links.html.erb
<ul> <% if user_signed_in? %> <li><%= link_to "My Profile", edit_user_registration_path%> </li> <li><%= link_to "Sign out", destroy_user_session_path%> </li> <% else %> <li><%= link_to "Sign up", new_user_registration_path%> </li> <li><%= link_to "Sign in", new_user_session_path%> </li> <% end %> </ul>
Потом для правильно отрисовки в app/helpers/application_helper.rb:
module ApplicationHelper def resource_name :user end def resource @resource ||= User.new end def devise_mapping @devise_mapping ||= Devise.mappings[:user] end end
В модели User:
#models/user.rb class User < ActiveRecord::Base before_create :create_role has_many :posts #надеюсь вы не забыли установить эту зависимость :) private def create_role self.roles << Role.find_by_name(:user) end end
Ещё UPDКогда мы ищём роль: Role.find_by_name(:user), то предпологается, что уже есть такая роль. Поэтому перед запуском приложения в файле db/seeds.rb
Role.create(:name => :admin) Role.create(:name => :user)И потом в консоли rake db:seed
UPD код: http://code.google.com/p/example-app/
Прошлый пост
в статье есть не совпадения. делал всё как в ней в результате в User.rb:
ОтветитьУдалитьclass User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :token_authenticatable, :encryptable, :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
# Setup accessible (or protected) attributes for your model
attr_accessible :email, :password, :password_confirmation, :remember_me
before_create :create_role
has_many :users_roles
has_many :roles, :through => :users_roles
has_many :posts
private
def create_role
self.roles << Role.find_by_name(:user)
end
end
как видно много строчек отсутствуют.
и еще в статье написано:
"Всего лишь строчку:
load_and_authorize_resource"
куда её добавлять совершенно не понятно.
строчку в PostController там вроде было написано, но пропало! удивительно в общем.
УдалитьЯ думаю лучше заливать такие вещи на гит. Что и сделаю вечером.
А что не понятно с моделью User? То что там много кода? так это генерирует devise, а я привёл только то, что нужно добавить.
Этот комментарий был удален автором.
УдалитьСовершено не понятно что приведенный код нужно ДОБАВИТЬ - ведь там присутствует class User < ActiveRecord::Base, но отсутствуют комментарии-метки где находится уже сгенерённый код.
УдалитьВпрочем, если планируется заливать код на github такая необходимость пропадёт сама собой.
я залил на гугл коде. Там есть в конце. Остально учту.
УдалитьНа Гугле не могу скачать
ОтветитьУдалитьвы должны установить http://mercurial.selenic.com/ меркуриал и сделать hg clone url_adress
УдалитьСпасибо!
УдалитьА не подскажите какие версии используются в данном примере?
ОтветитьУдалитьcancan 1.6.7
ОтветитьУдалитьdevise 2.0.0
Спасибо, уже скачал, посмотрел.
ОтветитьУдалитьА где в статье написано как привязывать роли к юзерам???
ОтветитьУдалитьЭтот комментарий был удален автором.
ОтветитьУдалитьЭтот комментарий был удален автором.
УдалитьЭтот комментарий был удален автором.
УдалитьПодскажите, использовала devise (без кан-кана) и создала scaffold post.
ОтветитьУдалитьВо первых, не работает код:
@post = current_user.posts.new(params[:post])
Выходит ошибка: NoMethodError in PostsController#create
undefined method `posts' for #
Пришлось сделать
@post = Post.new(params[:post])
@post.user = current_user
Почему?
Во вторых, как правильно ограничить действия Edit и Destroy для не авторов?
То есть я сделаю в views/posts/index.html.erb
<% if post.user == current_user %>
Edit/Destroy
<% else %>
<% end %>
А как правильно запретить в контроллере доступ к этим действиям?
Ответ только на второй вопрос, средствами devise не осуществляется ограничение действий по ролям, devise это средство авторизации, не совсем правилен сам подход использования devise для этого, советую все таки посмотреть в сторону cancan. В скринкасте rayanb по этому поводу все отлично пояснено: http://railscasts.com/episodes/192-authorization-with-cancan. Например для вашего случая с Edit и Destroy код будет следующим:
Удалитьif user.role?(:author)
can :create, Post
can :update, Post do |p|
p.try(:user) == user
end
can :destroy, Post do |p|
p.try(:user) == user
end
end
По статье непонятен смысл замены config.sign_out_via = :delete
ОтветитьУдалитьменяем на :get.
По своему принципу убийство сессии должно происходить методом :delete и данный метод отлично работает. Для чего меняется на :get?
Так же небольшой UPD:
ОтветитьУдалить"Папку shared создайте сами, и в ней файл _links.html.erb" Данные действия не нужны как минимум в версии devise 2.2.3 при генерации вьюх девайса сейчас оно само генерится