Microsoft's Internet Explorer browser has no built-in vector graphics machinery required for "loss-free" gradient background themes.
Please upgrade to a better browser such as Firefox, Opera, Safari or others with built-in vector graphics machinery and much more. (Learn more or post questions or comments at the Slide Show (S9) project site. Thanks!)
ActiveRecord è prima di tutto il nome di un design pattern identificato e battezzato da Martin Fowler nel libro Patterns of Enterprise Application Architecture
Questo design pattern consente di collegare alcuni dei costrutti base della OOP (le Classi e gli Oggetti) alle tabelle e ai dati contenuti in un database relazionale.
Le librerie/framework che implementano tale pattern si occuperanno di generare ed eseguire il codice SQL corrispondente alle operazioni da effettuare.
Tra i vantaggi, oltre alla migliore integrazione con il resto di un’applicazione sviluppata in OOP, vi è in genere l’indipendenza dal particolare DBMS (MySQL, PostgreSQL, SQLite, Oracle, SQLServer etc.)
Nel framework Ruby on Rails ActiveRecord è il nome dell’ORM che implementa (ovviamente in Ruby) il pattern di Martin Fowler.
Oltre a quanto detto riguardo al design pattern (che è stato implementato in molti altri framework e in molti altri linguaggi) l’implementazione Ruby aggiunge le seguenti particolarità:
Possiamo far uso dei generator per generare i file necessari ad implementare e testare i nostri modelli:
$ ruby script/generate model News exists app/models/ exists test/unit/ exists test/fixtures/ create app/models/news.rb create test/unit/news_test.rb create test/fixtures/news.yml create db/migrate create db/migrate/20090416195202_create_news.rb
Anche i modelli seguono delle convenzioni in RoR:
NOTA: nel caso in cui si stiano utilizzando database già esistenti, che non rispettano quindi le convenzioni di RoR, sarà possibile specificare il nome della tabella in maniera esplicita nel modello:
class MyModel set_table_name "my_table_name" end
A questo punto ci sarebbe da chiedersi… come facciamo a sapere come si chiamerà la tabella relativa al modello che stiamo creando?
$ ruby script/console Loading development environment (Rails 2.3.2) >> puts "News".tableize news => nil >> puts "Person".tableize people => nil >> puts "MyPerson".tableize my_people => nil
La classe Inflector è quella che si occupa di definire le regole di traduzione/pluralizzazione ed è possibile configurarne il comportamento modificando il file config/initializers/inflections.rb
NOTA: è sconsigliato abusarne… non cercate di localizzare in italiano i modelli e le regole di pluralizzazione se non volete impazzire.
I file nella directory db/migrate descrivono i cambiamenti incrementali da eseguire sul database:
class CreateNews < ActiveRecord::Migration def self.up create_table :news do |t| t.string :title t.string :author t.text :body t.timestamps end end def self.down drop_table :news end end
$ rake db:migrate (in /home/rpl/Projects/ALCA/MasterOpenSource/Rails/demo) == CreateNews: migrating ===================================================== -- create_table(:news) -> 0.0981s == CreateNews: migrated (0.0985s) ============================================
In questo modo verranno eseguiti i metodi self.up di tutte le migration nell’ordine in cui sono state create.
$ ruby script/dbconsole SQLite version 3.5.9 Enter ".help" for instructions sqlite> .schema CREATE TABLE "news" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "title" varchar(255), "author" varchar(255), "body" text, "created_at" datetime, "updated_at" datetime); CREATE TABLE "schema_migrations" ("version" varchar(255) NOT NULL); CREATE UNIQUE INDEX "unique_schema_migrations" ON "schema_migrations" ("version"); sqlite> select * from schema_migrations; 20090414195202 sqlite>
NOTA: la console eseguita da script/dbconsole cambia a seconda della configurazione contenuta nel file config/database.yml.
Modificando una migration esistente è necessario eseguire una migrazione inversa (che esegue i metodi self.down delle migration) e rieseguire la migrazione:
$ rake db:rollback (in /home/rpl/Projects/ALCA/MasterOpenSource/Rails/demo_tmp) == CreateNews: reverting ===================================================== -- drop_table(:news) -> 0.0516s == CreateNews: reverted (0.0519s) ============================================ $ rake db:migrate ...
NOTA: E’ buona educazione modificare solo migration di cui non si sia ancora effettuato un commit/rilascio ufficiale.
$ ruby script/console Loading development environment (Rails 2.3.2) >> News => News(id: integer, title: string, author: string, body: text, created_at: datetime, updated_at: datetime) >> News.find :all => [] >> news1 = News.new => #<News id: nil, title: nil, author: nil, body: nil, created_at: nil, updated_at: nil> >> news1.title = "prima news con rails" => "prima news con rails" >> news1.author = "Luca Greco" => "Luca Greco" >> news1.body = "blah blah blah blah" => "blah blah blah blah" >> news1.save => true
>> news_ary = News.all => [#<News id: 1, title: "prima news con rails", author: "Luca Greco", body: "blah blah blah blah", created_at: "2009-04-16 20:42:04", updated_at: "2009-04-16 20:42:04">] >> news_ary[0].author => "Luca Greco" >> News.all.each do |news| ?> puts "#{news.title} (#{news.author}): #{news.body}" >> end prima news con rails (Luca Greco): blah blah blah blah => [#<News id: 1, title: "prima news con rails", author: "Luca Greco", body: "blah blah blah blah", created_at: "2009-04-16 20:42:04", updated_at: "2009-04-16 20:42:04">]
Per una panoramica delle opzioni di ActiveRecord::Base.find:
http://api.rubyonrails.org/classes/ActiveRecord/Base.html#M002208
JUST USE IT
class SayController < ApplicationController def xml @news = News.find :all render :xml => @news end def json @news = News.find :all render :json => @news end end
class News < ActiveRecord::Base validates_presence_of :title, :author, :body end
$ ruby script/console Loading development environment (Rails 2.3.2) >> a_news = News.new => #<News id: nil, title: nil, author: nil, body: nil, created_at: nil, updated_at: nil> >> a_news.save => false >> a_news.errors => #<ActiveRecord::Errors:0xb6d990c0 @errors={"body"=>["can't be blank"], "author"=>["can't be blank"], "title"=>["can't be blank"]}, @base=#<News id: nil, title: nil, author: nil, body: nil, created_at: nil, updated_at: nil>> >> a_news.errors.full_messages => ["Body can't be blank", "Author can't be blank", "Title can't be blank"] >>
Per avere un quadro delle validazioni di alto livello già disponibili ci si può riferire all’apidoc:
Quando diventa necessario personalizzare il processo di validazione di un modello è possibile passare all’approccio più completo descritto al seguente link:
http://api.rubyonrails.org/classes/ActiveRecord/Validations.html
class Person < ActiveRecord::Base protected def validate errors.add_on_empty %w( first_name last_name ) errors.add("phone_number", "has invalid format") unless phone_number =~ /[0-9]*/ end def validate_on_create # is only run the first time a new object is saved unless valid_discount?(membership_discount) errors.add("membership_discount", "has expired") end end def validate_on_update errors.add_to_base("No changes have occurred") if unchanged_attributes? end end
Creare un nuovo modello da mettere in relazione con il precedente:
$ ruby script/generate model Author exists app/models/ exists test/unit/ exists test/fixtures/ create app/models/author.rb create test/unit/author_test.rb create test/fixtures/authors.yml exists db/migrate create db/migrate/20090414214924_create_authors.rb
class CreateAuthors < ActiveRecord::Migration def self.up create_table :authors do |t| t.string :name t.string :surname t.timestamps end change_table :news do |t| t.remove :author t.integer :author_id end end def self.down drop_table :authors change_table :news do |t| t.remove :author_id t.string :author end end end
$ rake db:migrate (in /home/rpl/Projects/ALCA/MasterOpenSource/Rails/demo) == CreateAuthors: migrating ================================================== -- create_table(:authors) -> 0.0078s -- change_table(:news) -> 0.1084s == CreateAuthors: migrated (0.1166s) =========================================
class News < ActiveRecord::Base validates_presence_of :title, :author, :body belongs_to :author end
class Author < ActiveRecord::Base has_many :news end
$ ruby script/console Loading development environment (Rails 2.3.2) >> auth1 = Author.new :name => "Luca", :surname => "Greco" => #<Author id: nil, name: "Luca", surname: "Greco", created_at: nil, updated_at: nil> >> auth1.save => true >> news1 = News.new :title => "test news", :author => auth1, :body => "news content" => #<News id: nil, title: "test news", body: "news content", created_at: nil, updated_at: nil, author_id: 1> >> news1.save => true >> auth1.news => [#<News id: 1, title: "test news", body: "news content", created_at: "2009-04-16 22:18:32", updated_at: "2009-04-16 22:18:32", author_id: 1>] >> news1.author => #<Author id: 1, name: "Luca", surname: "Greco", created_at: "2009-04-16 22:17:58", updated_at: "2009-04-16 22:17:58"> >> news1.author.name => "Luca" >>
require 'test_helper' class NewsTest < ActiveSupport::TestCase test "should not save news without title, author and body" do news = News.new assert !news.save end test "news without title should not be valid" do assert !news(:news_without_title).valid? end end
require 'test_helper' class AuthorTest < ActiveSupport::TestCase test "should not save author without name and surname" do author = Author.new assert !author.save end end
news_without_title: title: nil author: 1 body: "test content"
Le fixture non sono altro che dati (in formato YAML o CSV) pronti per l’uso nei test.
NOTA:
$ rake test:units (in /home/rpl/Projects/ALCA/MasterOpenSource/Rails/demo_tmp) /usr/bin/ruby1.8 -Ilib:test "/usr/lib/ruby/gems/1.8/gems/rake-0.8.3/lib/rake/rake_test_loader.rb" "test/unit/helpers/say_helper_test.rb" "test/unit/author_test.rb" "test/unit/news_test.rb" Loaded suite /usr/lib/ruby/gems/1.8/gems/rake-0.8.3/lib/rake/rake_test_loader Started F.. Finished in 0.355051 seconds. 1) Failure: test_should_not_save_author_without_name_and_surname(AuthorTest) [/test/unit/author_test.rb:6]: <false> is not true. 3 tests, 3 assertions, 1 failures, 0 errors rake aborted! Command failed with status (1): [/usr/bin/ruby1.8 -Ilib:test "/usr/lib/ruby...] (See full trace by running task with --trace)
Mentre si sviluppa un particolare testcase è utile poter eseguire un solo test file anzichè tutti i testunit:
$ cd test $ ruby unit/news_test.rb Loaded suite unit/news_test Started .. Finished in 0.326485 seconds. 2 tests, 2 assertions, 0 failures, 0 errors
Rails Guides (http://guides.rubyonrails.org)