簡體   English   中英

如何讓 ActiveRecord 在 Ruby on Rails 中顯示下一個 id (last + 1)?

[英]How do I get ActiveRecord to show the next id (last + 1) in Ruby on Rails?

如果一個對象要被持久化到數據庫中,ActiveRecord 是否有一種緊湊的方法來查詢接下來要使用的 id? 在 SQL 中,這樣的查詢看起來像:

SELECT max(id) + 1 FROM some_table;

這是稍微修改的Taryn East的版本:

Model.maximum(:id).next
# or
Model.calculate(:maximum, :id).next

資料來源:

#maximum @ActiveRecord::Calculations

#next @整數

在接受無花果的回答時,我可能想提請您注意一件小事。 如果您在保存之前將下一個 ID 設置為特定記錄,我認為這不是一個好主意。

因為作為基於 Web 的系統中的示例

  1. 你得到最后一個 id 為 10
  2. 您將下一個 id 設置為 11
  3. 在您保存記錄之前,其他人已經保存了記錄,現在最后一個 id 也應該是 12..

我不確定您是否希望最后一個 id 做我在這里的想法,但如果是這樣,這只是為了引起您的注意。

如果您的數據庫是 Postgres,您可以使用它獲取下一個 id(例如名為“users”的表):

ActiveRecord::Base.connection.execute("select last_value from users_id_seq").first["last_value"]

與其他答案不同,此值不受記錄刪除的影響。

可能有一個 mySQL 等價物,但我沒有設置來確認。

如果您已將數據導入您的 postgresql 數據庫,則導入后的下一個 id 值很有可能不會設置為大於您導入的最大整數的下一個整數。 因此,您將在嘗試保存 activerecord 模型實例時遇到問題。

在這種情況下,您需要手動設置下一個 id 值,如下所示:

ActiveRecord::Base.connection.execute("alter sequence users_id_seq restart with 54321;") #or whatever value you need

比公認的答案略好:

YourModel.maximum(:id) + 1

仍然容易出現競爭條件等,但至少它會記錄跳過的 id,並且比按 id 排序表然后返回最后一個表更有效。

這是一個老問題,但如果您刪除了最后一條記錄,其他答案都不起作用:

Model.last.id #=> 10
Model.last.destroy
Model.last.id #=> 9, so (Model.last.id + 1) would be 10... but...
Model.create  #=> 11, your next id was actually 11

我使用以下方法解決了這個問題:

current_value = ActiveRecord::Base.connection.execute("SELECT currval('models_id_seq')").first['currval'].to_i
Model.last.id #=> 10
Model.last.destroy
Model.last.id #=> 9
current_value + 1 #=> 11

獲取桌子上最大的id

YourModel.maximum(:id)

這將運行以下 sql

SELECT MAX("your_models"."id") AS max_id FROM "your_models"

通過調用to_i將結果轉換為整數。 這很重要,因為對於空表,上面的 max 命令將返回nil 幸運的是nil.to_i返回0

現在要獲取下一個可用的 id,只需添加 1 + 1`

最終結果

YourModal.maximum(:id).to_i+1

如果沒有其他人在使用該表(否則您將不得不使用鎖定),您可以從 MySQL 獲取自動增量值,SQL 命令是

SELECT auto_increment FROM information_schema.tables 
WHERE table_schema = 'db_name' AND table_name = 'table_name';

Rails 命令將是

ActiveRecord::Base.connection.execute("SELECT auto_increment 
     FROM information_schema.tables 
     WHERE table_schema = 'db_name' AND table_name = 'table_name';").first[0]

如果您想確保沒有其他人可以獲取“新”索引,則應鎖定該表。 通過使用類似的東西:

ActiveRecord::Base.connection.execute("LOCK TABLES table_name WRITE")

ActiveRecord::Base.connection.execute("UNLOCK TABLES")

但這是特定於每個數據庫引擎的。

順序 id 列的唯一正確答案是:

YourModel.maximum(:id)+1

如果您在默認范圍內對模型進行排序,則 last 和 first 將取決於該順序。

我認為對此沒有通用的答案,因為您可能不想假設一個數據庫優於另一個數據庫。 例如,Oracle 可以按順序分配 id,或者更糟的是,按觸發器分配 id。

就其他數據庫而言,可以使用非順序或隨機 id 分配來設置它們。

請問用例是什么? 為什么需要預測下一個 id? “下一個”是什么意思? 相對於什么時候? 競爭條件(多用戶環境)呢?

這里似乎需要一個消除競爭條件的答案,並解決預先提供唯一標識符的實際問題。

為了解決這個問題,您維護了第二個模型,它為主要模型提供唯一 ID。 這樣你就沒有任何競爭條件。

如果您需要確保這些 ID 的安全,您應該使用 SHA256(或 SHA512)對它們進行散列,並在生成它們時將散列存儲為標識符模型上的索引列。

然后,在主模型上使用它們時,可以關聯和驗證它們。 如果你不散列它們,你仍然會關聯它們,以提供驗證。

稍后我會發布一些示例代碼。

不要明白意圖,但您可能想考慮使用 GUID

你永遠不應該假設序列中的下一個 id 是什么。 如果您有超過 1 個用戶,那么您將面臨在創建新對象時 id 正在使用的風險。

相反,安全的方法是創建新對象並更新它。 對您的數據庫進行 2 次點擊,但使用您正在使用的對象的絕對 ID。

這種方法將猜測工作排除在等式之外。

我來到這個 SO 問題是因為我希望能夠預測在我的測試套件中創建的模型的 id(然后將該 id 用於對外部服務的 REST 請求,我需要預測確切的值來模擬請求) .

我發現Model.maximum(:id).next雖然很優雅,但在帶有事務性裝置的 Rails 測試環境中不起作用,因為數據庫中通常沒有記錄,所以它只會返回nil

事務性固定裝置使問題變得更加棘手,因為即使數據庫中沒有任何記錄,自動增量字段也會上升。 此外,使用ALTER TABLE ***your_table_name*** AUTO_INCREMENT = 100會破壞您的測試所在的事務,因為它需要自己的事務。

我為解決這個問題所做的是創建一個新對象並將 1 添加到其 id:

let!(:my_model_next_id) { FactoryBot.create(:my_model).id + 1 }

盡管有些 hacky(並且在您的數據庫上效率稍低,因為您為了它的 id 而創建了一個額外的對象),但它不會對事務做任何愚蠢的事情,並且在沒有記錄的測試環境中可靠地工作(除非您的測試在與訪問同一個數據庫並行......在這種情況下:競爭條件......也許?)。

除上述答案外,還有另一種檢查當前自動增量的方法:

query = "show create table features" 
res = ActiveRecord::Base.connection.execute(query)
puts res.to_a 

這也可以打印當前的AUTO_INCREMENT屬性,以及諸如 create table 語句、CHARSET 等表屬性。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM