Le blog de la CT2C

Starter App - Partie 1 - Configuration initiale du projet

Par Régis Millet aka Kulgar , publié le 16 Décembre 2016

Vers la partie suivante

Actualisé le 25 Novembre 2016 pour une Starter App fonctionnant en Ruby 2.2 et Rails 4.2

Introduction : pourquoi "Starter App" ?

Au fil de nos projets, nous nous sommes aperçus que nous réutilisions (évidemment) un certain nombre de gems. Ces gems forment une sorte de noyau cohérent pouvant être considéré comme le cœur de toute bonne application Rails.

Noyau cohérent car la majorité de ces gems s'agencent les unes avec les autres et très souvent, elles s'intègrent parfaitement ensemble.
Cœur de l'application Rails car ces gems, sans être primordiales pour faire tourner une application Rails, vont ajouter des fonctionnalités incontournables et utilisées partout sur le Web de nos jours.

Je vous propose donc avec cette liste d'articles "Starter App" de vous créer une base de projet "vierge", avec les configurations dont vous aurez besoin et les gems les plus utiles, nécessaires dans tout (ou presque) projet. A la fin de cette suite d'articles, vous disposerez d'une application :
- pré-configurée pour le déploiement (sur Heroku ou serveur dédié),
- traduisible dans différentes langues,
- capable d'envoyer des mails,
- possédant une partie administrative complète,
- ainsi qu'une interface client compatible mobiles,
- capable de bien d'autres choses encore...


Sans être une liste exhaustive, je vais vous lister ci-après quelques gems que nous allons découvrir ensemble. Ce ne sera pas une liste exhaustive car moi-même je découvre de nouvelles gems à chaque projet et il se peut que je veuille vous en présenter de nouvelles. Voici donc quelques unes des gems que nous allons voir par la suite :

  • Foundation: Peut-être connaissez-vous plutôt "Twitter Bootstrap" ? Ce sont deux frameworks pour le front end de votre application. Traduction : ces gems vous apportent tout un tas de fonctionnalités préconçues et une apparence toute faite (et plutôt pas mal) pour votre application. Après avoir mis en place un tel framework, vous serez surpris de voir la rapidité avec laquelle vous produirez une magnifique application compatible PC et Mobiles !
  • Devise : cette gem est devenue incontournable dans toute application avec une partie privée. Elle vous met en place en deux temps trois mouvements un système d'authentification robuste et facilement personnalisable. De plus, de nombreux plugins (d'autres gems) viennent se greffer dessus pour ajouter de nouvelles fonctionnalités. Comme la possibilité pour vos utilisateurs de s'authentifier grâce à leur réseau social préféré ou leur compte Google.
  • Simple Form : cette gem quoi que moins importante va vous faciliter la vie pour créer vos formulaires. Nous la verrons avec un d'autant plus grand intérêt que ceux qui l'ont conçues sont les mêmes que ceux qui ont conçu Devise. De fait, ces deux gems fonctionnent fort bien ensemble.
  • Cancan / Rolify : on ne peut parler de système d'authentification sans parler de rôles. Les rôles permettent de restreindre l'accès à certaines parties d'un site à certains utilisateurs spéciaux. Ce pourrait être, par exemple, la restriction de la partie "modératrice" d'un forum aux utilisateurs dont le rôle est d'être modérateur. Ces deux gems (CanCan / Rolify) fonctionnent de concert pour vous faciliter la mise en place de ce type de restriction. Il n'est pas surprenant non plus qu'une documentation fournie explique comment les mettre en place ensemble avec Devise.

... Voilà pour les gems que nous aborderons à coup sûr. Nous en verrons sûrement d'autres, notamment des gems pour vous faciliter le développement, mais pour l'heure, passons à la première étape et créons notre projet de base.

Création du projet


Nous allons commencer par créer une nouvelle application. Allez dans votre répertoire favori contenant tous vos projets rails, puis faites la commande (que vous connaissez sûrement) :

$ rails new starter_app

Cela vous crée le squelette de l'application et vous installe les dernières gems, c'est parfait ! En guise de base de données, nous allons utiliser MySQL2, parce que c'est celle qui est la plus répandue sur les configurations de base sur Serveurs dédiés ou mutualisés... Évidemment, si vous voulez déployer sur Heroku, utilisez plutôt PostgreSQL.

Notez que vous pouvez aussi utiliser SQLite3, mais ça ne serait pas vraiment une "Starter App" si je ne vous expliquais pas comment utiliser une autre SGBD.


Donc pour utiliser MySQL2, il vous suffit de modifier le fichier Gemfile en remplaçant la ligne

gem 'sqlite3'

Par
gem 'mysql2' # https://github.com/brianmario/mysql2 <- lien vers le projet associé sur GitHub 

Ceci étant fait, lancez la commande :

$ bundle install

Ensuite, rendez-vous dans le répertoire config et éditez le fichier database.yml puis renseignez les différentes options requises pour chaque environnement :

default: &default
# Remplacez selon le sgbd choisi
adapter: mysql2
# Encodage de votre base de données, en général utf8
encoding: utf8
# Règles de tri à utiliser pour votre base de données,
# utf8_general_ci est la mieux adaptée pour le français
collation: utf8_general_ci
# Quelques options de connexion
reconnect: false
pool: 5
timeout: 5000
# Identifiants de connexion à votre base de données,
# généralement identique dans les environnements development/test
username: identifiant
password: mot_de_passe
# Adresse IP de la machine où le serveur MySQL est hébergé,
# par exemple, sur un réseau privé local :
# host: 192.168.1.2
# ou localhost si c'est sur votre machine de développement
# ou rien du tout, localhost sera utilisé par défaut

development:
<<: *default
# Nom de votre base de données
database: starterapp_dev

# Même chose pour l'environnement de test,
# comme cet environnement est souvent identique à celui de development,
# on réutilise simplement les paramètres par défaut
test:
<<: *default
database: starterapp_test

# Pour l'environnement de production, vous devez adapter,
# en fonction du serveur où votre application sera hébergée.
production:
<<: *default
# On écrase ici certaines configurations par défaut
database: starterapp_prod
pool: 20
timeout: 10000
username: identifiant_serveur_mysql
password: mot_de_passe_serveur_mysql
host: XXX.XXX.XXX.XXX
# L'adresse IP du serveur de base de données MySQL.
# Ou ne le mettez pas si MySQL est installé sur le même serveur que l'application

Vous trouverez ici d'autres exemples de configuration du fichier database.yml.

Pour être sûr que tout fonctionne bien, faites la commande :

$ rake db:create

Si vous n'avez pas d'erreur, rails vous aura créé les bases de données des environnements Développement et Test sur votre serveur MySQL local.

J'indiquais ici auparavant que nous utilisions la gem "thin" un serveur plus rapide que WebRick, celui qui est utilisé par défaut. Mais en rails 4, la gem "spring" est utilisée pour accélérer l'environnement de développement. J'estime donc que thin n'est plus nécessaire.


Lancez le serveur et regardez si tout fonctionne bien en vous rendant sur la page d'index de votre application.

Faites attention, si vous voulez accéder à votre application Rails depuis un autre poste, il faut désormais exécuter la commande rails server avec une option : -b 0.0.0.0, ce qui rendra l'application RoR ouverte au reste de votre réseau.


Depuis plusieurs versions, Rails inclut turbolinks par défaut. Je vous laisse découvrir cette gem sensée accélérer grandement le chargement de vos pages Web. Cependant elle est souvent désactivée dans les projets car entre souvent en conflit avec d'autres framework front-end comme angular, react, etc. Vous êtes seuls aptes à décider de la désactiver ou non.


Si tout est bon, continuons !

Création de la première page


Histoire d'avoir de la matière pour tester nos futurs ajouts, créons une première page. N'allons pas par quatre chemins, vous connaissez sûrement la démarche :

-> Créons une route pour la page d'accueil :
Rails.application.routes.draw do
# Dans le fichier config/routes.rb, ajoutez la ligne:
root "client_pages#home"
end

Créez le controller "ClientPagesController" qui nous sera très utile par la suite :

$ rails generate controller ClientPages home

Cela vous crée tout ce qu'il faut : le controller, la view, etc. Pour préparer un peu la suite de nos configurations de base, nous allons personnaliser la page d'accueil. Modifiez donc le fichier "views/client_pages/home.html.erb" comme ceci :

<h1><%= t :home %></h1>
<p><%= flash[:notice] %></p>

Puis le fichier controllers/client_pages_controller.rb comme ceci :

class ClientPagesController < ApplicationController
def home
flash[:notice] = t(:greetings)
end
end

Comme vous le voyez, nous allons récupérer du texte depuis les fichiers de traduction présents dans config/locales. Modifiez le fichier en.yml qui s'y trouve comme ceci :

en:
home: "Home Page"
greetings: "Welcome to StarterApp"

Puis rafraîchissez votre page d'accueil. Si tout est bon, vous voyez le texte que nous avons inséré dans le fichier yml. C'est superbe, sauf que nous sommes francophones et nous avons fermement l'intention de proposer une version française de notre application ! Continuons donc la configuration de cette dernière.

Mise en français d'une application Rails



Dans un premier temps, créons un dossier nommé fr dans le dossier locales dans lequel nous stockerons tous nos fichiers yml contenant nos traductions en français.

Créez-y un premier fichier fr.yml contenant les lignes suivantes :
fr:
home: "Page d'accueil"
greetings: "Bienvenue sur StarterApp"

Par défaut, toute application Rails est en anglais. Cela signifie que toute application Rails charge et utilise uniquement les fichiers de langue (yml et autres) dans lequel elle détecte "en:". De plus, une application Rails ne charge pas tout les fichiers locales par défaut ! En effet, ceux présents dans des sous-dossiers sont ignorés. Ne me demandez pas la raison, je ne la connais pas.

Comme c'est peu pratique pour organiser ses fichiers, nous allons forcer l'application Rails à tous les charger. Dans le fichier config/application.rb, décommentez les deux lignes commençant pas 'config.i18n...' puis modifiez-les de la façon suivante :

config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.{rb,yml}').to_s] 
config.i18n.default_locale = :fr
config.i18n.available_locales = [:fr, :en]
config.i18n.fallbacks = [:fr, :en]

Nous avons simplement ajouté un '**' dans la variable load_path pour forcer Rails à charger toute la hiérarchie du dossier locales récursivement. Ainsi les fichiers présents dans des sous-dossiers de locales seront également chargés lors de l'initialisation de l'application.

Nous avons spécifié la langue par défaut en initialisant la variable config.i18n.default_locale à :fr. Nous précisons par ce biais que notre application Rails doit charger, par défaut, la langue française.

Nous avons rajouté une ligne en précisant les langues disponibles. Nous excluons ainsi toutes les autres langues et Rails saura qu'elles ne sont pas supportées.En outre cette configuration est devenue obligatoire dans les versions de Rails plus récentes.

Enfin, nous avons configuré l'ordre "fallback" des langues. Si une langue n'est pas trouvée par défaut sur notre application, celle-ci ira chercher la traduction française puis l'anglaise si la traduction française n'existe pas. N'hésitez pas à changer cet ordre selon votre public cible principal (francophone, anglophone, etc.)

Profitons-en pour définir le Time Zone, autrement dit, l'horloge locale utilisée par notre application Rails lorsqu'elle doit afficher une date ou une heure :

# Toujours dans application.rb, décommenter #config.time_zone et mettre : 
config.time_zone = 'Paris'

Il s'agit là du time zone par défaut. Si vous possédez des utilisateurs internationaux il faudra bien faire attention à leur laisser la possibilité de modifier leur fuseau horaire dans leurs paramètres.

Bien, maintenant, comme nous avons deux langues supportées, nous devons faire en sorte que Rails récupère la langue à afficher à nos utilisateurs selon les pages où ils naviguent. Il existe plusieurs manières de faire cela, toutes très bien expliquées ici : guide officiel Ruby On Rails sur i18n.

Nous utiliserons la méthode la plus simple, à savoir que la langue sera fournie à l'aide d'un paramètre passé dans l'url. Modifiez votre fichier controller/application_controller.rb de la façon suivante :

before_filter :set_locale

def set_locale
I18n.locale = params[:locale] || I18n.default_locale
end

Et c'est tout. Nous avons utilisé un before_filter pour exécuter la méthode set_locale avant chaque requête. Cette méthode définit simplement la langue en modifiant la variable i18n.locale en récupérant le paramètre [:locale] fourni depuis l'URL ou en utilisant la langue par défaut si ce paramètre n'est pas précisé.

Relancez votre serveur et rafraîchissez la page. Cette dernière devrait désormais être en français. Testons notre méthode set_locale en se rendant sur la page : http://localhost:3000/?locale=en. Vous avez maintenant le texte en anglais.

Bien, sauf que nous n'allons pas tout traduire à la main non plus. Fort heureusement un bon nombre d'éléments ont déjà été traduits par d'autres développeurs et sont mis généreusement à disposition dans des gems. Il suffit, pour les utiliser, d'installer ces gems. Allons-y ! Dans votre Gemfile, ajoutez ces deux gems :

gem "rails-i18n" # github.com/svenfuchs/rails-i18n
gem "i18n" # github.com/svenfuchs/i18n

La première gem contient un grand nombre de traductions, comme nous avions défini la variable config.i18n.available_locales, Rails ne chargera que les traductions incluses dans cette variable (autrement, il les charge toutes). Cette gem traduit notamment les formats des dates, les jours, les mois, bref, tout ce qui peut être généré et affiché automatiquement par Ruby On Rails.
La deuxième est incluse par défaut en tant que dépendance indirecte de Rails. Mais je l'inclus quand même, juste pour que vous ayez le dépôt GitHub ainsi que la dernière mise à jour de la gem. C'est elle qui fournit toutes les méthodes concernant i18n et donc toutes les fonctionnalités de traduction.

Il reste le problème des pages statiques, comme les pages d'erreur 404 / 500 ... qui se trouvent dans le dossier public. Pour ces pages statiques, il suffit de créer un fichier par langue comme ceci : 500.fr.html, 500.en.html ... Rails récupérera la langue adéquate en cas d'erreur. N'oubliez pas de préparer une page 500.html par défaut dans le cas où ça serait Rails lui-même qui ne fonctionnerait plus !

Depuis Rails 4 a également été ajouté un système de mise au singulier / pluriel, etc. automatique en fonction de la langue actuellement active. Cela se passe dans config/initializers/inflections.rb. Et il existe un dépôt github contenant le code pour le français. Vous pouvez ajouter cette gem, ça ne coûte rien, à priori.

gem 'inflections' # github.com/davidcelis/inflections

C'est terminé pour ce qui concerne les traductions. C'est une bonne chose de faite car beaucoup de gems que nous serons amenés à utiliser sont déjà traduites et nous les aurons ainsi quasi directement en français. :)

Il nous reste encore deux petites parties pour en terminer avec la configuration.

Configuration de base de l'application


Nous pouvons encore paramétrer quelques autres petites choses bien pratiques dans le fichier application.rb et les fichiers d'environnement. Par exemple, la variable config.filter_parameters est très importante d'un point de vue sécurité. Elle filtre en effet tous les paramètres éventuellement fournis par vos utilisateurs des fichiers journaux de votre application. Mettez-y tous les attributs de vos models qui seraient sensibles, donc :password, :password_confirmation, :email, :name, etc.. Vous pouvez éditer ce paramètre dans "config/initializers/filter_parameter_logging.rb"


Ensuite, j'ai pour habitude (et c'est une bonne habitude) d'avoir une belle hiérarchie dans tous mes dossiers de mon application. Cela concerne surtout deux dossiers : lib et models. Je vous invite à créer un nouvel initializers : autoload_paths.rb.

Dans le dossier lib, j'y mets (dans un sous-dossier) tout le code que je réutilise entre mes différentes applications. Seulement par défaut, l'application Rails ne charge pas ces fichiers au lancement du serveur. Nous allons donc lui préciser de charger aussi ces fichiers.

# Dans config/initializers/autoload_paths.rb, ajoutez : 
# Comment the lib/shared line if you don't need it.
Rails.application.config.autoload_paths += %W(#{Rails.root}/lib/shared)

Évidemment, n'oubliez pas de créer le sous-dossier shared dans le dossier lib (enfin, peu importe le nom que vous lui donnez. ). Stockez-y vos modules et class Ruby personnels ainsi que les modules/class Rails auxquels vous ajoutez des méthodes (comme String, par exemple). Vous n'êtes pas obligés d'effectuer cette configuration, elle est juste nécessaire si vous en avez une réelle utilité.

Si vous créez un sous-sous-dossier, par exemple lib/shared/table_functions n'oubliez pas qu'ensuite vos modules devront être initialisés comme ceci :

# lib/shared/table_functions/headers.rb
module TableFunctions::Headers
end

Voilà pour ce qui est du dossier lib. Pour ce qui est du dossier models, la configuration est un peu différente. Dans config/initializers/autoload_paths.rb, ajoutez-y la ligne suivante :

Rails.application.config.autoload_paths += Dir["#{Rails.root}/app/models/*"].find_all { |f| File.stat(f).directory? }

On spécifie simplement à Rails de charger tous les fichiers contenus dans le dossier "models" y compris ceux dans les sous-dossiers. Pour ensuite ranger vos fichiers models dans différents sous-dossiers, il vous faudra faire attention de toujours donner, à vos sous-dossiers, un nom différent de vos models. En ajoutant simplement un _related à la fin du nom de votre sous-dossier, vous aurez une belle architecture. Par exemple :

app/models :
- /users_related :
-- user.rb
-- address.rb
- /products_related :
-- product.rb
-- category.rb
...

Notez l'initialisation d'un model, par exemple le fichier users_related/user.rb :

class User < ActiveRecord::Base
end

Vous n'avez pas besoin de préciser de namespace, i.e. vous n'aurez pas à écrire :

class UsersRelated::User < ActiveRecord::Base
end

Tout simplement parce que nous chargeons récursivement tous les fichiers du répertoire "models". Les namespaces doivent être réservés aux models héritant d'un autre model, par exemple si Customer hérite de User, vous aurez à écrire :

class User::Customer < ActiveRecord::Base
end

Sans oublier d'ajouter la colonne type à la table users nécessaire à Rails pour que l'héritage fonctionne parfaitement. De cette façon, vous garderez une structure propre dans vos models. Je peux vous dire que dès que vous atteignez la vingtaine de models, si vous n'avez pas mis en place une certaine structure, vous passerez votre temps à chercher celui dont vous avez besoin.

Enfin, une dernière configuration qui peut vous être très utile : l'ajout d'un dossier comme répertoire par défaut pour les assets. Ne vous êtes-vous jamais demandé comment vous pourriez ajouter un dossier contenant toutes vos fonts en tant qu'assets ? Eh bien ce n'est pas plus compliqué que cela :

# Dans config/initializers/assets.rb
Rails.application.config.assets.paths << "#{Rails.root}/app/assets/fonts"

De cette façon, vous ajoutez le dossier fonts à vos assets. Cela vous permet ensuite, dans vos fichiers scss, d'accéder à vos fonts comme ceci :

@mixin face {
@font-face {
font: {
family: "my_font";
weight: 'normal';
style: 'normal';
}
src: asset-url('my_font.eot', font);
src: asset-url('my_font.eot?#iefix', font) format('embedded-opentype'),
asset-url('my_font.woff', font) format('woff'),
asset-url('my_font.ttf', font) format('truetype'),
asset-url('my_font.svg', font) format('svg');
}
}

Vous utilisez la méthode asset-url et précisez que vous cherchez une font en renseignant le deuxième paramètre de cette méthode, ici font.

Configuration des environnements


Reste à configurer les différents environnements dans le dossier config/environments. Pour l'environnement de développement, il s'agit surtout de paramétrer les options pour l'envoi des mails, histoire que l'on puisse tester cette fonctionnalité devenue incontournable. En développement j'utilise désormais letter_opener. Ajoutez cette gem à votre Gemfile :

group :development do 
...
gem "letter_opener" # github.com/ryanb/letter_opener
...
end

Reste à configurer convenablement config/environments/development.rb :

  # ActionMailer Config
# en dev, l'host c'est localhost ou l'ip du poste où tourne votre application RoR
config.action_mailer.default_url_options = { :host => 'localhost:3000' }
config.action_mailer.delivery_method = :letter_opener
# change to true to allow email to be sent during development
config.action_mailer.perform_deliveries = true
# pour récupérer les erreurs lors d'un envoi de mail
config.action_mailer.raise_delivery_errors = true
config.action_mailer.default :charset => "utf-8"

Faites de même pour l'environnement de production :

  # ActionMailer Config
config.action_mailer.default_url_options = { :host => 'www.example.com' }
config.action_mailer.delivery_method = :smtp
config.action_mailer.default :charset => "utf-8"


Je vous invite également à créer un fichier config/initializers/action_mailer.rb.example et à ajouter dans .gitignore (si vous utilisez git) config/initializers/action_mailer.rb. Dans le ".example" vous pouvez y mettre :

ActionMailer::Base.smtp_settings = {
:address => "smtp.server.com", # par exemple pour Google : smtp.gmail.com
:port => 587, # généralement utilisé par Google
:user_name => "votre@adresse.com",
:password => "votre_mot_de_passe",
:authentication => :login, # méthode d'authentification, attention :plain enverra votre mot de passe en clair
:enable_starttls_auto => true # Un peu de sécurité, ça fait jamais de mal, attention ce paramètre n'est pas toujours supporté par les serveurs SMTP
}

Une fois votre application en production vous n'aurez qu'à renommer ce fichier en action_mailer.rb et le modifier convenablement. Je vous indique ces précautions pour éviter que votre mot de passe vers votre serveur SMTP ne soit accessible par tous. Un autre moyen est d'utiliser les variables d'environnement, ce qui donnerait :

ActionMailer::Base.smtp_settings = {
:address => "smtp.server.com",
:port => 587,
:user_name => ENV['SMTP_LOGIN'], # utiliser la constante ENV pour accéder aux variables d'environnement dans ruby
:password => ENV["SMTP_PASSWORD'],
:authentication => :login,
:enable_starttls_auto => true
}

Cela vous éviterait de mettre ce fichier de config dans .gitignore. En revanche cela implique de savoir utiliser ces variables d'environnement et de pouvoir les paramétrer. Ce n'est pas toujours possible, notamment sur des mutualisés (mais ça l'est sur Heroku, par exemple).

Enfin, selon si vous prévoyez de déployer sur un serveur Apache, NGinx ou Heroku vous aurez à utiliser l'une des trois lignes suivantes : (évidemment, pas les 3 en même temps)

config.action_dispatch.x_sendfile_header = "X-Sendfile" # apache
config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # nginx
config.assets.initialize_on_precompile = false # Heroku

Concernant l'environnement de test, configurez-le comme l'environnement de production ou l'environnement de développement selon si vous testez votre application sur votre machine ou sur le serveur.

Et voilà ! Avec tout ça, vous avez une belle configuration de base pour votre application. Comme toujours, si j'ai oublié quoi que ce soit, n'hésitez pas à me le signaler.

Dans la prochaine partie, je vous présenterai quelques gems que nous installerons qui fournissent quelques outils pour faciliter le développement de notre application.


Index -- --

  • 3 Commentaire


  • Désolée pour la réponse tardive. :)
    "expecting keyword_end" veut dire qu'il manque un "end" quelque part . Se peut-il que tu l'aies supprimé par mégarde ?
    Sinon, la ligne "config.autoload_paths" doit être placée entre "class Application < Rails::Application" et "end", avec les autres config. C'est peut-être ça qui coince ?


    Par Arpsara, le 13 Mars 2014

  • salut merci pour le tutoriel. J'utilise rails 3.2.16 j'ai comme erreur dans application.rb à la ligne config.i18n.load_path += Dir[Ra... une erreur
    user1@ubuntu:~/Documents/programming/rails_book/tuto/StraterApp$ rails s
    /home/user1/.rvm/gems/ruby-2.0.0-p195/gems/railties-3.2.16/lib/rails/commands.rb:53:in `require': /home/user1/Documents/programming/rails_book/tuto/StraterApp/config/application.rb:63: syntax error, unexpected tIDENTIFIER, expecting keyword_end (SyntaxError)
    ...'**', '*.{rb,yml}').to_s]config.i18n.default_locale = :fr
    ...


    Par gdiamond, le 5 Mars 2014

  • Bonjour,

    C'est un excellent article Merci.


    Par #tayebM, le 14 Janvier 2013
Insérez votre commentaire
  1. Min: 50 caractères, Max: 800. Actuellement: 0 caractères

  2. ne pas remplir