ActiveRecord教程
(一、ActiveRecord基礎)
ActiveRecord是Rails提供的一個對象關系映射(ORM)層,從這篇開始,我們來了解Active Record的一些基礎內容,連接數據庫,映射表,訪問數據等。
Active Record使用基本的ORM模式:表映射成類,行映射成為對象,列映射成對象的屬性。與很多大量使用配置的ORM庫不同,Active Record最小化了配置。想象一下,有一個使用Active Record的程序把Mysql數據庫中的orders表轉換到類,通過制定的ID查找到order,設定order的名稱,然后保存回數據庫:
require "rubygems"
require_gem "activerecord"
ActiveRecord::Base.establish_connection(:adapter => "mysql",
:host => "localhost", :database => "railsdb")
class Order < ActiveRecord::Base
end
order = Order.find(123)
order.name = "Dave Thomas"
order.save
在上面的例子里不需要任何配置,Active Record為我們做了這些事情,下面我們來看看ActiveRecord是怎樣工作的。
表和類
當你創建了一個ActiveRecord::Base類的子類,Active Record假定表名是復數的,而類名是單數的,當類名包括多個單詞時,表名被假定為單詞間帶有下劃線,復數形式不規則,例如:
類名 表名 類名 表名
Order orders LineItem line_items
TaxAgency tax_agencies Person people
Diagnosis diagnoses Quantity quantities
Batch batches Datum data
默認的,Active Record的表名是復數的,類名是單數的,如果你不太習慣,可以通過設置一個全局標記來禁用它,在config目錄的environment.rb文件中設置:
ActiveRecord::Base.pluralize_table_names = false
單復數規則可以對付大部分情況,對于一些特殊情況,Active Record允許我們覆蓋默認的生成的表名,使用set_table_name命令,例如:
class Sheep < ActiveRecord::Base
set_table_name "sheep" # Not "sheeps"
end
class Order < ActiveRecord::Base
set_table_name "ord_rev99_x" # Wrap a legacy table...
end
(二、列和屬性)
ActiveRecord中的一個對象相當于數據庫中表的一行,對象的屬性對應于表的列,也許你會注意到我們的Order類沒有提及關于 orders表的任何東西,這是因為ActiveRecord在運行時來確定這些對應關系,Active Record將數據庫中的模式反應到類中。
我們的orders表可能使用下面的sql來創建:
create table orders (
id int not null auto_increment,
name varchar(100) not null,
email varchar(255) not null,
address text not null,
pay_type char(10) not null,
shipped_at datetime null,
primary key (id)
);
我們可以創建一個類來轉換這個表:
require 'rubygems'
require_gem 'activerecord'
# Connection code omitted...
class Order < ActiveRecord::Base
end
當我們創建了Order類,就可以訪問它的屬性來獲取信息,下面的代碼使用columns()方法,來返回一個Columns對象的數組,在這里,我們顯示了orders表中的每個列,并且顯示指定字段的詳細信息。
require 'pp'
pp Order.columns.map { |col| col.name }
pp Order.columns_hash['shipped_at']
運行代碼,會得到下面的輸出:
["id", "name", "email", "address", "pay_type", "shipped_at"]
#<ActiveRecord::ConnectionAdapters::Column:0x10e4a50
@default=nil,
@limit=nil,
@name="shipped_at",
@type=:datetime>
注意,Active Record決定了每個列的類型,在這個例子里,將shipped_at列作為datetime類型,該列的值被保存在一個ruby的Time類型的對象中,我們可以寫些代碼來驗證該列的類型及其內容:
order = Order.new
order.shipped_at = "2005-03-04 12:34"
pp order.shipped_at.class
pp order.shipped_at
輸出為:
Time
Fri Mar 04 12:34:00 CST 2005
下面的列表展示了sql和ruby間的數據類型對應關系:
SQLType Ruby Class SQLType Ruby Class
int, integer Fixnum float, double Float
decimal, numeric Float char, varchar, string String
clob, blob, text String datetime, time Time
interval, date Date Boolean 后面詳細介紹
有一個潛在的可能是關于decimal的,在數據庫里,使用decimal的列來存儲number和fix number型,Active Record將decimal映射成Float類的對象,盡管這樣可以應用于大多數應用,浮點數是不精確的,在對這一類型的屬性進行一系列操作的時候,可 能會發生舍入的錯誤,你也許可以使用integer類型來作為替代方案,例如,存儲貨幣型的時候可以將元,角,分,分別存入不同的字段。做為一種選擇,你 可以使用聚合(aggregations),使用多個分開的字段來構建貨幣類型。
(三、Boolean屬性)
一些數據庫支持boolean類型,而另一些則不支持,這使得Active Record要抽象boolean類型變得困難。例如,如果數據庫不支持boolean類型,有的開發者使用char(1)來替代,而內容使用“t”和 “f”來表示true和false,而另外一些開發者使用integer類型,0是false,1是true。即使數據庫支持boolean類型,在內部 也許還是使用0和1來存儲。
在Ruby里,在條件判斷中,數字0和字符f都被認為是true值,這就意味著如果你直接使用屬性的值,你的代碼會被認為該列的值是true,而不是你認為的false,例如:
# 不要這樣使用
user = Users.find_by_name("Dave")
if user.superuser
grant_privileges
end
當在查詢條件中使用屬性時,你必須在列名后添加一個問號:
# 這樣是正確的
user = Users.find_by_name("Dave")
if user.superuser?
grant_privileges
end
當使用訪問操作符來獲取屬性的值時,當值為數字0,或者字符“0”,“f”,“false”,或“”(空字符串),或nil,或一個常量false時,都被認為是false,否則,就會被認為是true。
如果你在一個遺留系統上或者非英語系統上開發,上面對true的定義也許會無法工作,在這種情況下,你可以override內建的謂詞方法的定義,例如,荷蘭語情況下,字段也許包含J或者N,這種情況下,你可以像下面這樣:
class User < ActiveRecord::Base
def superuser?
self.superuser == 'J'
end
# . . .
end
(四、存儲結構化數據)
有時,能夠在某個屬性中直接存儲任意的ruby對象是很方便的,一種辦法就是Active Record支持序列化,將一個ruby對象變為一個YMAL字符串,并且將這個字符串存儲到屬性對應的數據庫字段中。在數據庫定義中,這個字段必須為text類型。
因為Active Record將數據庫中的Char型和text型映射為ruby的string型,所以如果我們需要告訴Active Record使用序列化功能,例如,我們想知道某個客戶進行的最后的5次消費,我們創建一個含有text類型字段的表來保存信息:
create table purchases (
id int not null auto_increment,
name varchar(100) not null,
last_five text,
primary key (id)
);
在轉換這個表的Active Record類中,我們要使用serialize()聲明,來告訴Active Record要排列對象:
class Purchase < ActiveRecord::Base
serialize :last_five
# ...
end
當我們創建了一個新的Purchase對象,我們可以給last_five列賦任何值,在這個例子里,我們給last_five列設置一個字符串數組,
purchase = Purchase.new
purchase.name = "Dave Thomas"
purchase.last_five = [ 'shoes', 'shirt', 'socks', 'ski mask', 'shorts' ]
purchase.save
當我們讀入它的時候,這個屬性已經被設置為數組:
purchase = Purchase.find_by_name("Dave Thomas")
pp purchase.last_five
pp purchase.last_five[3]
代碼的輸出為:
["shoes", "shirt", "socks", "ski mask", "shorts"]
"ski mask"
盡管這個功能是很強大且便利的,但是只有當你不打算在ruby以外的項目中使用這些序列化的信息,除非那個程序也能夠使用YMAL格式。特別 是,這些信息很難被SQL查詢所利用,你也許會考慮使用聚合(aggregation)來替代,在后面我們會介紹這種辦法來達到相同的效果。
(五主鍵和ID)
也許你已經注意到了,在我們前面的代碼中,數據庫定義里都使用了一個integer型的字段id作為主鍵,這是Active Record的一個約定。
或許你要問,為什么不用訂單編號或者某個有意義的列來作為主鍵呢?使用id作為主鍵有一個很重要的原因,就是如果使用具有內在格式的主鍵的 話,隨著時間推移,有可能其中的規則也會變化。例如,使用ISBN號碼來給book表做主鍵,畢竟ISBN號碼是唯一的,但是,有可能當一本書寫完后,美 國的出版業已經發展了并且在所有的ISBN號碼后又附加了一位數字。
如果我們使用了ISBN作為book表的主鍵,我們就要更新所有book表的記錄來反映這個變化,而且還有一個問題,還有其他表引用了book表的主鍵,我們就要更新所有的引用,這還牽涉到要刪除外鍵,所有的這一切都是非常痛苦的。
如果使用有意義的值作為主鍵,那么我們將收到外界業務規則的影響,如果使用id,我們可以自己完全控制,而且如果象ISBN等一些東西改變的話,將不會影響到數據庫結構。
如果你從一個新的數據庫結構開始,可能會遵循約定,給所有的表都使用id作為主鍵,但是,當你使用的是一個既存的數據庫開始的時候,Active Record提供了簡單的方法來讓你重新給表指定主鍵,例如:
class BadBook < ActiveRecord::Base
set_primary_key "isbn"
end
通常,Active Record會注意給新創建的記錄生成主鍵值-使用自增長的整數。不管怎樣,當你override表的主鍵名字的時候,你就需要自己負責給新建記錄一個唯 一的主鍵值。也許有些讓人驚訝,你還是設置一個id的屬性來完成這件事,因為Active Record所關心的是,主鍵的設置永遠都使用名為id屬性,set_primary_key的聲明只是設置了使用的列名,下面的例子,我們使用ISBN 作為主鍵。
book = BadBook.new
book.id = "0-12345-6789"
book.title = "My Great American Novel"
book.save
# ...
book = BadBook.find("0-12345-6789")
puts book.title # => "My Great American Novel"
p book.attributes #=> {"isbn" =>"0-12345-6789",
"title"=>"My Great American Novel"}
也就是說,在設置主鍵的時候,使用id屬性,其他時候,使用真實的列名。
(六、連接數據庫)
Active Record抽象了數據庫連接的概念,幫助應用程序來處理底層的數據庫鏈接的細節,作為替代,Active Record使用通用的調用,將細節委托給一組數據庫適配器。
可以使用establish_connection( )方法來制定連接,下面的例子創建了一個mysql數據庫連接,數據庫的名字是railsdb,服務器的Host名為dbserver.com,用戶名為railsuser,密碼為railspw。
ActiveRecord::Base.establish_connection(
:adapter => "mysql",
:host => "dbserver.com",
:database => "railsdb",
:username => "railsuser",
:password => "railspw"
)
Active Record支持DB2,MySql,Oracle,Postgres,SqlServer,以及SqlLite,每一種數據庫適配器在鏈接的參數上都有一些細小的差別,下表列出了常用的參數:
注意Oracle適配器的名字為oci。
數據庫連接和Model類是關聯的,每個類都從父類那里繼承了鏈接,ActiveRecord::Base作為所有的Active Record類的父類,設置這里的數據庫連接就給所有的活動記錄類設置了鏈接,當然,如果需要的話,你也可以復寫(override)鏈接配置。
下面的例子里,我們的大多數表都在MySql數據庫中,庫名為online,由于一些歷史原因,customers表在名為backend的數據庫中,
ActiveRecord::Base.establish_connection(
:adapter => "mysql",
:host => "dbserver.com",
:database => "online",
:username => "groucho",
:password => "swordfish")
class LineItem < ActiveRecord::Base
# ...
end
class Order < ActiveRecord::Base
# ...
end
class Product < ActiveRecord::Base
# ...
end
class Customer < ActiveRecord::Base
# ...
end
Customer.establish_connection(
:adapter => "mysql",
:host => "dbserver.com",
:database => "backend",
:username => "chicho",
:password => "piano")
在我們前面所寫的depot程序中,我們沒有使用establish_connection方法,而是在 config/database.yaml文件中指定了數據庫連接的參數信息,對于大多數rails程序來說,這是首選的方式,不僅因為將配置信息和代碼 分離,而且在測試和部署時也能帶來方便,上面的表格里列出的參數都可以應用在YAML文件中,這一點我們在前面的配置文件一節已經有介紹。
最后,如果你通過一個標記訪問establish_connection(),Rails會在database.yaml文件中查找名字對應的配置節,來獲取鏈接的參數,這樣就可以將所有的數據庫連接配置從代碼中分離出來。
(七、創建記錄)
Active Record使得實現CRUD的數據庫基本操作變得簡單,在下面的幾節里我們使用Mysql數據庫中的orders表來進行CRUD的操作,這次先看創建(Create)。
我們假想有一個Model,名為Order:
class Order < ActiveRecord::Base
end
在面向對象的模型里,表對應類,表中的行對應類的對象。我們可以通過創建一個類的對象來創建一條記錄。對orders表,我們可以使用 Order.New()方法來創建一個Order的對象,也就對應了orders表的一條記錄,然后我們給該對象的每個屬性賦值,最后,我們調用對象的 save()方法將數據寫回數據庫,如果不調用save()的話,那么這個對象僅僅在內存中存在,而不是數據庫。
an_order = Order.new
an_order.name = "Dave Thomas"
an_order.email = "dave@pragprog.com"
an_order.address = "
123 Main St
"
an_order.pay_type = "check"
an_order.save
Active Record的構造器有一個可選的塊(block),這個塊可以將創建的Order對象做為參數,這樣就不需要再創建一個Order類的對象的變量了:
Order.new do |o|
o.name = "Dave Thomas"
# . . .
o.save
end
Active Record也可以接收一組哈希(Hash)參數的值來作為可選參數,由屬性的名字和相對應的值組成:
an_order = Order.new(
:name => "Dave Thomas",
:email => "dave@pragprog.com",
:address => "
123 Main St
",
:pay_type => "check")
an_order.save
注意到現在為止,我們還沒有任何關于id的設置,這是因為我們使用Active Record的默認約定,將orders表的主鍵為一個integer類型的列。在存入數據庫的時候,Active Record自動給新建的對象生成一個唯一的值,并且設置到id屬性上,我們可以在save()之后查詢id的值:
an_order = Order.new
an_order.name = "Dave Thomas"
# ...
an_order.save
puts "The ID of this order is #{an_order.id}"
new()構造函數在內存中創建了一個Order類的對象,你需要在某個時候調用save()方法來保存到數據庫。Active Record還有一個約定的方法create(),下面的例子說明這個方法的用法,同時展示了創建對象和存儲到數據庫:
an_order = Order.create(
:name => "Dave Thomas",
:email => "dave@pragprog.com",
:address => "
123 Main St
",
:pay_type => "check")
也可以給create()方法傳遞哈希(hash)的數組,在數據庫中創建多條記錄,并且返回對應的對象數組。
orders = Order.create(
[ { :name => "Dave Thomas",
:email => "dave@pragprog.com",
:address => "
123 Main St
",
:pay_type => "check"
},
{ :name => "Andy Hunt",
:email => "andy@pragprog.com",
:address => "
456 Gentle Drive
",
:pay_type => "po"
} ] )
方法new()和create()的真正目的就是讓我們可以通過一組參數就能夠創建Model對象:
order = Order.create(params)
(八、讀取記錄)
讀取記錄包括指定那些特定的數據是你感興趣的,你給Active Record指定標準,Active Record再返回給你一些對象,其中包含了符合條件的記錄的數據。
在一個表中檢索數據的最簡單的辦法就是指定主鍵,任何一個Model都支持find()方法,該方法支持一個或多個主鍵值,如果只指定了一個 主鍵,將會返回對應的對象,如果指定了多個主鍵給find方法,該方法一組相應的對象。注意,當沒有任何符合條件的數據的時候,將會拋出一個 RecordNotFound異常,所以如果find方法沒有拋出這個異常的話,返回的數組中的對象個數就等于給find方法指定的id數目。
an_order = Order.find(27) # find the order with id == 27
# Get a list of order ids from a form, then
# sum the total value
order_list = params[:order_ids]
orders = Order.find(order_list)
count = orders.size
通常,在查詢的時候都要用到除過id以外的值,Active Record提供了一組設置來執行這些查詢,我們會介紹find使用方法,從基本的查詢,再到高階些的動態查詢。
到現在我們只是了解了find方法的最基本的內容,通過指定id來獲取一個或一組對象。另外,我們還可以使用一些標記比如:first,:all來作為find方法的參數。
:first將返回符合條件的第一條記錄,:all將返回所有符合條件的記錄,下一篇我們來看看Active Record是如何處理sql的。
注:find在3.1版中建議不使用,改用where。by--biyeah
(九、行數和再加載數據)
Active Record提供了兩個方法來獲取符合條件的記錄的條數:count()和count_by_sql()。例如:
c1 = Order.count
c2 = Order.count(["name = ?", "Dave Thomas"])
c3 = LineItem.count_by_sql("select count(*) " +
" from line_items, orders " +
" where line_items.order_id = orders.id " +
" and orders.name = 'Dave Thomas' ")
puts "Dave has #{c3} line items in #{c2} orders (#{c1} orders in all)"
在一個程序中,數據庫有可能被多個進程或多個程序訪問,隨時都有可能獲取最新的Model對象,這些對象有可能剛剛被編輯過。
從某種程度上講,這主要應用在事務中,不管怎么說,當你需要手動刷新Model對象時,Active Record可以幫助你,只需調用reload()方法,Model對象屬性的值就會被數據庫中的值更新。
stock = Market.find_by_ticker("RUBY")
loop do
puts "Price = #{stock.price}"
sleep 60
stock.reload
end
(十、更新記錄)
前面了解了檢索的方法,這次來看看Active Record怎樣更新數據庫中的記錄。
如果你有一個Active Record對象(或許對應于order表),你可以通過調用save方法將它寫道數據庫中去,如果這個對象是先前從數據庫中讀取出來的,save方法將會更新既有的記錄,否則將會新建一條記錄。
如果一條既有記錄被更新,Active Record將會用它的主鍵和來匹配內存中的對象,Active Record對象中的屬性被更新到對應的列,即使一個列中的值沒有變化也會被更新,在下面的例子中,id為123的訂單所有的內容都會被更新:
order = Order.find(123)
order.name = "Fred"
order.save
不管怎樣,在下面的例子里,Active Record對象只包含id,name,paytype,當對象被保存的時候僅僅只有這些字段被更新,注意如果你想要把對象保存到數據庫,那么在使用find_by_sql方法時,一定要包含id字段。
orders = Order.find_by_sql("select id, name, pay_type from orders where id=123")
first = orders[0]
first.name = "Wilma"
first.save
另外,Active Record還提供了update_attribute()方法,該方法可以將Model對象的某個屬性保存到數據庫。
order = Order.find(123)
order.update_attribute(:name, "Barney")
order = Order.find(321)
order.update_attributes(:name => "Barney",
:email => "barney@bedrock.com")
我們可以把讀取和更新結合在一起,使用update()方法或update_all(),update()方法使用一個id和一組屬性,如果在數據庫中對應的記錄,就更新指定的屬性,然后返回model對象。
order = Order.update(12, :name => "Barney", :email => "barney@bedrock.com")
也可以傳遞一組id或者屬性和值的hash給update()方法,這樣會更新所有匹配的記錄,并且返回一組model對象。
最后,update_all()方法允許你指定給update語句指定Where條件,下面的例子給所有標題中含有java的商品漲價10%:
result = Product.update_all("price = 1.1*price", "title like '%Java%'")
這里的返回值依賴于具體的數據庫適配器,很多數據庫都返回被更新的記錄數目。
下面我們看看save()和save!()這兩個方法。
簡單的save()方法在Model對象存在并且可以的保存的情況下返回true:
if order.save
# all OK
else
# validation failed
end
這樣會導致你在所有調用save方法的地方都要加上檢查,但是Active Record假定save方法是在Controler的Action的上下文中的,并且視圖里的代碼不進行這些檢查。(這部分書上看不明白,不能確定)。
不管怎樣,如果你需要在上下文環境中保存Model對象,并且想確定是否所有的錯誤都被處理了,你可以使用save!()方法,如果Model對象不能保存,那么這個方法會拋出一個RecordInvailid異常:
begin
order.save!
rescue RecordInvalid => error
# validation failed
end
轉載于:https://www.cnblogs.com/deepbreath/p/4153317.html
總結
以上是生活随笔為你收集整理的ActiveRecord教程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: MySQL数据库SQL层级优化
- 下一篇: PHP的ISAPI和FastCGI比较