Rails之道 ---><The Rails Way> 摘录(6)運用ActiveRecord

1.當你在開發模式下運行Rails服務器時,通過Web瀏覽器對數據表結構的改動會立即在ActiveRecord對象中反映出來.然而,如果你運行著Rails的控制臺,那么對數據表結構的改動并不會自動反映到ActiveRecord對象.當然,你可以通過手動的方式將這些改動反映出來,在控制臺輸入Dispatcher.reset_application!即可.

2.遷移是Rails提供的一種幫助你更新應用程序的數據表結構(也就是熟悉的數據描述語言========DDL)的方式,通過它,在每次改變應用程序代碼的時候,你不需要刪除或者重新創建數據表,這就意味著你不會丟失開發數據.在執行遷移的時候,唯一改變的事將數據表結構從一個版本改為另一個版本,不管這個改動是版本更新還是回退.

3.Rails針對創建遷移提供了一個生成器.我們可以通過命令行查看幫助文本,如下所示:

$ script/generate migration
Usage: script/generate migration MigrationName [options]
Rails Info:
-v, --version Show the Rails version and quit.
-h, --help Show this help message and quit.
General Options:
-p, --pretend Run but do not make any changes.
-f, --force Overwrite files that already
exist.
-s, --skip Skip files that already exist.
-q, --quiet Suppress normal output.
-t, --backtrace Debugging: show backtrace on
errors.
-c, --svn Modify files with subversion.
(Note: svn must be in path)

Description:
The migration generator creates a stub for a new database
migration.
The generator takes a migration name as its argument. The
migration
name may be given in CamelCase or under_score.
The generator creates a migration class in db/migrate prefixed by
its number in the queue.
Example:
./script/generate migration AddSslFlag
With 4 existing migrations, this will create an AddSslFlag
migration
in the file db/migrate/005_add_ssl_flag.rb

從上可知,你只需要為遷移提供一個駝峰式的描述性名字,生成器就能完成余下的工作.在需要改變現有表的屬性時,我們只要直接調用生成器即可.(備注:諸如數據庫模型生成器等其他的生成器同樣為你創建遷移腳本,除非你特別指定了----skip-migration選項)

4.所有列選項都接受下面三個選項,
[quote](1):default => value
設置默認值.在創建數據行的時候,該默認值將作為該列的初始值.如果你希望值為null,那么你并不需要進行顯式的設置,只要不設置該項即可.

(2)limit => size
為字符串、文本、二進制或者整數列增加一個范圍大小的參數.根據列類型不同,這個參數的含義也會相應不同.一般來說,對于字符串類型的限制是指字符的個數,而對于其他類型,大小規則是指在數據庫中存儲相應值所需的字節數

(3):null => true
通過增加not null的約束到數據庫的級別上,將某個列設為必須的.

5.小數精度
聲明為:decimal的列接受以下兩個選項:
(1):precision = > number
精度(precision)是指數字的總位數
(2):scale => number
而數值范圍(scale)則是小數點后面數字的總位數.
eg: 123.45的精度為5,而數值范圍是2(自從加入火星后,現在對這個數字很敏感.....)[/quote]

6.別用浮點數來存儲貨幣值,或者說,別用浮點數來存儲需要固定精度的所有類型的數據.

7.ActiveRecord有一個attributes方法,它返回一個散列,包括所有屬性和相應的值(通過read_attribute方法返回的值).如果使用自定義的屬性讀寫器,那么attributes方法被調用的時候,它并不會使用自定義的屬性讀取器來訪問屬性值,但是attributes= (在其中進行大量賦值操作)確實會調用自定義的屬性寫入器.(備注,通過attributes方法返回的散列并不是ActiveRecord對象內部結構的引用,而是一個副本,這意味著改變它的值并不會對原對象造成影響.)

8.對于許多程序而言,最常見的操作就是基于一個或兩個列進行簡單的查詢.因此Rails提供了一種簡單而有效的方式來進行這些查詢,并且不需要通過find方法的條件參數.這種方式能夠實現,有賴于Ruby那充滿魔力的method_missing回調,每當你調用了還沒有定義的方法的時候,這個回調就會被調用.

9.find_or_create_by_:如果對象存在,那么查找器就會返回它,否則會創建一個并且返回創建的對象.(備注:如果你不想一開始就保存創建的對象,可以使用find_or_initialize_by_查找器)

10.find_by_sql這個類方法可以直接使用SQL的Select查詢,并且給予查詢結果,返回ActiveRecord對象的數組.
eg:
 >>Client.find_by_sql("select * from clients")

備注:你應該慎用此方法,除非是在不得不用的時候.
(1)這個方法減少了數據庫的可移植性,如果使用了ActiveRecord正規的find方法,那么Rails會在底層為你處理好不同數據庫之間的差異.
(2)ActiveRecord已經擁有了一堆內建的函數,用于抽象select語句,因此,重新發明新的方式來實現同樣的功能是不明智的.
>>Client.find_by_sql("select * from clients where code like 'A%'")
==>
>> param = "A"
>>Clinent.find(:all, :coditions => ["code like?", "#{param}%"])


11.update_all,它與使用update..where這樣的SQL語句進行更新是緊密對應的.update_all方法可以接受兩個參數,一個對應于SQL語句的SET部分,而另一個則對應于WHERE子句,也就是條件.這個方法返回了更新記錄的數目.
eg:
Order.update_all("name = 'Yang'", "pay_type= 'cc'")


12.在ActiveRecord中的屬性,在你需要防止一時疏忽以及大量賦值操作的時候,你可以使用下面兩個類方法來控制對屬性的訪問.
(1)attr_accessible方法以屬性列表為參數,這個列表指明了能夠用于大量賦值的可訪問屬性.(這是用于大量賦值的一種更保守的做法)

(2)如果在默認的情況下,大部分屬性都是開放的,而只有在需要的時候才會被限制某些屬性訪問時,你應該使用attr_protected方法.傳遞給這個方法的屬性將會受到保護,不能用于大量賦值.在大量賦值過程中,這些屬性的賦值會簡單地被忽略,你只能通過直接復制到方法來為這些屬性賦值,如下eg所示:

class Customer < ActiveRecord::Base
attr_protected :credit_rating
end

customer = Customer.new(:name => “Abe”, :credit_rating => “Excellent”)
customer.credit_rating # => nil

customer.attributes = { “credit_rating” => “Excellent” }
customer.credit_rating # => nil

# and now, the allowed way to set a credit_rating
customer.credit_rating = “Average”
customer.credit_rating # => “Average”


13.刪除和銷毀
如果你需要從數據庫中移除某個記錄,那么你有兩種選擇:
(1)destroy方法不僅將記錄從數據庫中移除,還會凍結它(將其設為只讀),這樣你就不能再次保存它.
>> bad_timesheet = Timesheet.find(1)
>> bad_timesheet.destroy
=> #<Timesheet:0x2481d70 @attributes={“updated_at”=>”2006-11-21
05:40:27”, “id”=>”1”, “user_id”=>”1”, “submitted”=>nil, “created_at”=>
“2006-11-21 05:40:27”}>

>> bad_timesheet.save
TypeError: can’t modify frozen hash
from activerecord/lib/active_record/base.rb:1965:in `[]=’

(2)另一種可選的方法是可以將destroy和delete方法作為類方法進行調用,只需要傳遞所刪除的id即可.兩個方法都能接受單個id或者id數組作為參數.
Timesheet.delete(1)
Timesheet.destroy([2, 3])

方法的命名看起來有些前后矛盾,事實上并非如此.delete方法直接使用SQL語句,并且不會裝載任何實例(因此會更快).destroy方法會裝載ActiveRecord對象實例,并且通過實例來調用destroy方法.兩者的語義差別是非常細微的,不過,如果你在before_destroy回調中進行了賦值或者擁有依賴關聯的時候,差別就會凸現了.在有依賴關聯的情況下,調用destroy方法會使得子對象和父對象一起被刪除.

14.數據庫鎖定
[color=blue]"鎖定"是一個專業術語,它是指用于防止應用程序的并發用戶復寫各操作結果的技術.從數據庫裝載數據模型數據的時候,ActiveRecord通常都不會使用任何類型的鎖,如果某個特定的Rails應用程序在同一時間只有一個用戶在更新數據,那么你并不需要擔心鎖的問題.
當多于一個的用戶同時訪問并且更新相同數據的時候,你作為開發人員務必要考慮并發性的問題,這是非常重要的.自我提問一下,如果兩個用戶同時嘗試更新某個特定的數據模型,會發生什么類型的沖突(collision)或者競爭條件(race collision)呢?
在數據庫支撐的應用程序中,有很多方法可以處理并發性問題,而ActiveRecord支持中的兩種方式:樂觀鎖定(optimistic locking)和悲觀鎖定(pessimistic locking).[/color]
(1)樂觀鎖定
是指在沖突發生的時候用于檢測并且解決沖突的策略.在多用戶并且沖突發生較少的情況下,樂觀鎖定是受推崇的.在使用樂觀鎖定的時候,數據記錄并不會真正被鎖定,因此"樂觀鎖定"這個術語多少有點用詞不當.
樂觀鎖定是個非常常用的策略,因為對于絕大多數應用程序的設計而言,某個特定用戶主要還是更新屬于它自己的數據,而不是別人的,所有兩個用戶搶著更新同一個數據的可能性極少.樂觀鎖定潛在的意圖是既然沖突很少發生,那么只有它們真的發生的時候,我們才去處理它.
如果你控制數據庫表結構,那么樂觀鎖定是很容易實現的.對于某個特定的表,你只需要增加一個lock_version、默認值為零點整數列即可

class AddLockVersionToTimesheets < ActiveRecord::Migration
def self.up
add_column :timesheets, :lock_version, :integer, :default => 0
end
def self.down
remove_column :timesheets, :lock_version
end
end

增加了lock_version之后,ActiveRecord的功能就會改變.如果兩個不同的數據模型實例被裝載并且被不同用戶保存,那么第一個實例將會成功保存,而第二個實例將會導致ActiveRecord::StaleObjectError異常拋出.

我們可以通過一個簡單的單元測試來說明樂觀鎖定的功能:
class TimesheetTest < Test::Unit::TestCase

fixtures :timesheets, :users
def test_optimistic_locking_behavior
first_instance = Timesheet.find(1)
second_instance = Timesheet.find(1)

first_instance.approver = users(:approver)
second_instance.approver = users(:approver2)

assert first_instance.save, "First instance save succeeded"

assert_raises ActiveRecord::StaleObjectError do
second_instance.save
end
end
end


測試通過了,因為調用第二個實例的save方法會引發ActiveRecord::StaleObjectError異常,這是我們期待的效果.

(備注:
a.要使用其它數據列代替lock_version,那么你可以使用set_locking_column來改變這個設置.要讓這個改動應用于整個應用程序,則需要在enviroment,rb中增加以下代碼:
ActiveRecord::Base.set_locking_column 'alternate_lock_version'
與其它ActiveRecord設置類似,你可以在數據模型類中增加一個生命,從而改變單個數據模型的設置.

class Timesheet < ActiveRecord::Base
set_locking_column ‘alternate_lock_version’
end


b.處理StaleObjectError
在設置了樂觀鎖定之后,我們需要得體的處理StaleObjectError.根據更新數據不同的關鍵性,你可能需要花費很多時間去構建對用戶友好的解決方案,以便保存更新失敗的用戶所提交的數據.即使是在更新數據很容易再創建的情況下,只要要通過一些控制器代碼讓用戶知道為什么更新失敗,如下所示:

def update
begin
@timesheet = Timesheet.find(params[:id])
@timesheet.update_attributes(params[:timesheet])
# redirect somewhere
rescue ActiveRecord::StaleObjectError
flash[:error] = "Timesheet was modified while you were editing it."
redirect_to :action => ‘edit’, :id => @timesheet
end
end

)

樂觀鎖定有很多優勢,它不需要數據庫提供特殊的支持,而且容易實現,正如你從代碼中看到的,處理StaleObjectError只需要很少的代碼.而樂觀鎖定的缺點主要集中在更新操作會慢一些,因為應用程序會檢查鎖定版本.除此以外,它可能會造成糟糕的用戶體驗,因為在更新提交之前,用戶無法知道更新能否成功.如果更新數據丟失,那么用戶會感到非常憤怒...(這才是他媽的用戶體驗...設身處地的想,我要提交了一次,然后無端端失敗,并且數據也沒了,比如寫了Blog,我肯定是沒興趣再玩下去,并且會對這個網站產生厭煩的感覺,并告知所有我知道的人....或者直接找站長理論去...)

(2)悲觀鎖定
它需要特所的數據庫支持(大型數據類都內嵌了這樣的支持),并且在更新操作發生的時候鎖定特定的數據行.這樣防止了其他用戶讀取將要更新的數據,從而避免用戶操作已經失效的數據.
對于Rails而言,悲觀鎖定是非常新的附加特性.在下面的例子中,悲觀鎖定與事務一起配合工作.
eg:
Timesheet.transaction do
t = Timesheet.find(1, :lock=> true)
t.approved = true
t.save!
end

對于一個存在的數據模型實例調用lock!方法也是可行的,這個方法在底層只是簡單地調用"reload(:lock => true)".對于一個底層包含屬性更改的實例,你則不應該這樣,因為重新載入的操作會丟失屬性的更改.
悲觀鎖定在數據庫的層面上起作用.油ActiveRecord生成的SELECT語句會包含FOR UPDATE(或者類似的內容)子句,這樣就使得其他所有鏈接都無法訪問由該SELETE語句返回的數據行.只有在事務提交之后,鎖才會被釋放.從理論上說,假如Rails進程在數據事務操作完成之前結束,那么在連接被終止或者超時之前,鎖都不會被釋放


(3)需要考慮到問題
使用樂觀鎖定的Web應用程序會獲得最佳的伸縮性,因為它不使用特定的數據庫所提供的鎖定功能,這在前文已經討論過了.不過,你不得不為應用程序增加相應的邏輯,以便處理更新失敗的情況.悲觀鎖定會實現得更容易一些,但是又會引發以下的問題:在一個Rails進程等待另外一個Rails進程釋放數據鎖定的時候,它只是在等待,也就無法為其它進入的需求提供服務了.(請記住:Rails是單線程的)
作者說:既然在Rails中,數據庫事務的持久化并不會跨越多個HTTP請求,而悲觀鎖定又在另一個平臺上,所以它并不危險.事實上,可以確定,在這樣一個無分享(shared-nothing)架構中,跨越多個HTTP請求的數據庫事務的持久化是不可能實現的.
另外一個需要留意到情況是許多用戶爭相訪問某個特定的記錄,而更新這個特定記錄又需要很長的時間.為了實現最佳的效果,要讓使用悲觀鎖定的事務保持足夠小,并且確定它們能夠很快的執行.

15.高級查找
我們通常都需要通過某些條件(對應WHERE子句)對查找操作(底層就是一個SELECT的SQL語句)獲得的結果集進行篩選.ActiveRecord為你提供許多方式來實現這個功能,也就是將散列傳遞給find方法.
[quote](1):conditions 參數指定了查詢條件,而它可以被指定為字符串、數組或者散列,代表著SQL語句的WHERE部分.如果輸入數據源自外部,那么數組形式的參數就會被使用.
如果你指定的條件包含布爾值,那么你就需要特別留意.不同的數據庫有多種不同的方式表達布爾值.有的是"1"和"0",而有的是"T"和"F".
如果你使用數組或者散列作為條件并且使用Ruby布爾值作為參數,那么Rails會為你處理數據轉換的問題,這對用戶來說是透明的.

Timesheet.find(:all, :conditions => ['submitted=?', true])


(2):order選項能通過SQL子句來指定數據列的排序.
Timesheet.find(:all, :order => 'created_at desc')

(備注:SQL規范制定:在省略ascending/descending)選項的情況下,默認排序是升序的.

隨機排序
Rails并不會對:order選項的值進行校驗,這意味著你可以傳遞任何值,而不只是排序的列或升降序,只要底層數據庫能夠理解該值即可.在需要獲取一個隨機值的時候,該特性十分有用,以下例子說明了這一點:
# MySQL
Timesheet.find(:first, :order => ‘RAND()’)

# Postgres
Timesheet.find(:first, :order => ‘RANDOM()’)

# Microsoft SQL Server
Timesheet.find(:first, :order => ‘NEWID()’)

# Oracle
Timesheet.find(:first, :order => ‘dbms_random.value’)

(ff:請記住,對大型的數據集進行隨機排序會造成嚴重的性能問題,大多數數據庫都是如此,MySQL的問題尤為明顯.)

(3):limit能夠接受整形的參數,從而對查詢返回的數據行的數量進行限制.:offset參數則指定了偏移量,也就是從結果集的哪一行開始獲取數據行,它的取值范圍為1至最大的索引.這兩個選項合在一起,都用于對結果進行分頁.
eg:針對10個記錄一頁的時間表列表,以下代碼就用于查找第二頁的記錄.

Timesheet.find(:all, :limit => 10, :offset => 11)

(備注:根據應用程序數據模型的細節,對于某一特定的查詢,總是限制其獲取的ActiveRecord對象的最大數量是非常有意義的.如果讓用戶能夠觸發一些查詢,并一次性獲取了成千上萬的ActiveRecord對象,就無疑是引火ZF(cao ni ma ge B ---FGW,這也算敏感詞).)

(4):select
在默認情況下,:select選項是"*",與"SELECT * FROM"語句類似.但是,在需要的時候,這個值是可以改變的.
eg:
>> b = BillableWeek.find(:first, :select => "monday_hours +
tuesday_hours + wednesday_hours as three_day_total")
=> #<BillableWeek:0x2345fd8 @attributes={"three_day_total"=>"24"}>


(備注:
warning:>> b.monday_hours
NoMethodError: undefined method `monday_hours’ for
#<BillableWeek:0x2336f74 @attributes={“three_day_total”=>”24”}>
from activerecord/lib/active_record/base.rb:1850:in
`method_missing’
from (irb):38


要獲取對象常規的列值以及計算出來的列值,需要給:select參數增加個*
:select => '*, monday_hours + tuesday_hours + wednesday_hours as
three_day_total'


)

(5):form選項指定了所生成的SQL語句的表名部分.如果你需要包含用于連接的額外表或者對數據視圖的引用,那么你可以提供一個自定義值.
eg:

def find_tagged_with(list)
find(:all,
:select =>"#{table_name}.*",
:from => "#{table_name}, tags, taggings",
:conditions =>
["#{table_name}.#{primary_key}=taggings.taggable_id
and taggings.taggable_type = ?
and taggings.tag_id = tags.id and tags.name IN (?)",
name, Tag.parse(list)])
end

(備注:如果你在思考為什么使用table_name而不是一個顯式的值,那么可以告訴你,因為這個代碼混入(mixin)到了一個使用Ruby 模組的目標類)

(6)group by 選項
通過某個屬性名,我們能夠將結果集進行分組,就像GROUP BY SQL子句一樣.一般來說,你需要將:group選項結合:select選項一起用,因為合法的SQL要求包含分組操作的SELECT語句所選取的列要么是聚合函數,要么就是數據表的常規列.

>> users = Account.find(:all,
:select => ‘name, SUM(cash) as money’,
:group => ‘name’)
=> [#<User:0x26a744 @attributes={“name”=>”Joe”, “money”=>”3500”}>,
#<User:0xaf33aa @attributes={“name”=>”Jane”, “money”=>”9245”}>]


(備注:額外獲取的列值是字符串類型 -------ActiveRecord并不會對它們進行類型轉換.你需要使用to_i和to_f方法將它們轉換為數據類型.
>> users.first.money > 1_000_000
ArgumentError: comparison of String with Fixnum failed
from (irb):8:in ‘>’

)

(7):lock
在事務作用域內對查找操作指定":lock => true"選項會對選取的數據行設置唯一的鎖.

(8):joins
在你執行“GROUP BY”并且聚合了其他表達數據的時候,:joins選項是非常有用的,但你并不需要裝載關聯的對象.
Buyer.find(:all,
:select => 'buyers.id, count(carts.id) as cart_count',
:joins => 'left join carts on carts.buyer_id=buyers.id',
:group => 'buyers.id')


盡管如此,:joins和:include選項最常見的用法還是讓你能夠在單個SELETE語句中主動獲取(eager-fetch)額外的對象.

(9):readonly
指定":readonly => true "選項會將返回的對象標記為只讀的.你可以改變它們的屬性,但是不能將這些更改的屬性保存到數據庫.(ff:這個是個很邪惡的用法)
>> c = Comment.find(:first, :readonly => true)
=> #<Comment id: 1, body: “Hey beeyotch!”>
>> c.body = “Keep it clean!”
=> “Keep it clean!”
>> c.save
ActiveRecord::ReadOnlyRecord: ActiveRecord::ReadOnlyRecord
from /vendor/rails/activerecord/lib/active_record/base.rb:1958



[/quote]
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
将svn数据迁移至GitLab的过程可以分为以下几个步骤: 1. 首先,需要确认你的GitLab版本。你可以通过运行命令`cat /opt/gitlab/embedded/service/gitlab-rails/VERSION`来查看GitLab的版本。 2. 然后,创建一个备份目录以存储GitLab的数据。你可以使用如下命令创建备份目录并备份原有的数据: ``` cp -r /opt/gitlab/embedded/service/gitlab-rails{,.bak} ``` 此命令会在`/opt/gitlab/embedded/service/gitlab-rails`目录下创建一个备份目录`.bak`,并将原有的数据复制到备份目录中。 3. 接下来,将从SVN迁移的代码复制到GitLab的存储目录下。假设你已经将SVN的代码迁移到了`gitlab-11-2-stable-zh`目录中,你可以使用如下命令将其复制到GitLab的存储目录: ``` /bin/cp -rf gitlab-11-2-stable-zh/* /opt/gitlab/embedded/service/gitlab-rails/ ``` 此命令会将`gitlab-11-2-stable-zh`目录下的所有文件和文件夹复制到GitLab的存储目录中。 4. 最后,需要更新GitLab的配置文件以指定备份路径。你可以通过运行如下命令查看当前配置文件中的备份路径: ``` cat -n /etc/gitlab/gitlab.rb | sed -n '299p' ``` 此命令会显示配置文件中第299行的内容,其中包含了备份路径的设置。 请确保将其中的`/gitlab-backup`替换为你自己的备份路径,并保存配置文件。 完成以上步骤后,你的SVN数据就会被成功迁移至GitLab。希望对你有帮助!<span class="em">1</span><span class="em">2</span><span class="em">3</span><span class="em">4</span>

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值