Les modèles dans Capcode
J’avais dit que la prochaine fois que je parlerais de Capcode, cela serait pour montrer son utilisation avec Cappuccino… Pardonnez-moi, mais il faudra encore attendre.
En effet, je me suis dit1 que vous parler de l’ajout des modèles était aussi une partie intéressante. En effet, bonne nouvelle, j’ai ajouté la possibilité de créer ses modèles avec couch_foo et DataMapper.
Vous avez pu vous en rendre compte avec l’exemple de blog donné dans la seconde partie de mon article sur la création d’un framework avec Rack, l’utilisation de couch_foo est relativement facile. En effet, il suffit de créer les classes (héritant de CouchFoo::Base) mappant le modèle de donné, d’initialiser la connexion à la base de données via CouchFoo::Base.set_database et le tour est joué. Dans l’exemple de blog, j’avais mis en place une simple classe Story (avec trois propriétés : title, body et date) :
require 'rubygems' require 'couch_foo' ... CouchFoo::Base.set_database(:host => "http://localhost:5984", :database => "my_blog") class Story < CouchFoo::Base property :title, String property :body, String property :date, String end
Par la suite, l’écriture, la lecture la mise à jour ou la suppression de données se font respectivement via les méthodes CouchFoo::Base.create, CouchFoo::Base.find, CouchFoo::Base.update et CouchFoo::Base.delete. Bien entendu, couch_foo supporte les conditions, les associations…
Faisons la même chose avec DataMapper.
La création de la classe Story est sensiblement identique. La différence est que dans le cas de DataMapper, cette classe n’hérite d’aucune classe, mais qu’elle doit inclure le module DataMapper::Resource :
class Story include DataMapper::Resource property :id, Integer, :serial => true property :title, String property :body, String property :date, String end
Autre petite différence que vous aurez notée : la présence de la propriété :id. En effet, avec DataMapper, les tables doivent obligatoirement avoir une clé. Pour cela il faut soit déclarer la propriété de type serial, soit indiquer explicitement que c’est une clé. Dans le premier cas, vous avez deux possibilités :
property :id, Serialou
property :id, Integer, :serial => true
Dans le second cas, voici la méthode d’écriture :
property :name, String, :key => true
La connexion à la base se fait quant à elle en utilisant la méthode DataMapper.setup. Lors de cet appel, nous avons le choix entre utiliser une URL de connexion ou un hachage. Ainsi, les deux méthodes suivantes sont identiques :
DataMapper.setup( :default, "mysql://user:password@localhost:3306/my_database") DataMapper.setup( :default, { :adapter => 'mysql', :database => 'my_database', :username => 'user', :password => 'password', :host => 'localhost', :port => 3306 })
Enfin, l’écriture, la lecture la mise à jour ou la suppression de données se font respectivement via les méthodes DataMapper::Model.new, DataMapper::Model.get!, DataMapper::Resource.update_attributes et DataMapper::Resource.destroy. Là encore, vous pouvez jouer avec les conditions, les associations et autres joyeusetés.
Comme vous avez pu le voir, nous n’avons pas parlé de migration. En fait avec couch_foo, il n’y a nul besoin de faire une quelconque demande pour créer le modèle de données. Cette opération est faite automatiquement lors de l’initialisation de la connexion. Avec DataMapper, il faut faire une demande explicite. Pour cela nous avons le choix entre plusieurs méthodes : DataMapper.auto_migrate! ou DataMapper.auto_upgrade!. Vous l’aurez certainement compris, mais auto_migrate! est destructeur alors qu’auto_upgrade! ne l’est pas.
Sachant tout cela, nous pouvons maintenant ajouter les modèles dans Capcode. Pour cela nous allons créer deux fichiers, l’un pour couch_foo (couchdb.rb) et l’autre pour DataMapper (dm.rb).
# couchdb.rb require 'rubygems' require 'couch_foo' require 'yaml' require 'logger' module Capcode Base = CouchFoo::Base module Resource end class << self def db_connect( dbfile, logfile ) dbconfig = YAML::load(File.open(dbfile)).keys_to_sym Base.set_database(dbconfig) Base.logger = Logger.new(logfile) end end end
# dm.rb require 'rubygems' require 'dm-core' require 'yaml' require 'logger' module Capcode class Base end Resource = DataMapper::Resource class << self def db_connect( dbfile, logfile ) #:nodoc: dbconfig = YAML::load(File.open(dbfile)).keys_to_sym DataMapper.setup(:default, dbconfig) DataMapper::Logger.new(logfile, :debug) DataMapper.auto_upgrade! end end end
Sachant que dans un cas, notre modèle doit hériter d’une classe et dans l’autre il doit inclure un module, nous avons ajouté dans les deux fichiers un module et une classe. Ainsi, la création des modèles peut avoir sensiblement la même syntaxe.
Exemple de modèle créé avec couchdb.rb :
require 'capcode/base/couchdb' class Story < Capcode::Base include Capcode::Resource property :title, String property :body, String property :date, String end
Exemple de modèle créé avec dm.rb :
require 'capcode/base/dm' class Story < Capcode::Base include Capcode::Resource property :id, Integer, :serial => true property :title, String property :body, String property :date, String end
Enfin, afin d’éviter de devoir demander explicitement la création de la base, nous allons modifier la méthode run en y ajoutant le morceau de code suivant :
# Start database if self.methods.include? "db_connect" db_connect( conf[:db_config], conf[:log] ) end
Et ActiveRecord ?
ActiveRecord est un peu plus long à mettre en place. En effet, non seulement il faut créer les modèles, mais en plus, il faut mettre en place les migrations. Avec couch_foo et DataMapper les informations relatives au modèle de données sont directement dans les classes d’accesseur. Avec ActiveRecord, ces informations sont à part. Donc soit vous créez le modèle à la main, soit vous devez créer des migrations. Si nous reprenons les exemples donnés ci-dessus, voici l’enchaînement demandé :
1. Création du script de migration :
# migrate/001_create_stories.rb class CreateStories < ActiveRecord::Migration def self.up create_table :stories do |t| t.string :title t.string :body t.string :date end end def self.down drop_table :stories end end
2. Création d’un Rakefile pour la création du schéma de base :
# Rakefile require 'active_record' require 'yaml' task :default => :migrate desc "Migration de la base via les scripts situés dans migrate. Vous pouvez utiliser VERSION=x" task :migrate => :environment do ActiveRecord::Migrator.migrate('migrate', ENV["VERSION"] ? ENV["VERSION"].to_i : nil ) end task :environment do ActiveRecord::Base.establish_connection( YAML::load(File.open('database.yml')) ) ActiveRecord::Base.logger = Logger.new(File.open('database.log', 'a')) end
3. Lancement de la migration :
$ rake (in /Users/greg/temp/ar) == CreateStories: migrating ================================================== -- create_table(:stories) -> 0.0029s == CreateStories: migrated (0.0037s) =========================================
4. Utilisation :
# test.rb require 'rubygems' require 'active_record' require 'yaml' dbconfig = YAML::load(File.open('database.yml')) ActiveRecord::Base.establish_connection(dbconfig) class Story < ActiveRecord::Base end Story.new( :title => "Hello", :body => "Bonjour le monde", :date => Time.now.to_s ).save Story.find( :all ).each do |s| puts s.title puts s.date puts s.body end
Pour exécuter cet exemple, nous avons également besoin d’un fichier de configuration YAML :
# database.yml adapter: sqlite3 database: base.db
J’ai quelque peu exagéré. Il est tout à fait possible de tout mettre dans un même fichier (migration, Rakefile, utilisation). C’est par exemple ce que fait Camping…
Quoi qu’il en soit, la mise en place des modèles avec ActiveRecord demande un effort légèrement plus important. De plus, couch_foo et bien plus proche de DataMapper qu’ActiveRecord dont il se revendique de copier le style. Enfin, je trouve DataMapper bien plus agréable à utiliser. ActiveRecord n’en reste pas moins un excellent ORM, mais qui ne trouvera pas sa place dans Capcode2.
Tout cela nous amène à une nouvelle version de Capcode.
1 Il ne s’est rien dit du tout ! C’est jusqu’il en a besoin.
2 En tout cas pas avec moi…
Tags: ActiveRecord, Capcode, Cappuccino, couch_foo, DataMapper
