
GILEM maîtrise toutes les phases du process d’industrialisation de logiciel et propose donc dans le domaine des Etudes & Développement dans son sens le plus large , la gamme complète de savoir faire sur tout le spectre de ce process et des métiers de l’informatique
Published: Wednesday, April 16th, 2008
Published: Monday, December 17th, 2007
Object#send revisited
Do you need a way of packing in a single array all the necessary information for invoking a method with its parameters ?
Her’s a nice trick, courtesy of Ruby
send_data = [ :my_method, :param1, :param2, :param3 ] # ../.. obj.send( send_data.shift, send_data )
This should ring a bell with lispers.
Calling send_data.shift yields the symbol corresponding to the method that is to be invoked and what is left in the array are the arguments you want to call the method with.
So, on the receiving end :
def my_method( param1, param2, param3 ) # Erase the budget deficit ;-) end
Seasoned Rubyists may remark about the fact that send_data.shift should be send_data.shift! to be consistent with most of the Ruby standard libray, since the array is modified by the call.
Is is not. We have to live with this, but it is a nice trick all the same isn’t it ?
Published: Wednesday, December 12th, 2007
If it walks like a human and quacks like a human then it must be a human
Just gsub( ‘quacks’, ‘talks’ ) the above an you have summarized the Turing test for the dummies.
Now let us see, trough a practical example, how there is more to duck-typing than initiliazing variable from an inferred value type.
Don’t mess up with Ruby types but you may be be pragmatic about them
People coming from statically compiled languaged may have a difficult time with this :
In Ruby it doesn’t matter as much what type an object belongs to, but how it behaves.
In other words it is often more relevant to ask :
obj.respond_to? :some_message # Yes, methods calls are messages sent to objects
Than to ask :
obj.is_a? SomeClassIt is made even more relevant (and hard to come to terms with by the C++-minded) by the fact that individual objects may dynamically gain or lose attributes and methods during their lifetime.
Let us see in in action.
Fooling attachment_fu into believing its talking with file uploads
If you don’t know attachment_fu plugin go and have a look. This mature plugin is the intelligent laisybone’s tool of choice for uploading images into galleries. A must !
But I happened not to want to upload images the classical way, via file_field. I just wanted to batch the upload of a list of images I had beforehand and then feed them to attachment_fu for usual thumnail creation and storing (it knows how to store it on Amazon S3 for you if you wish !).
Bad luck, just asking attachment_fu to upload an image specified by its URL will not work. It expects the magical object returned by file_field and will have nothing to do with a string.
So this leaves us with two possible courses of action : reverse engineer the plugin and modify it (yerk !) so that it accepts strings or reverse engineer to understand what is produced file_field by an try to mimic it.
Fortunately there remains a third possibility :
Careful examination of the interface between attachment_fu and the app show that it can be reduced to a single method call :
# Feed one source image to attachment_fu image_item.uploaded_data = img
Now have a look at what happens on attachment_fu’s side :
def uploaded_data=(file_data) return nil if file_data.nil? || file_data.size == 0 self.content_type = file_data.content_type self.filename = file_data.original_filename if respond_to?(:filename) if file_data.is_a?(StringIO) file_data.rewind self.temp_data = file_data.read else self.temp_path = file_data.pat end end
From this we deduce that attachment_fu will accept to talk with a StringIO object, provided this object also responds to original_filename (whitch is not a StringIO method).
Getting the StringIO from the URL is easy :
# get pict_url somehow open pict_url do |img| # Do something with img now ... end
First, tell the object about its own filename
open pict_url do |img| img.instance_variable_set(:@uploaded_data, File.basename( pict_url )) end
Now, add the relevant method as a sigleton method and feed the lot to attachment_fu
open pict_url do |img| img.instance_variable_set(:@uploaded_data, File.basename( pict_url )) def img.original_filename @uploaded_data end # Here you are fu, you may invoke original_filename now image_item.uploaded_data = img end
But where did image_item come from, by the way ?
Oh yes, well that’s attachment_fu stuff, but just to reassure you its not dark magick :
class ImageItem < ActiveRecord::Base has_attachment :content_type => :image, :storage => :file_system, :max_size => 1000.kilobytes, :resize_to => '320x200>', :thumbnails => { :thumb => '100x100>' }, :processor => 'ImageScience' validates_as_attachment end
So :
image_item = ImageItem.newWrap up
As we have seen as long as we provide a credible duck to Ruby code, the language will let us cheat our way cleanly out of programming difficuties.
We didn’t have to modify an existing pluging and compromise future compatibility, nor did we have to resort to dirty programming tricks on the application side.
The duck may be a decoy, but it passed the Turing test so everything is OK.
Published: Wednesday, December 12th, 2007
What trade-off for menus usability ?
This paper will revolve around how to help the user navigate by means of simple-to-understand menus whithout ending up with complicated code.
Usability is very much a matter of trial and error. How then do we make it costless to try lots of possibilities ?
First of all, what do I call menus ?
Nothing to do with pull-down menus. Nothing to do, in fact, with any particular kind of menu ; we will be taking about menu content. What we put in it and according to whitch rules and how we specify these rules.
The astute reader has already thought “some kind of DSL”, good guess !
Though, it helps to be able to visualise things. So although we may render these menus in any conceivable way, we may think of them as a two-level menu bar, the like of LinkeIn or Digg.
Let’s call that thing a menu iterator
On one hand I want to be able to write :
<div id="menu"> <ul> <% app_menu do |label, path, title, pict, selected| %> <li><%= link_to label, path, :title => title %></li> <% end %> </ul> </div>
On the other hand I want to specify what to put in my menus for any given context and be done with it. Something like :
controller_menu! do [ { :label => 'Shop' , :title => 'Back to my shop', :path => [ :user_shop_path, :"@current_shop.user", :"@current_shop" ], :pict => '' }, { :only => :show , :label => 'Client complains' , :title => 'Complains registered by this client', :path =>[ :client_complains_path, :@client ], :pict => '' }, { :only => :index , :label => 'Add a client' , :title => 'Add a client, even though he is likely to complain', :path => [ :new_shop_client_path, :current_shop ], :pict => '' }, ] end
And this is exactly what we are going to do :-)
A Restful-friendly two-level menu architecture
Whereas mice_menus could be made to work at any level of nesting we will stick to a two-level approach. This will be expecially convenient for making the plugin Restful-friendly, since (in the author’s opinionated mind) two level of nesting is what works best with Restful (see my first post, er… in French :)
So, following a rather natural path, we will have an application-wide top-level menu with many controller-specific sub-menus. The code distribution follows naturally :
Application-wide top-level menu defined in ApplicationController
Controller-specific sub-menus defined in their respective controllers
class ApplicationController < ActionController::Base # Simplistic main menu : app_menu! do [ { :label => 'Home' , :title => 'Home page' , :path => '/', :pict => '' }, { :status => :unregistered , :label => 'Register' , :title => 'Immediate, free registration to acme.com' , :path => new_user_path, :pict => '' }, # More menu items ] end end
class Shops::CatalogItemsController < ApplicationController controller_menu! do [ { :only => [ :edit, :zoom ], :label => 'Item Notice' , :title => 'See complete notice', :path => [ :shop_catalog_item_path, { :shop_id => :'current_catalog.shop', :catalog_id => :current_catalog , :id => :@catalog_item } ], :pict => '' }, { :only => :index , :label => 'Catalog' , :title => 'Display catalog containing this item', :path => [ :shop_catalog_path, { :shop_id => :'current_catalog.shop', :id => :current_catalog } ], :pict => '' }, { :only => [ :index, :show ] , :label => 'Add an Item' , :title => 'Create new item for this catalog', :path => [ :new_shop_catalog_item_path, { :shop_id => :'current_catalog.shop', :catalog_id => :current_catalog } ], :pict => '' }, { :except => :index , :label => 'Articles' , :title => 'List items of this catalog', :path => [ :shop_catalog_items_path, { :shop_id => :'current_catalog.shop', :catalog_id => :current_catalog } ], :pict => '' }, { :only => :show , :label => 'Modify this Item' , :title => 'Modifier properties of this item', :path => [ :edit_shop_catalog_item_path, { :shop_id => :'current_catalog.shop', :catalog_id => :current_catalog , :id => :@catalog_item } ], :pict => '' }, ] end end
Let’s take a closer look at the syntax
First of all, let us distinguish again between levels :
- Application-wide top-level menu defined by app_menu!
- Controller-specific sub-menus defined by controller_menu!
Notice the “!”. Later on you will use app_menu and controller_menu in _menu.html.erb and _sub_menu.html.erb, whithout the exclamation mark.
app_menu!
First the vanilla, unconditional menu item. Whatever happens you can navigate Home.
{ :label => 'Home', :title => 'Home page', :path => '/', :pict => '' }
No rocket science so far.
{ :status => :unregistered , :label => 'Register' , :title => 'Immediate, free registration to acme.com' , :path => new_user_path, :pict => '' },
Something new here, we’ve thrown in a condition :
Uregistered users will be shown a “Register” menu item. There’s not much else they can do, is there ?
Notice tha we are using a restful path (:path => new_user_path). No problem, whatever new_user_path evaluates to is likely to be OK whatever the context. Thing will get slightly more complicater later on.
controller_menu!
{ :only => :index , :label => 'Catalog' , :title => 'Display catalog containing this item', :path => [ :shop_catalog_path, { :shop_id => :'current_catalog.shop', :id => :current_catalog } ], :pict => '' }
Lots of thing happening here !
By specifying :only => :index we are retricting this menu item to the context of the index action.
That’s not all ! look at the :path. What are all these symbols doing there ?
Well, things are not as simple a above (remember, :path => new_user_path ?). Paths will evaluate differently each time and the actual values are not available when the menu item is being evaluated, so Rails would complain if we did use the shop_catalog_path method for instance (yes it is a method, just in case you hadn’t realized this fact).
Le me stress this fact again (to be honest it cost me some intensive head-scratching at one point), you cannot invoke a named path that takes argument outside a context where these arguments are available.
When the controller is being evaluated (at startup) these values are NOT available.
This calls for a little disgression about Ruby methos calls !
Thanks Ruby, method calls are messages sent to objects
Due to its Smalltalk origins, Ruby considers method calls as messages sent to objets. Experienced Rubyists know that the following experssions are equivalent :
my_object.method_call( arg ) my_object.send( :method_call, arg )
By using the send() approach the premature call problem is solved. The mice_restful code knows what message to sent to the current object at the right time.
What sort of path are supported ?
Paths may be expressed as strings or as arrays. In the latter case, arrays may contain symbols or one symbol followed by a hash.
A string corresponds to a constant path, period.
The first element of an array corresponds to the named path method, the rest (symbols or hash) to the named path’s parameters.
Symbols will be evaluated differently depending whether they are prefixed or not by a “@” (@ will trigger instance_variable_get instead of send).
Let us have a look at a few equivalences. The second line of each pair corresponds to run-time evaluation of methods and variables that are expected to exist (convention) :
[:user_orders, :current_user] user_orders( current_user ) [:edit_user_order, :current_user, @order] edit_user_order( current_user, @order ) [ :edit_shop_catalog_item_path, { :shop_id => :'current_catalog.shop', :catalog_id => :current_catalog , :id => :@catalog_item } ] edit_shop_catalog_item_path( :shop_id => :'current_catalog.shop', :catalog_id => :current_catalog , :id => :@catalog_item )
Notice the :’current_catalog.shop’ symbol. Using quotes around a symbol is perfectly legal. It come quite handy here, since it allows us to store composite expressions.
More on conditional expression
So it is posssible to filter menu items by context. As you may have guessed from above code it is possible to include or exclude one ore several actions :
{ :only => :index , :label => 'Some Label', :title => 'Some title', :path => some_path } { :except => [:new :edit], :label => 'Some Label', :title => 'Some title', :path => some_path }
By the way we are not restricted to CRUD actions :
{ :except => [:beep :run], :label => 'Catalog' , :title => 'Some title', :path => some_path }
The above is OK as long beep and run are actions of this controller.
But there is more to conditionals ! It can be even more dynamic.
{ :if => :test_condition , :label => 'Some Label', :title => 'Some title', :path => some_path } { :unless => :test_other_condition, :label => 'Some Label', :title => 'Some title', :path => some_path }
Of course you have to have somewhere in your controller :
protected def test_condition # some test end def test_other_condition # some test end
On the HTML side
Let’s say you want to implement a digg-like two-levels menu bar :
Your /app/views/layouts/application.html.erb will contain
<%= render :partial => 'layouts/menu' %> <!-- Some (R)HTML --> <% if sub_menu_present %> <%= render :partial => 'layouts/sub_menu' %> <% end %>
Then /app/views/layouts/_menu.html.erb :
<div id="menu"> <ul> <% app_menu do |label, path, title, pict, selected| %> <li><%= link_to label, path, :title => title %></li> <% end %> </ul> </div>
And eventually /app/views/layouts/_menu.html.erb to crown it all :
<div id="sub-menu"> <ul id="nav"> <% controller_menu do |label, path, title, pict, selected| %> <li><%= link_to label, path, :title => title %></li> <% end %> </ul> </div>
Wrap up
There is a little more to mice_menus than described here but it is still a bit tied to a specific application.
mice_menus, along with its companion mice_resful has already been battle-tested sucessfully. Although it has yet to get even a version number it may make the difference between opting for a rigid, semi-hard coded menu system and evolving by cost-free trial and error a very usable contextual menu system.
Last but nor least, mice_menus has been extracted from a pure restful application. Needless to say it is meant to play well the restful game.
Where do I get it ?
the plugin lives here, AS IS of course ;-)
Published: Friday, December 7th, 2007
You live and learn
This cost me about one hour of hair-pulling so I hope this post will help sparing other rubyists’ scalps ;-)
I was trying to play with the amazon-ecs gem both with irb and a proper script.
No luck, in both case all I got was
irb(main):001:0> require 'amazon/ecs'
LoadError: no such file to load -- amazon/ecs
from (irb):1:in `require'
from (irb):1
irb(main):002:0>The solution turned out to be quite simple :
irb(main):002:0> require 'rubygems' => true irb(main):003:0> require 'amazon/ecs' => true irb(main):004:0>
Published: Thursday, December 6th, 2007
(En Français plus bas)
It’s too good to be true
After having wrestled in vain with the Awstats setup (not Awstats’s fault but my own limitations as a server administrator) I decided there MUST exist a Rails Web Statistics plugin.
A brief Googling got me to Sitealizer, bingo !
Installing Sitealizer is dead simple :
script/plugin install http://opensvn.csie.org/sitealizer
Now follow the couple of documented steps and that’s it, you’ve got your stats set up for you :-)
Well, this is true with Rails 1.2, but when you freeze your Rails to edge (2.0) you’re in for some trouble :
When you navigate to http://myapp.com/sitealizer you get an exception, with Rails complaining bitterly about not findinglayouts/sitealizer.html.erb.
No use renaming /vendor/plugins/sitealizer/lib/app/views/layouts/sitealizer.rhtml into sitealizer.html.erb, here is NOT the problem.
The solution lies in the Rails 2.0 Multiple Controller View Paths :
Adapting the example found in New Feature for Rails 2.0: Multiple Controller View Paths, you just need to add to add one line at the bottom of vendor/plugins/sitealizer/init.rb :
ActionController::Base.view_paths.unshift File.join( File.dirname(__FILE__), 'lib/app/views')
Et voilà :-)
C’est trop beau pour être vrai
(Comme l’article est court je peux me permettre de traduire pour mes compatriotes ;-)
Après m’être battu en vain avec le paramétrage de Awstats (Ce qui ne reflête que mes propres limites en tant qu’administrateur d’un serveur) j’ai décidé qu’il DEVAIT exister un plugin Rails pour les statistiques Web.
Quelques secondes sur Google et voilà : Sitealizer, bingo !
L’installation de Sitealizer est on ne peut plus simple :
script/plugin install http://opensvn.csie.org/sitealizer
Puis suivez les deux ou trois étapes très bien documentées et vous avez vos statistiques de fréquentation pour votre appli :-)
Enfin c’est vrai pour Rails 1.2, mais les ennuis commencent si on passe en Rails edge (2.0) :
Si vous naviguez sur http://myapp.com/sitealizer vous avez droit à une exception, Rails se plaignant amèrement de ne pas trouver layouts/sitealizer.html.erb.
Pas la peine de renommer /vendor/plugins/sitealizer/lib/app/views/layouts/sitealizer.rhtml en sitealizer.html.erb, là n’est PAS le problème.
La solution est dans les Multiple Controller View PathsRails 2.0 :
En adaptant l’exemple, New Feature for Rails 2.0: Multiple Controller View Paths, il vous suffit d’ajouter cette ligne en bas de vendor/plugins/sitealizer/init.rb :
ActionController::Base.view_paths.unshift File.join( File.dirname(__FILE__), 'lib/app/views')
Et voilà ! (en Français dans le texte :-)
Published: Monday, December 3rd, 2007
lachaise.org Contact Form
Published: Monday, December 3rd, 2007
Avertissement
Ami lecteur, ne vois point ici un tutoriel Restful. Ce ne sont que des leçons tirées de la pratique, des approches testées (celles qui ont survécu jusqu’à présent), le tout porté par un grand désir de savoir comment les autres Railers abordent Restful.
Préambule
Dira-t-on un jour que Ruby on Rails s’est imposé définitivement grâce au pari de ses créateurs sur Restful ?
Intrigant au début (Simply Restful ?), contraire aux habitudes objet, REST est un des concepts dont la simplicité convainc d’emblée l’intuition avant de dérouter la compréhension, jusqu’à faire douter, avant de s’imposer dans toute sa clarté mais moyennant une sérieuse marche d’approche.
La première phase rappelle la découverte de Rails au travers des scaffolds :
“Fantastique !”, “Amazon.com en deux clics !” l’enthousiasme naïf et puis la découverte pénible que le scaffolding c’est plus utile pour convaincre que pour produire.
L’équation, POST, GET, PUT, DELETE = INSERT, SELECT, UPDATE, DELETE ne peut pas manquer de séduire. On sent bien que c’est un peut trop facile, mais on a trop envie d’y croire.
Surtout que la découverte suivante laisse penser qu’on a touché le gros lot : les chemins nommés!
new_order_path, show_invoice_path( @invoice ), par ici la monnaie ! edit_user_profile_path( current_user, @profile ), c’est plus de la programmation c’est du langage naturel !
Pourtant ça va coincer.
Il reste cependant un peu de pain blanc, les routes imbriquées :
On peut exprimer la relation has_many/belongs_to : c’est une nette valeur ajoutée par rapport au simple CRUD ; mais le malaise point déjà.
Don’t be so CRUD (try and be polite ;-)
Si CRUD peut suffire à représenter le étapes atomiques du destin d’une donnée, has_many/belongs_to ne couvre pas la richesse des relations possibles entre objets du mond réel.
Et même si on se limite aux relations 1-n on va vite se sentir à l’étroit.
Comment, par exemple maintenir la relation entre un article commandé et l’utilisateur qui à passé commande ?
current_user.orders.find(@order).items, on CRUD ça dans quel controlleur ?
Et si on veut naviguer plus profond dans les relations entre le Modèles, doit on imbriquer les controlleurs dans la même mesure ? C’est la tentation naïve, mais l’hyperspécialisation des routes et la combinatoire démentielle qui en découle ainsi que l’éruption de sphagettis dans routes.rb montrent vite que c’est une impasse.
Réponse (contradicteurs bienvenus ;-) deux niveaux, pas plus !
Deux niveaux, pour une application sérieuse ça donnera quand même inévitablement un grand nombre de contrôleurs.
On joue le jeu CRUD n’est-ce pas ? Les actions ad-hoc qu’on s’interdit d’utiliser, il faut en retrouver la fonctionnalité d’une façon quelconque.
On accroît donc le nombre des contrôleurs.
C’est comme le RISC, on réduit le jeu d’instructions, faut compenser autrement.
Au fait on gagnait quoi à s’imposer la discipline CRUD ?
Ah oui, on gagnait, la prédictibilité de l’API REST. C’est toujours ça de gagné et respond_to fera le reste, mais a-t-on toujours besoin d’une API REST ?
Ah oui, et en plus d’être simplement systématique, c’est très structurant. On le sent tout de suite, mais c’est plus dur de dire pourquoi.
Restful est structurant car il impose une règle du jeu très simple, impossible à ne pas comprendre, comme un axiome de base.
Le noeud de cette histoire c’est que cette simplicité vaut la peine des contraintes qu’elle inflige.
Lorsqu’on est parvenu à exprimer la logique d’une application réelle en respectant la règle, sans créer de boursouflures on a construit sur le roc.
Assez de mots, du code
Enfin pas trop de code.
Parce qu’avec la prolifération des contrôleurs ça fait vite des tartines, hautement répétitives qui plus est : index, show, new, create, edit, update, destroy c’est vite fatiguant !
Plusieurs solutions existent, voici la mienne (plugin maison) :
class Shops::CatalogsController < ApplicationController before_filter(:current_shop) restful_methods # Génère tout le CRUD, merci :-) end
Le reste est déduit du nom de la classe. Dans les cas plus compliqué on rajoute des paramètres et c’est Ruby qui bosse.
Le terrain étant ainsi déblayé on peut se concentrer sur l’architecture du code. On va pouvoir compliquer en collant au réel et ne pas s’embourber pour autant.
Donc revenons à la question centrale : puisqu’on a sacrifié des degrés de liberté dans les contrôleurs en adoptant CRUD et en se limitant à deux niveaux d’imbrication, où les récupère-t-on ?
Clairement, il y a besoin d’un artifice. Comment faire pour que cette artifice participe à structurer plutôt que d’être une verrue à la place d’une autre ?
Perspectives variées
(Encore une fois, l’approche exposée est expérimentale. Elle marche pour mon application et résiste bien à chaque difficulté. Elle se consolide de jour en jour et si elle a un talon d’Achille je ne l’ai pas encore perçu).
Partons de la supposition qu’un application se compose de plusieurs perspectives distinctes.
Perspectives, points de vue, contextes, prenez l’expression qui vous plaît le plus.
La perspective la plus évidente est celle de l’utilisateur : Celui qui est loggé :
map.resources :users do |p| p.resources :profiles , :controller => 'users/profiles' p.resources :addresses , :controller => 'users/addresses' p.resources :skills , :controller => 'users/skills' end
Supposons une application de RH simpliste.
Moi, utilisateur de base et candidat à l’emploi, ma perspective est simple et rentre dans un CRUD à deux niveaux, matérialisé par exemple par ce contrôleur :
class Users::SkillsController < ApplicationController restful_methods # Génère tout le CRUD, merci :-) end
Le recruteur, lui, me verra sous une autre perspective et c’est là que ça bascule !
Doit-on réutiliser Users::SkillsController pour présenter mon CV au recruteur comme le réflexe POO y incite ? Ma réponse (opinionated, si vous le permettez ;-) est non.
class Recruiters::SkillsController < ApplicationController restful_methods # Génère tout le CRUD, merci :-) end
Pour les routes ça donnera donc :
map.resources :recruiters do |p| p.resources :skills , :controller => 'recruiters/skills' end
Problème, la relation sous-jacente entre modèles est toujours @user.skills et bien sûr on tombe sur @recruiter.skills généré d’après le nom de la classe !
Qu’à celà ne tienne :
class Recruiters::SkillsController < ApplicationController restful_methods :parent_model => :user end
Mais au fait, en tant que recruteur je n’ai pas à créer des aptitudes à mes candidats, ni à les modifier, je ne fais que les consulter ; du coup CRUD c’est même trop pour mes besoins :
class Recruiters::SkillsController < ApplicationController before_filter(:current_recruiter) # Some magick here ;-) restful_methods :only => [:index,:show], # C'est tout, merci bien. :parent_model => :user end
Voilà.
Ca va changer un peu la formulation des routes nommées
Au lieu de show_user_skill_path(@user, @skill)on devra utiliser un hash en paramètre afin d’indiquer le user :
show_recruiter_skill_path( :user_id => @user, :recruiter_id => @current_recruiter, :id => @skill)
Comme ça on passe toute l’info, @current_recruiter correspond à la perspective courante, celle du recruteur et @user est le candidat propriétaire de @skill
On on a évité ainsi une horreur :
map.resources :recruiters do |p| p.resources :user do |u| u.resources :skills, :controller => 'recruiters/users/skills' end end
En résumé :
En évitant la prolifération de niveaux d’imbrication des routes grace à l’artifice des perspectives (ou contextes ou un meilleur nom…) que la sobriété CRUD était très structurante sans être un carcan.
Allons plus loin
On s’est limité pour le moment aux relations 1-n ; celà marche-t-il pour du n-n ?
Supposons une chaîne de magasins, qui possède donc plusieurs magasins et (on lui souhaite) plusieurs clients qui ne vont pas toujours dans le même magasin :
class User < ActiveRecord::Base # Shop/clients relationships has_many :clientships has_many :providers, :through => :clientships, :source => :shop end
class Shop < ActiveRecord::Base # Shop/clients relationships has_many :clientships has_many :clients, :through => :clientships, :source => :user end
Et, bien sûr au milieu :
class Clientship < ActiveRecord::Base belongs_to :user belongs_to :shop end
Dans la perspective du boutiquier je contemple mes clients (qui sont néanmoins des users) :
class Shops::ClientsController < ApplicationController before_filter(:current_shop) # Génère le CRUD en prenant en compte le has_many => :thtough restful_methods :through => [ :clientship, :shop, :client ] end
Dans la perspective du client je vois mes fournisseurs habituels :
class Users::ProvidersController < ApplicationController # Pas besoin de générer current_user, AAA l'a déjà fait restful_methods :except => :current_parent :through => [ :clientship, :user, :provider ] end
Mais comment est-ce possible d’exprimer du n-n de la même façon que du 1-n ?
Simple, on ne considère que current_user ou current_shop respectivement ; la relations est alors bien 1-n.
Et vu de l’extérieur - un client REST, par exemple - on a pas besoin de connaitre les subtilités du many-to-many interne : un magasin à n clients, un client a n fournisseurs, ça suffit.
Ce qui justifie que les routes et les contrôleurs prennent des libertés par rapport au modèle sous-jacent.
Conclusion provisoire
Parce que définitive c’est prématuré. Tout ça est encore frais.
Il parait clair, cependant, que la contrainte REST/CRUD en forçant à aller à l’essentiel force à concevoir des architectures d’applications sobres et robustes.
Ce en sera pas la première fois que de grandes créations sont issues de contraintes acceptées.
So you want to write a fugue ? … ;-)
Le plugin est disponible ici, AS IS of course ;-)




Recent Entries


