su - postgres postgres@albrecht:~$ createuser rails #Non Le nouveau rôle est-il super-utilisateur ? (o/n) n #Oui Le nouveau rôle est-il autorisé à créer des bases de données ? (o/n) o #Non Le nouveau rôle est-il autorisé à créer de nouveaux rôles ? (o/n) n postgres@albrecht:~$ alter user rails with password '<mot_de_passe>'ou :
su - postgres createuser -U postgres -W -P rails
cd <projet_rails> rake db:createCréation de toutes les B.D.D. (Dev., Test et Prod°) :
rake db:create:all
rake db:drop
rake db:reset=> aucune donnée non insérée par les migrations ne survit
#Commande est la classe du modèle commande @commande = Commande.find_by_id(19719) #@commande est une instance de cette classe en mémoire (un objet Ruby), #qui contient les valeurs (trouvées dans la B.D.D.) de la commande sélectionnée if @commande.prix_ttc.blank? @commande.prix_ttc = @commande.prix_ht * 1.196 end @commande.save
config.active_record.schema_format
db/${RAILS_ENV}_structure.sql
db/schema.rbMais ce type de schema ne peut pas exprimer les éléments spécifiques aux B.D.D. :
gem install annotate-models cd <projet> annotate annotate-models #will wipe out the comment block from the last run including all comments you add to the block
rake db:migrate --trace VERSION=<SSAAMMJJHHMMSS>Défait la(les) dernière(es) migrations
rake db:rollback STEP=<nb de migrations en arrière>Défait puis refait la(les) dernière(es) migration(s)
rake db:migrate:redo STEP=<3>Exécute le up ou down d'une migration particulière
rake db:migrate:{up, down} VERSION=<timestamp>
change_table :visites do |t| #ajout d'une colonne t.integer :duree end
change_table :<table> do |t| t.column t.index t.timestamps t.change t.change_default t.rename t.references t.belongs_to t.string t.text t.integer t.float t.decimal t.datetime t.timestamp t.time t.date t.binary t.boolean t.remove t.remove_references t.remove_belongs_to t.remove_index t.remove_timestamps end
:id => false
:primary_key
:options => "ENGINE=BLACKHOLE"defaut :
:options => "ENGINE=InnoDB"
drop_table :<nom_table>
add_column :products, :part_number, :string #ou avec la syntaxe 'sexy' t.<type> <nom_colonne> #soit dans l'exemple def self.up create_table :products do |t| t.string :part_number end end
change_table :nom_table do |t| t.change :nom_colonne, :type_colonne, {options} end
rename_column :<table> :<ancien_nom>, :<nouv_nom> #ou avec la syntaxe 'sexy' t.rename :<ancien_nom>, :<nouv_nom>
remove_column :<table> :<nom_col>, :<nom_col>, ... #ou avec la syntaxe 'sexy' t.remove :<nom_col>, :<nom_col>, ...
:null => false :default => 0
add_column :visites, :id, :primary_key
t.column :name, 'polygon', :null => false
add_index :accounts, [:branch_id, :party_id], :unique => true, :name => 'by_branch_party' t.index :<nom_index>
remove_index :<nom_indexe>
http://agilewebdevelopment.com/plugins/foreign_key_associations http://agilewebdevelopment.com/plugins/foreign_key_migrations
t.references :<modele>
#Cette classe utilise Employe ou Produit de façon polymorphique class Photo < ActiveRecord::Base belongs_to :imageable, :polymorphic => true end class Employe < ActiveRecord::Base has_many :photos, :as => :imageable end class Produit < ActiveRecord::Base has_many :photos, :as => :imageable endDans cet exemple, l'instruction :
t.references :imageable, :polymorphic => {:default => 'Photo'}Ajoutera un colonne imageable_id et une colonne de type string imageable_type avec une valeur par défaut valant : 'Photo'.
@picture.imageable @product.pictures
execute <<-SQL ALTER TABLE products ADD CONSTRAINT fk_products_categories FOREIGN KEY (category_id) REFERENCES categories(id) SQL
execute "ALTER TABLE products DROP FOREIGN KEY fk_products_categories"
raise ActiveRecord::IrreversibleMigration, "<message_explicatif>"
<Classe_modele>.reset_column_informationCela permet par exemple d'initialiser un colonne à partir de calculs, juste après son ajout à une table.
script/generate model <NomModel>Ont un nom de la forme :
SSAAMMJJHHMMSS_create_nom_model.rb
script/generate migrationSi le nom de la migration est de la forme :
"AddXXXToYYY"ou
"RemoveXXXFromYYY"et est suivi d'une liste de noms de colonnes avec leur type,
script/generate migration AddDetailsToProducts part_number:string price:decimal script/generate migration AddPositionToDiapositives position:integer
http://ar.rubyonrails.org/classes/Fixtures.html
test/fixturesATTENTION : ils ne sont pas forcément utilisés que pour les tests!
# 1) Charge une garniture depuis un fichier Ruby : require 'active_record/fixtures' Fixtures.create_fixtures("#{Rails.root}/test/fixtures", "operating_systems") Fixtures.create_fixtures("#{Rails.root}/test/fixtures", "users") # 2) Charge un fichier de garnitures dans une migration avec up and down : #db/migrate/<fichier_de_migration>.rb require 'active_record/fixtures' class LoadCustomerData def self.up down directory = File.join(File.dirname(__FILE__), "data") Fixtures.create_fixtures(directory, "customers") end def self.down Customer.delete_all end end
gem install fastercsv #ou rake gem:install fastercsv
#config/environment.rb : config.gem 'fastercsv' #dans le fichier de migration du projet : FasterCSV.foreach("#{RAILS_ROOT}/fichier.csv") do |ligne| titre = ligne[0] next if titre == "Title" end
db/seeds.rbUtilisation :
rake db:seedExemples :
#db/migrate/<fichier_de_migration>.rb # Chargement du fichier de seed depuis une migration Rake::Task["db:seed"].invoke
# db/seeds.rb # 1) Charge un fichier texte depuis le fichier de Seed require 'open-uri' require 'active_record/fixtures' Country.delete_all open("http://openconcept.ca/sites/openconcept.ca/files/country_code_drupal_0.txt") do |countries| countries.read.each_line do |country| code, name = country.chomp.split("|") Country.create!(:name => name, :code => code) end end # 2) génère automatiquement un grand nombre d'enregistrement Debut=11 Fin=2000 puts "création de #{Fin - Debut + 1} photos, à partir de #{Debut}" (Debut..Fin).each { |i| if Photo.find_by_nom("photo_bis_#{i}").nil? puts "création de photo_bis_#{i}" Photo.create( :nom => "photo_bis_#{i}", :commentaire => "commentaire photo bis #{i}" ) else puts "photo_bis_#{i} existe déjà" end }
# lib/tasks/dump_load.rake namespace :db do namespace :seed do require 'db/seed_tables' desc "décharge les tables contenant les données initiales dans db/<RAILS_ENV>_seed.sql. La variable SEED_TABLES doit-être définie dans db/seed_tables.rb!!!" task :dump => :environment do config = ActiveRecord::Base.configurations[RAILS_ENV] dump_cmd = "mysqldump --user=#{config['username']} --password=#{config['password']} #{config['database']} #{SEED_TABLES.join(" ")} > db/#{RAILS_ENV}_seed.sql" system(dump_cmd) end desc "charge les données initiales depuis db/<RAILS_ENV>_seed.sql dans la base courante" task :load => :environment do config = ActiveRecord::Base.configurations['test'] system("mysql --user=#{config['username']} --password=#{config['password']} #{config['database']} < db/#{RAILS_ENV}_seed.sql") end end end
# lib/tasks/fixtures.rake namespace :db do namespace :fixtures do desc 'Personnalized : Create YAML test fixtures from data in an existing database. Defaults to development database. Set RAILS_ENV to override.' task :dump => :environment do puts "cette tâche apparaîtra dans (rake --tasks) du projet" sql = "SELECT * FROM %s" skip_tables = ["schema_migrations"] ActiveRecord::Base.establish_connection(:development) (ActiveRecord::Base.connection.tables - skip_tables).each do |table_name| i = "000" j = 0 File.open("#{RAILS_ROOT}/test/fixtures/#{table_name}.yml", 'w') do |file| data = ActiveRecord::Base.connection.select_all(sql % table_name) file.write data.inject({}) { |hash, record| j += 1 hash["#{table_name.singularize}_#{i.succ!}"] = record hash }.to_yaml puts "#{table_name} : [#{j}]" end end end end end
RAILS_ENV=development rake db:fixtures:dump RAILS_ENV=production rake db:fixtures:load
#Dans une méthode contrôleur : ActiveRecord::Base.record_timestamps = false #temporarily turn off magic column updates @page.update_attribute(:position, pos) ActiveRecord::Base.record_timestamps = true #turning updates back on
#rails console _sql = "alter table categories drop column parent_id" ActiveRecord::Base.connection.execute(_sql)
<controlleur>/new
<controlleur>/<id>
<controlleur>/<id>/edit <input type="hidden" name="_method" value="put" />
<a href="<controller>/<id>" onclick="if (confirm('Es-tu sûr(e)?')) { var f = document.createElement('form'); f.style.display = 'none'; this.parentNode.appendChild(f); f.method = 'POST'; f.action = this.href;var m = document.createElement('input'); m.setAttribute('type', 'hidden'); m.setAttribute('name', '_method'); m.setAttribute('value', 'delete'); f.appendChild(m);var s = document.createElement('input'); s.setAttribute('type', 'hidden'); s.setAttribute('name', 'authenticity_token'); s.setAttribute('value', 'arpS61rIZTta3ZSiNmmeO1G0NUb30szy0+7Qx9OQIHw='); f.appendChild(s);f.submit(); };return false;">Supprimer</a> <a href="<controller>/<id>" data-confirm="Are you sure?" data-method="delete" rel="nofollow">Supprimer</a> <input type="hidden" name="_method" value="delete" />
#exemple : customer.orders(true).empty?
<classe_modele>.find_by_<nom_attribut>("<valeur_attribut>")
<classe_modele>.all
<sequence_de_modeles>.order(:<tri_selon_cet_attribut_>)Si l'on veut un tri sur un attribut virtuel (défini par une méthode dans le modèle) :
# nom_long est une méthode dynamique (à créer) du modèle (pas un attribut de la B.D.D.) # notez le ! à la fin du sort, qui implique que la séquence elle-même est modifiée : @all_categories.sort! { |a,b| a.nom_long <=> b.nom_long }
Reservation.find_all_by_date(Date.today, :include => [:evenement, :sessions])Attention : Les noms des relations en jointure doivent respecter le singulier ou pluriel du type de relation (C.f. le modèle).
Reservation.find_all_by_date(Date.today, :include => [{:evenement => :organisateur}, :sessions ])Où : Un organisateur organise des évènements, qui ont plusieurs réservations, et elle-mêmes plusieurs sessions.
@utilisateurs = Utilisateur.find( :all, :include => { :communautes => { :localisation => {} } , :conditions => ["localisations.ville = ?", ville] } )
#Filtre sur la valeur d'une colonne : named_scope :school_names, :conditions => {:requirement_type => Type::SCHOOL_NAMES} named_scope :active, :conditions => {:active => true} #Filtre avec bloc : named_scope :recent, lambda { { :conditions => ['created_at > ?', 1.week.ago] } } #Utilisation, on peut les utiliser en cascade : User.active.recentExemple complet :
class Article named_scope :published_at_like, lambda {|date_at| { :conditions => ['published_at LIKE ? ', "%#{date_at}%"] }} named_scope :user_id, lambda {|user_id| { :conditions => {:user_id => user_id} } named_scope :published, { :conditions => {:published => true} } named_scope :not_published, { :conditions => { :published => false} } named_scope :category, lambda {|category_id| { :conditions => ['categorizations.category_id = ?', category_id], :include => 'categorizations' }} named_scope :draft, { :conditions => {:state => :draft} } named_scope :no_draft, {:conditions => ['state <> ?', 'draft'], :order => 'created_at DESC'} named_scope :searchstring, lambda { |search_string| tokens = search_string.split.collect {|c| "%#{c.downcase}%"} { :conditions => [(['(LOWER(body) LIKE ? OR LOWER(extended) LIKE ? OR LOWER(title) LIKE ?)']*tokens.size).join(' AND '), *tokens.collect{ |token| [token] * 3 }.flatten] } } def self.search_no_draft_paginate(search_hash, paginate_hash) list_function = ["Article.no_draft"] if search_hash.nil? search_hash = {} end if search_hash[:searchstring] list_function << 'searchstring(search_hash[:searchstring])' end if search_hash[:published_at] and %r{(\d\d\d\d)-(\d\d)} =~ search_hash[:published_at] list_function << 'published_at_like(search_hash[:published_at])' end if search_hash[:user_id] && search_hash[:user_id].to_i > 0 list_function << 'user_id(search_hash[:user_id])' end if search_hash[:published] list_function << 'published' if search_hash[:published].to_s == '1' list_function << 'not_published' if search_hash[:published].to_s == '0' end if search_hash[:category] and search_hash[:category].to_i > 0 list_function << 'category(search_hash[:category])' end paginate_hash[:order] = 'created_at DESC' list_function << "paginate(paginate_hash)" eval(list_function.join('.')) end endPour savoir quelles sont les conditions résultantes d'une cascade de named scopes, utilisez la méthode scope(:find) :
#Appliqué à l'exemple ci-dessus : Article.user_id.category.published.scope(:find)
>> p = Post.find(:first) => ... >> p.title = "Another Title" => "Another Title" >> p.changed? => true >> p.changed => ["title"] >> p.changes => {"title"=>["New Title", "Another Title"]} >> p.title_changed? => true >> p.title_was => "New Title" >> p.title_change => ["New Title", "Another Title"] >> p.title_will_change! => "Another Title"
has_many :<table_etrangere>, :through => :<table_intermediaire>
#models/employee.rb class Employee < ActiveRecord::Base has_many :subordinates, :class_name => 'Employee', :foreign_key => 'manager_id' belongs_to :manager, :class_name => 'Employee' end #controllers/employee_controller.rb @employee.subordinates @employee.manager
#models/produit.rb class Produit < ActiveRecord::Base has_and_belongs_to_many :categories end #models/categorie.rb #Attention aux inflexions sur cette classe !!! class Categorie < ActiveRecord::Base has_and_belongs_to_many :produits end #controllers/produit_controller.rb @produit.categories #controllers/categorie_controller.rb @categorie.produits
#exemple, models/produit.rb : has_and_belongs_to_many :categories, :join_table => "prods_cats"
belongs_to :follower, :class_name => "Membre", :foreign_key => "membres_follower"
module MyApplication module Business class Supplier < ActiveRecord::Base has_one :account, :class_name => "MyApplication::Billing::Account" end end module Billing class Account < ActiveRecord::Base belongs_to :supplier, :class_name => "MyApplication::Business::Supplier" end end end
class Project has_and_belongs_to_many :developers, :after_add => :evaluate_velocity def evaluate_velocity(developer) ... end end
http://m.onkey.org/2010/1/22/active-record-query-interface
active_support/inflections.rb
#config/initializers/inflections.rb inflect.plural(<sing>, <plur>) inflect.singular(<plur>, <sing>) #en anglais category => categories inflect.irregular 'categorie', 'categories' #=> génère dans la liste des inflexions : (?i-mx:(c)ategorie$)\1ategories #Les instructions suivantes ne marchent pas : une règle générale prends le pas #inflect.singular 'categories', 'categorie' #inflect.plural 'categorie', 'categories'
'categories'.singularize
ActiveRecord::Base.connection.reset_pk_sequence!('<nom_table>')