Dans ce tutorial, nous partons du principe que vous connaissez déjà rubyonrails, nous utiliserons ici rails 3.1 et ruby 1.9.3 (à adapter pour rails 3 et rb 1.8).
Nous allons créer le début (vraiment la base de la base...) d'une plateforme de blog utilisant Devise, CanCan et ActiveAdmin
1) Création de notre application
rails new myblog
On ajoute dans le gemfile
gem 'devise'
gem 'cancan'
gem 'activeadmin'
## gem 'sass-rails' déjà dans le group assets
gem 'meta_search', '>= 1.1.0.pre'
Hop un bundle install pour installer les gems manquants
bundle install
Génération du model users et roles
rails g model user username:string
rails g model role name:string
On va maintenant créer un model category pour nos categories d'articles
rails g model category title:string content:text
On va maintenant créer un model post pour nos articles, l'intérêt de references c'est
que lors des migrations rails génére des index en base de données sur la clé
étrangère créée, de plus le belongs_to est aussi ajouté (il faudrait dans l'idéal
ajouter le has_many à la main dans le model category
rails g model post title:string content:text category:references
Création de la table de jointure en (dans la migration du model role)
create_table "roles_users", :id => false do |t|
t.references :user, :role
end
Hop on migrationne... :-p
bundle exec rake db:migrate
Création du controller public avec action index
rails g controller public index
Mettre l'action index de public comme page racine root dans config/routes.rb
root :to => "public#index"
On renomme le fichier /assets/images/index.html
mv public/index.html public/_index.html
On teste notre application
rails s
Dans une autre console
firefox localhost:3000
vous devriez voir ça :
Public#index
Find me in app/views/assets/images/index.html.erb
2) Installation de ActiveAdmin
rails generate active_admin:install
bundle exec rake db:migrate
ReDémarrez, sinon ça marchera pas. (donc ctrl + c puis rails s)
rails s
On lance un navigateur web et on constate que le style n'est plus tout à fait le même (à cause d'active_admin)
firefox localhost:3000
Ensuite on essaye ActiveAdmin, et wouahou le super écran de login, terrible...
login / password : admin@example.com / password
firefox localhost:3000/admin
Note : A savoir que ActiveAdmin utilise Devise
Maintenant on veut voir nos données (posts et catégories) dans ActiveAdmin
rails generate active_admin:resource post
Et
rails generate active_admin:resource category
Deux fichiers sont créés (app/admin/posts.rb et app/admin/categories.rb), ils permettent de configurer l'affichage et les filtres pour ActiveAdmin
Démarrage du serveur, s'il ne l'ai pas, et vous pouvez jouer (localhost:3000/admin)
rails s
On est francais hein, alors en francais c'est mieux hein ! On copie depuis Github le fichier de langue
cd config/locales && wget https://raw.github.com/gregbell/active_admin/master/lib/active_admin/locales/fr.yml
On renomme le fichier de active_admin, sinon la prochaine commande va l'écraser, (en fait, par
convention, le nom du fichier fini par quelquechose.LG.yml mais ça n'a pas d'importance car rails voit
dans le fichier yml "fr:" ou "en:" donc il sait de quelque langue il s'agit.)
mv config/locales/fr.yml config/locales/active_admin.fr.yml
Et on fait la même chose pour rails (format des dates, etc...)
cd config/locales && wget https://raw.github.com/svenfuchs/rails-i18n/master/rails/locale/fr.yml
Et on dit que notre application est en francais, dans config/application.rb on mets :
config.i18n.default_locale = :fr
Hop on redémarre le serveur
rails s
Et on teste, wouhaou mais c'est en Francais !
firefox localhost:3000/admin
Bon au login il y a une petite erreur (d'affichage : "translation missing: fr.devise.sessions.user.signed_in"),
en fait le fichier locale qu'on a récupéré est uniquement pour active_admin et non pour devise, donc voici comment faire :
cd config/locales && wget https://raw.github.com/diaspora/diaspora/master/config/locales/devise/devise.fr.yml
Puis redémarrez le serveur :
rails s
3) Installation de devise
Du fait que ActiveAdmin utilise devise, on saute cette étape :
Sinon vous auriez du faire le
1) root :to => "mon_controller#mon_action" dans config/routes si ce n'était pas fait
2) mettre
<%= notice %>
<%= alert %>
dans la vue ou le layout3) En console : rails generate devise:install # <-- ça c'est donc l'étape qu'on saute
Maintenant on install devise pour notre model user
rails generate devise user
Des routes ont été créées pour gérer le sign_in sign_up, sign_out, mot de passe perdu, etc...
Le model a été marqué comme utilisant devise :
## Include default devise modules. Others available are:
## :token_authenticatable, :encryptable, :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
Une migration a été créée pour le model user (ajoute les champs nécessaires au bon fonctionnement de devise).
Vous pouvez aller voir la doc de devise pour gérer des composants : https://github.com/plataformatec/devise
On effectue la migrabilitée
bundle exec rake db:migrate
Vous pouvez voir à quoi ressemble notre table users dans db/schema.rb :
create_table "users", :force => true do |t|
t.string "username"
t.datetime "created_at"
t.datetime "updated_at"
t.string "email", :default => "", :null => false
t.string "encrypted_password", :limit => 128, :default => "", :null => false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.integer "sign_in_count", :default => 0
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
end
Redémarrer le serveur
rails s
Vous pouvez accéder à quelques vues, offerte par devise :
firefox http://localhost:3000/users/sign_in
firefox http://localhost:3000/users/sign_out
firefox http://localhost:3000/users/password/new
Pour voir toutes les nouvelles vues :
bundle exec rake routes | grep /users
ça donne ça :
new_user_session GET /users/sign_in(.:format) {:action=>"new", :controller=>"devise/sessions"}
user_session POST /users/sign_in(.:format) {:action=>"create", :controller=>"devise/sessions"}
destroy_user_session DELETE /users/sign_out(.:format) {:action=>"destroy", :controller=>"devise/sessions"}
user_password POST /users/password(.:format) {:action=>"create", :controller=>"devise/passwords"}
new_user_password GET /users/password/new(.:format) {:action=>"new", :controller=>"devise/passwords"}
edit_user_password GET /users/password/edit(.:format) {:action=>"edit", :controller=>"devise/passwords"}
PUT /users/password(.:format) {:action=>"update", :controller=>"devise/passwords"}
cancel_user_registration GET /users/cancel(.:format) {:action=>"cancel", :controller=>"devise/registrations"}
user_registration POST /users(.:format) {:action=>"create", :controller=>"devise/registrations"}
new_user_registration GET /users/sign_up(.:format) {:action=>"new", :controller=>"devise/registrations"}
edit_user_registration GET /users/edit(.:format) {:action=>"edit", :controller=>"devise/registrations"}
PUT /users(.:format) {:action=>"update", :controller=>"devise/registrations"}
DELETE /users(.:format) {:action=>"destroy", :controller=>"devise/registrations"}
D'une manière générale, si vous souhaitez voir les routes
bundle exec rake routes
Essayez de vous inscrire sur https://localhost:3000/users/sign_up
firefox http://localhost:3000/users/sign_up
Vous êtes enregistré et logué, une preuve ?
Accéder à https://localhost:3000/users/sign_up et vous serez redirigé sur "/".
Bon là le problème c'est que l'on peut pas se délogué, on va donc ajouter un bouton pour la vue
"public#index", ouvrons donc le fichier "app/views/assets/images/index.html.erb" et ajoutez-y ceci :
<p>
<%= link_to "Déconnexion", destroy_user_session_path, :method => :delete %>
</p>
Lancer le navigateur et déconnecté vous, vous avez peut-être l'impression
qui ne s'est rien passé et pourtant vous pouvez de nouveau accéder à https://localhost:3000/users/sign_up
Pour que les instructions envoyées par mail fonctionne il faut définir l'url
dans le fichier config/environments/development.rb (il faudra aussi le faire en production.rb)
config.action_mailer.default_url_options = { :host => 'localhost:3000' }
On Redémarre le serveur :
rails s
Bon vous avez oublié votre mot de passe, mais si je vous le dis ! Allez hop on test :
firefox http://localhost:3000/users/password/new
là vous devriez avoir, reçu votre mot de passe, mais en fait non, il faut configurer actionmailer dans rails :
ici il s'agit d'une config qu'il faudra certainement adapter pour vous.
Ajouter le fichier config/initializers/email.rb et mettez dedans (enfin pas bêtement configurez ça comme il faut) :
ActionMailer::Base.smtp_settings = {
:address => "smtp.gmail.com",
:port => 587,
:domain => "mondomaine.com",
:user_name => "monidentifiant",
:password => "monmotdepasse",
:authentication => "plain",
:enable_starttls_auto => true
}
Redémarrer le serveur
rails s
Bon réessayez, chez moi ça marche en tout cas (ah bon c'est pas un argument ?)
Vérifiez que ça n'a pas atterri dans vos spams. Cliquez sur le lien "Change my password"
http://localhost:3000/users/password/new
Tout ça c'est bien !
Bon mais les vues sont en anglais, et nous avions décidé de tout mettre en francais, alors on va générer les vues :
rails generate devise:views
Maintenant vous traduisez devise à la main (les vues se trouvent dans app/views/devise/... )
Bon devise avec le model admin_user d'ActiveAdmin ne nous intéresse pas vraiment nous avons notre model user pour faire le travail.
Pour ne plus utiliser le devise d'ActiveAdmin (ou en tout cas outrepasser l'authentification, il suffit de commenter les lignes suivantes dans le fichier config/initializers/active_admin.rb :
config.authentication_method = :authenticate_admin_user!
config.current_user_method = :current_admin_user
A ce stade, si vous redémarrez votre serveur vous constaterez qu'il n'y a plus de système
d'authentification pour accéder à localhost:3000/admin
Bon par contre nous voulons quand même une authentification avec notre installation personnelle de devise et notre model user
Donc les lignes ci-dessus vous pouvez les remplacer par celles-ci (ce sont des méthodes générées par devise : "authenticate_mon_model!" et "current_mon_model") :
config.authentication_method = :authenticate_user!
config.current_user_method = :current_user
Il faut ajouter une ligne dans config/initializers/active_admin.rb à la section # == Logging Out
En dessous de ceci (la ligne est commenté) :
## config.logout_link_path = :destroy_admin_user_session_path
Ajoutez ceci :
config.logout_link_path = :destroy_user_session_path
[EDIT] Et voir ici un autre truc à rajouter : https://twitter.com/petitchevalroux/status/237873578815680513
Bon et du coup vous pouvez par la même occasion commenter les routes devise_for :admin_users dans config/routes.rb et ajouter la config pour user de la sorte :
devise_for :users
## devise_for :admin_users, ActiveAdmin::Devise.config
C'est bien, on avance prochaine étape c'est l'installation de CanCan et cablage avec Devise et ActiveAdmin.
4) Installation de CanCan
Génération du model de gestion des permissions (ability) c'est là dedans que l'on va définir les droits des utilisateurs
rails g cancan:ability
Vous pouvez voir la doc de CanCan : pour savoir quoi mettre dans ability : https://github.com/ryanb/cancan/wiki/Defining-Abilities
Pour résumer : avec CanCan, un model ability permet de voir si l'on a les droits à une ressource. (voir les prochaines lignes de code)
Il faut un before filter ("authorize_resource", "load_and_authorize_resource" ou bien encore directement dans la méthode "authorize!")
dans le controller ou bien dans l'action pour dire à CanCan que l'on veut gérer les droits pour cette action / controller.
Sinon la vérification des droits pour l'utilisateur courant ne se fait pas.
Je vous conseille de TOUT mettre sous le controle de CanCan (et dans ability vous gérez le cas où
user est nil, comme ça pas d'oublie) et justement pour que CanCan vous avertisse d'un oubli vous mettez
dans votre controleur la méthode "check_authorization" qui ne manquera pas de vous signalez qu'une
action n'est pas sous contrôle de CanCan (ou bien dans application_controller si tout est sous control CanCan)
Bon maintenant que tout est expliqué pour CanCan on va ajouter les contrôles pour les posts (billet du blog).
L'idéal c'est que les utilisateurs puissent manager leurs posts (=billets).
Pour tester CanCan dans ActiveAdmin, on va interdire de manager le post qui a pour id 2, pour cela je vous laisse créer 3 posts (du coup id 1,2 et 3)
Ensuite pour vérifier les authorisations éditez le fichier : app/admin/posts.rb, vous noterez qu'il y a un
bug avec load_and_authorize_resource et index, ça viendrait d'un problème entre CanCan load_resource et kaminari donc patience.
ActiveAdmin.register Post do
# Pour CanCan
# controller.load_and_authorize_resource # Ne fonctionne pas pour index
controller.authorize_resource
end
Et dans ability.rb, le corps de la méthode initialize :
user ||= User.new # guest user (not logged in)
if user.nil? then
## Droit à rien, non mais...
else
#can :manage, :all
can :manage, Post, :id => 2
can :manage, Category, :id => 2
end
Pas besoin de relancer le serveur, essayez de modifier le post numéro 1 ou 3, ça devrait échoué !
Bon maintenant on a nos utilisateurs et on veut associer les posts à l'utilisateur courant (sans le montrer dans le formulaire ni en hidden (car ça serait modifiable)).
rails g migration add_user_id_to_posts
Donc dans la méthode "change" on mets ceci :
add_column :posts, :user_id, :integer
add_index :posts, :user_id
On migrabilite...
bundle exec rake db:migrate
Maintenant ça se complique un peu, je veux masquer la relation de user à post dans le
formulaire ActiveAdmin et je veux que le user_id corresponde au current_user de plus je
souhaite récupérer le blog en train d'être administré et quasiment tout scoper sur blog (on veut faire un plateforme multi-blog)
Il existe un méthode scope_to qui permet de scoper par défaut les models dans ActiveAdmin (Attention c'est différent de scope :all)
Donc dans app/admin/posts.rb, ajoutez ceci pour scoper sur blog :
scope_to :current_blog
Et pour associer l'utilisateur et le blog pour chaque post (toujours dans app/admin/posts.rb) :
after_build do |post|
post.user_id = current_user.id
post.blog_id = current_blog.id
end
Bon ensuite il faut définir la méthode current_blog appelée, donc dans ApplicationController (app/controller/application_controller.rb):
def current_blog
return Blog.where(:url => request.env['HTTP_HOST']).first
end
Ce serait bien d'avoir un model blog en fait :
rails g model blog name:string url:string user:references
rails g migration add_blog_id_to_posts
Créez la colonne blog_id dans posts dans la migration précédemment créée :
add_column :posts, :blog_id, :integer
add_index :posts, :blog_id
On effectue la migratitude :
bundle exec rake db:migrate
Et il faut dire à user qu'il a plusieurs blogs (app/model/user.rb) :
has_many :blogs
Et à post qu'il appartient à un blog (app/model/post.rb) :
belongs_to :blog
Et dans blog.rb :
has_many :posts
Ensuite en console créons le blog :
Blog.new(:user_id => 1, :name => "Mon blog", :url => "localhost:3000").save
Jusque là aucun soucis
Essayons maintenant, créez, filtrez, etc... :
firefox localhost:3000/admin/posts
Bon le problème c'est que dans le formulaire les champs "user" et "blog" apparaissent et nous ne nous
le voulons pas. Nous allons donc trouver un moyen de virer ces champs.
Bon pour les filtres en cherchant un peu, on trouve, voici comment procéder (dans app/admin/posts.rb):
(Post.column_names.map{ |c| c.to_sym } - [:blog_id, :user_id]).each{ |field|
filter field
}
Bon et pour le formulaire voici le code toujours dans (app/admin/posts.rb)
form do |f|
f.inputs "Posts" do
f.input :title
f.input :content
f.input :category
end
f.buttons
end
Dans le show, pour les relations belongs_to, active_admin fait un appel à la méthode name sur le model lié : @post.blog.name @post.user.name
donc vous pouvez faire ceci dans app/model/user.rb :
def name
return email
end
De cette manière l'email s'affichera, sinon une autre solution consiste à redéfinir show (https://activeadmin.info/docs/6-show-screens.html)
Bon l'idéal ici serait de mettre un éditeur W(what)Y(you)S(see)I(is)W(what)Y(you)G(get) pour le contenu du post.
Bon en fait je vais vous faire installer un éditeur mais pas vraiment WYSIWYG : markitup
Je ne suis pas fan du WYSIWYG pourquoi ? Parce que le client pête toute la charte graphique en faisant du site un sapin de noël
(choix des couleurs, surlignement, taille, font).
Pour installer markitup je vous invite à suivre cet article : https://blog.escarworld.com/post/2011/09/26/Pr%C3%A9parer-MarkItup-pour-rails-3.1
De la même manière vous pouvez décider de modifier "show" pour afficher le markdown / textile / bbcode converti en html (les gems à utiliser pour les conversions vers html sont listés sur le lien ci-dessus).
Je pense qu'on a fait le tour vous allez peut-être me dire que ce n'est pas terminé, je ne souhaitai
pas coder une application entière mais essayer de d'anticiper les problèmes potentiels avec ActiveAdmin.
Si vous avez quelques questions je m'efforcerai d'y répondre, si vous remarquez des erreurs/fautes n'hésitez pas !
Maintenant, il vous appartient de choisir entre tout refaire à la main (long mais customizable) ou d'utiliser ActiveAdmin (bien fait et déjà fait, mais pas pleinement personnalisable). Qu'en pensez-vous ?
EDIT : myblog sources