Localisation, Engine et architecture

Bonsoir à tous,

Après beaucoup de lecture, je viens vous demander vos avis sur ces sujets.

Après un post sur la localisation j’ai lu beaucoup de choses mais je ne
suis pas sur de mon choix … on ne parle que de traductions et ce n’est
pas vraiment mon cas.
Je voudrais tout simplement placer tout les messages de mes vues dans
des fichiers externes afin de minimiser leurs redondances et de les
modifier rapidement. Je n’ai pas de véritables besoins de traduction.

D’autre part j’ai lu pas mal de chose sur les engine. Des avis plutot
divergeant mais avez vous des retours de prod ? Est ce que c’est
complexe à déployer/maintenir ?

Pour terminer, j’ai une question sur l’architecture d’une appli rails.
Après avoir lu des tutos, étudier des applis rails, je me rends compte
que quelque chose m’échappe. Très peu d’applis laissent leurs codes dans
des controllers sous la forme de “module” (/app/controller/arborescence
… ). Certaines se servent du répertoire component, d’autre de library,
d’autre place le maximum dans un engine …

Quelle est à votre avis la meilleure façon de réaliser une application
rails performante et évolutive ???

Merci beaucoup d’éclairer ma lanterne, car trop de lecture tue la
lecture …

Sur ce bonne nuit.

Merci Edouard d’avoir formulé des questions qui trottaient dans la tête
depuis un moment. Merci beaucoup pour ta réponse Jean-François !

Ta proposition de gestion de fichier de langage m’a beaucoup inspiré, et
ton
explication de l’extraction du code m’a fait comprendre pas mal de
rouages.
Surtout cette phrase :

une lib (si c’est pas spécifique à Rails), un plugin (si ça l’est), un
component ou un engine s’il y a une interface qui va avec.

Concernant le fichier de langage, tu dis de le mettre dans
RAILS_ROOT/messages/messages.yml et “Ce fichier sera lu au démarrage”.
Dois-je comprendre que n’importe quel fichier .yml présent dans
l’arborescence d’une appli Rails est chargé au démarrage, où qu’il soit
?

Edouard :

1/

Je voudrais tout simplement placer tout les messages de mes vues
dans des fichiers externes afin de minimiser leurs redondances et de les
modifier rapidement. Je n’ai pas de véritables besoins de traduction.

Ce que tu peux faire, c’est mettre tes messages dans un fichier YAML
(par exemples dans RAILS_ROOT/messages/messages.yml ),
ça ressemblera à :


bonjour: Bonjour
au_revoir: Au revoir

Ce fichier sera lu au démarrage et les messages seront stockés dans un
Hash qui sera converti en un objet HashWithIndifferentAccess constant,
appelons le MESSAGES.

dans lib/

class String
def unspacify!
self.gsub!(/ /, ‘_’)
end
end

class Symbol
def unspacify!
self
end
end

Définir un helper dans application_helper.rb qui ressemblerait à ça :
(m comme message, mais tu es libre de choisir un autre nom de méthode)

def m(msg)
msg.unspacify!
MESSAGES.has_key?(msg) ? MESSAGES[msg] : ‘message non disponible’
end

Dans ta vue, tu pourras alors faire des :

<%=m :bonjour %>
ou
<%=m :au_revoir %>

ou <%=m ‘au revoir’ %>

Après, on peut faire des choses plus compliquées si on veut traiter des
messages comme “Il y a 5 produits.”

dans messages.yml
quantite_produit: Il y a %d produits.

alors on peut modifier m() comme ça par exemple :

def m(msg, *args)
msg.unspacify!
if MESSAGES.has_key?(msg)
if args.empty?
MESSAGES[msg]
else
MESSAGES[msg] % args
end
else
‘message non disponible’
end
end

pour aboutir à <%=m(:quantite_produit, 5) %>

Si on veut être plus rigoureux, il faut traiter les singuliers/pluriels
:slight_smile:
vérifier le bon nombre d’arguments utilisé par message (par
exemple dans MESSAGES, au lieu d’avoir des String comme
valeurs,on pourrait avoir des objets Messages qui auront le
nombre d’arguments et la chaîne comme variable d’instance,
le nbre d’arguments étant déterminé en parsant la chaîne en
recherchant les %s, %d…), etc.

2/

D’autre part j’ai lu pas mal de chose sur les engine. Des avis plutot
divergeant mais avez vous des retours de prod ? Est ce que c’est
complexe à déployer/maintenir ?

Je n’ai jamais été fan des Engines, sans vraiment savoir pourquoi
donc je n’utilise pas trop ce système. Je ne vois pas trop pourquoi
ce serait plus compliqué à maintenir qu’un plugin lambda.
ça rajoute une couche, donc ça rajoute un risque. Par exemple,
lors d’une mise à jour de Rails, ton appli Rails en elle-même ne
marche plus, ou le système d’Engines ne marche plus, ou ton Engine
ne marche plus. Mais si utilises plutôt un plugin, les risques sont :

  • ton appli Rails ne marche plus
  • ton ou tes plugins ne marchent plus.
    t’as aussi des risques, mais t’as une dépendence en moins.

Pour terminer, j’ai une question sur l’architecture d’une appli rails.
Après avoir lu des tutos, étudier des applis rails, je me rends compte
que quelque chose m’échappe. Très peu d’applis laissent leurs codes dans
des controllers sous la forme de “module” (/app/controller/arborescence

Ben si, il y en a.

… ). Certaines se servent du répertoire component,

bah pourquoi pas si on aime les composants.

d’autre de library, d’autre place le maximum dans un engine …

Mais pour moi c’est essentiellement 2 questions, une question
d’extraction, quelles sont les fonctionnalités que je peux extraire
de mon appli qui pourrait être commune à une autre et que je
pourrais donc réutiliser. Par exemple, l’histoire d’affichage de
messages du début, on peut peut-être l’extraire et en faire
une lib (si c’est pas spécifique à Rails), un plugin (si ça l’est),
un component ou un engine s’il y a une interface qui va avec.

La deuxième question est, est-ce que je souhaite extraire
l’interface avec ? Par exemple, supposons que je fasse un wiki
et que je souhaite l’extraire. Soit, j’en fais un component ou un
engine et je fournis l’interface (les vues, le css, le layout) prêt
à l’emploi. Soit j’en fais un plugin, sans trop d’interface mais
avec les fonctionnalités et dans mon autre site web, je construis
l’interface du wiki, soit encore je fournis un générateur qui
fournit les fonctionnalités du wiki et un minimum d’interface
(comme le scaffold, ça permet de voir si ça marche et ensuite
on casse toutes les vues pour coller à notre interface et la
charte graphique). Donc, en gros, soit je fournis une interface,
et le boulot consistera à le faire fondre dans l’interface générale
de mon site web, soit il n’y en a pas ou très peu, et le boulot
consistera à la construire. (quand je parle d’interface, ce n’est
pas seulement une feuille de style et des images, c’est la
structure html, comment les liens hypertexte s’organise, la
présentation des informations… : même si le CSS change,
on repère tout de suite un DotClear, un MediaWiki, un Instiki,
un Dokuwiki, un Trac, un phpBB, un IMP ou un Plone… la question
est de savoir si c’est gênant ou pas)

Quelle est à votre avis la meilleure façon de réaliser une application
rails performante et évolutive ???

En gros et en une phrase : tu peux refactoriser tant que tu souhaites si
tu as
une batterie de tests qui empêche la non-regression de ton appli (et un
système
de contrôle de versions pour revenir en arrière, si tu as fait fausse
route).

Merci beaucoup d’éclairer ma lanterne, car trop de lecture tue la
lecture …

Mais si tu souhaites qu’on te réponde, comment peux-tu faire
sans lire les réponses ??

-- Jean-François.

Jean-François a écrit :

ça ressemblera à :
dans lib/
end
Dans ta vue, tu pourras alors faire des :
dans messages.yml
MESSAGES[msg] % args
vérifier le bon nombre d’arguments utilisé par message (par

complexe à déployer/maintenir ?
t’as aussi des risques, mais t’as une dépendence en moins.
bah pourquoi pas si on aime les composants.

et le boulot consistera à le faire fondre dans l’interface générale

rails performante et évolutive ???

http://lists.rubyonrails.fr/mailman/listinfo/railsfrance

Bonsoir,

merci beaucoup pour ta réponse complète et remplis d’exemples. Un petit
cour qui m’a fait beaucoup de bien. Tout comme Jonathan, ton explication
m’a apporté beaucoup d’éclaircissements sur des sujets qui me
chagrinaient depuis un moment.

Au sujet du point 1 tu expliques comment définir les méthodes dans les
libs. Cependant j’ai deux petites questions à ce sujet :
-Grâce à ces définitions, tu surcharges bien les types String et Symbol ?
-Faut il un nommage particulier et une inclusion particulière pour
prendre en compte ces changements dans le helper que tu as décris ?

Au sujet de l’architecture, tu as énormément clarifié mon problème. Mais
une question nouvelle apparait : comment se crée un component ? (au sens
architecture bien sur ; arborescence, nommage, prise en compte par le
framework, appel dans l’appli …)

Pour la lecture, c’était bien sur le temps passé sur les pages des
forums et ML après le taf. Je me suis éclaté à lire ton mail, çà fait
plais’ des explications claires et concises comme çà.

Bonne soirée à tous

PS : je profite de ce mail pour remercier tous ceux qui m’ont aidé
depuis mes débuts dans rails. Je baigne dans l’open source depuis
maintenant 7 ans et c’est toujours fortement agréable de voir des
communautés comme celle là qui soutiennent tant les nouveaux arrivés.
Bravo à tous !!

Jonathan :

Concernant le fichier de langage, tu dis de le mettre dans
RAILS_ROOT/messages/messages.yml et “Ce fichier sera
lu au démarrage”. Dois-je comprendre que n’importe quel fichier
.yml présent dans l’arborescence d’une appli Rails est chargé
au démarrage, où qu’il soit ?

Non. Je suis passé rapidement sur ce point, parce que j’avais des
choses à écrire derrière :slight_smile: Il faut remplacer “Ce fichier sera lu
au démarrage” par “Il faut faire en sorte que ce fichier soit lu au
démarrage”.

Lire un fichier YAML et stocker le résultat dans une variable
ne nécessite pas 10 000 lignes de code… (merci Ruby) Sans traiter les
éventuels erreurs en cas d’échec, ça s’écrit comme ça :

pas besoin de require ‘yaml’ car Rails le charge déjÃ

MESSAGES = YAML.load(File.read("#{RAILS_ROOT}/messages/messages.yml"))

voire même

MESSAGES = YAML.load_file("#{RAILS_ROOT}/messages/messages.yml")

plus propre :

MESSAGES_DIR = ‘messages’
MESSAGES_FILE = ‘messages.yml’
filename = File.join(RAILS_ROOT, MESSAGES_DIR, MESSAGES_FILE)
MESSAGE = YAML.load_file(filename)

t’enrobes ça dans une méthode de classe ou une fonction de module,
histoire
d’être propre et tu l’appelles dans environment.rb par exemple.

 -- Jean-François.

Trouage de cul, man :wink:

Trop tard pour soumettre à Chad F. pour Rails Recipes, mais il est
encore temps pour soumettre à Dave T. pour AWDWR 2 !

Merci encore d’avoir pris le temps d’expliquer tout ça, et de donner des
exemples concrets !

Jean-François a écrit :

MESSAGES = YAML.load_file("#{RAILS_ROOT}/messages/messages.yml")
d’être propre et tu l’appelles dans environment.rb par exemple.

Merci encore pour tout ces détails. J’ai réussi à mettre en place le
système que tu as détaillé et j’ai compris beaucoup de choses.

Edouard :

Au sujet du point 1 tu expliques comment définir les méthodes dans les
libs. Cependant j’ai deux petites questions à ce sujet :
-Grâce à ces définitions, tu surcharges bien les types String et Symbol ?

  • Il n’y a pas de types en Ruby, il n’y a que des objets et des classes
    (qui sont des objets :slight_smile: ) et les classes ne sont pas des types.

  • En Ruby, les classes sont ouvertes, c’est comme un saloon,
    on rentre, on sort,… il y a pleins de courants d’air. Je peux ajouter
    une méthode, supprimer une existante, rajouter une variable
    d’instance…
    c’est la liberté. Mais c’est un peu comme une tronçonneuse, c’est
    super, on peut tout couper et tout façonner, mais on peut se couper
    les jambes si on n’est pas attentif :slight_smile: Il faut faire attention, car
    par exemple, d’autres personnes aussi peuvent faire leurs modifs :
    dans le cas de Rails, Rails rajoute aussi des choses (via
    ActiveSupport).
    Bien connaître les méthodes déjà existantes dans la classe
    pour éviter de redéfinir sans faire exprès, par mégarde, ça peut
    être source d’erreur.

Si je fais :
class String
def length
0
end
end

Il va y avoir des dégâts !

Ici, il n’y a pas de surcharge ou de redéfinition car String#unspacify
n’existait pas.

Remarque, tu peux réouvrir une classe n’importe où, mais si
tu le fais ça et là , faut partir à la chasse quand tu débuggues
pour retrouver la définition.

J’ai un exemple, dans Rails, tu peux faire ça : ary.blank?
hash.blank? str.blank? etc. Et où sont définis Array#blank?,
Hash#blank?, String.blank? … dans ActiveSupport, certes.
mais si tu regardes dans les fichiers de core_ext/array/
ou core_ext/hash core_ext/string/ ils n’y sont pas. En fait,
les définitions sont regroupées dans core_ext/blank.rb
Donc, soit faut le savoir, soit faut faire des grep à fond :slight_smile:

C’est une des difficultés de Ruby, c’est ce côté jeu de piste…
mais c’est parfois amusant :slight_smile:

Autre exemple :
Dans config/routes.rb, on peut parfois lire ceci :

map.with_options :controller => ‘articles’ do |m|
m.foo ‘:foo/:bar’, :action => ‘foobar’, :foo => /…/, :bar => /…/
m.connect ‘ga/:id’, :action => ‘ga’
m.bu ‘meu/bu’, :action => ‘bu’

end

C’est-à -dire, pour éviter d’avoir à répéter :controller => ‘articles’,
t’utilises #with_options et tu passes un block, c’est DRY, etc.

Cool. J’aimerais bien voir où est défini #with_options, c’est
sûrement une méthode de map quand on fait un
AC::Routing::Routes.draw do |map|, c’est sûrement dans le
code du système des Routes. Et ben non, ça y est pas.
En fait, c’est ActiveSupport qui a défini Object#with_options.
Fallait deviner ! Mais, voyons voir, si c’est défini au niveau
de la classe Object, c’est dispo pour toutes les classes, donc
je peux m’en servir ailleurs… pourquoi pas dans les Migrations ?

Supposons que j’ai un truc comme ça :

def self.up
create_table :bidules do |t|
t.column :machin_id, :integer
t.column :foo, :datetime, :null => false
t.column :bar, :integer, :null => false
t.column :baz :boolean, :null => false

end
end

C’est pas très DRY, tous ces :null => false. Bah, j’ai
une construction assez proche des routes, je peux faire
pareil :

def self.up
create_table :bidules do |t|
t.column :machin_id, :integer

t.with_options(:null => false) do |tab|
  tab.column :foo, :datetime
  tab.column :bar, :integer
  tab.column :baz :boolean
  ...
end

end
end

Et voilà , c’est DRY !
:slight_smile:

-Faut il un nommage particulier et une inclusion particulière
pour prendre en compte ces changements dans le helper que tu
as décris ?

Pas sûr de bien comprendre la question, mais si je mets
mes bidouilles de String#unspacify et Symbol#unspacify
dans lib/misc.rb, je fais un require ‘misc’ dans environment.rb et
ce sera pris en compte.

Au sujet de l’architecture, tu as énormément clarifié mon problème.
Mais une question nouvelle apparait : comment se crée un component ?
(au sens architecture bien sur ; arborescence, nommage, prise en
compte par le framework, appel dans l’appli …)

A dire vrai, je n’utilise pas les composants. Donc une lecture d’AWDwR
1 ou 2 ou de sources comme Typo (qui s’en sert ou s’en servait) sera
plus utile.

-- Jean-François.