

現在`User`モデルに以下のような処理を入れて`uuid`を実現しています。

[code]

    def before_save_action_name
        uuid = SecureRandom.uuid
        uuid = SecureRandom.uuid while User.exists?(:uuid => uuid)
        self.uuid = uuid
    end
    
[/code]

Rubyの表現力があれば1行で作れそうな気がするのですが(特にuuidに代入している行が2つある部分)、  
どのように実装するのが理想でしょうか。


CC BY-SA 3.0
2017-08-09T23:43:12.290
37075
2018-04-15T16:33:51.557
9149
question
ruby-on-rails ruby rails-activerecord
railsでモデルにuuidを持たせる実装について


私も、どこからかの借り物ですが、以下のような実装をしています。  
user.rbに

[code]

    before_create :generate_token
    
    protected
    
    def generate_token
      self.uuid = loop do
        random_token = SecureRandom.urlsafe_base64(nil, false)
        break random_token unless self.class.exists?(uuid: random_token)
      end
    end
    
[/code]

`SecureRandom.urlsafe_base64(nil, false)`だと以下のような文字列  
`8MswKOY37rN357LyVCkMyw`

`SecureRandom.uuid`だと以下のような文字列が生成されます。  
`382872bd-7d29-4443-b69d-c837ae466531`

気になるのはuuidは一回きりしか生成しないなら、before_saveよりもbefore_createの方がベターです。  
before_saveだとcreateとupdateの保存でも実行されてしまいます。  
<https://apidock.com/rails/ActiveRecord/Callbacks/before_save>

> before_save() public  
>  Is called before Base.save (regardless of whether it’s a create or update
> save).


CC BY-SA 3.0
2017-08-10T03:35:54.983
37086
2017-08-10T03:41:30.383
2017-08-10T03:41:30.383
5436
5436
37075
answer


UUIDは128ビットあり、v4(ランダム)の場合8.2×10^14個生成してようやく衝突確率が0.000001%になります。([参考表](https://ja.wikipedia.org/wiki/%E8%AA%95%E7%94%9F%E6%97%A5%E6%94%BB%E6%92%83#.E6.95.B0.E5.80.A4.E7.9A.84.E8.A7.A3.E8.AA.AC))
そんなレアケースに対して毎回 `exists?` でチェックするのはコストが高い気がします(もちろん計測すべきですが)

DB側でユニーク制約をかけておいて、保存の例外時に `retry` するくらいのほうが見通しがいいんじゃないですかね。


CC BY-SA 3.0
2017-08-10T13:39:57.440
37106
2017-08-10T13:39:57.440
17037
37075
answer


[こちらの回答](https://ja.stackoverflow.com/a/37106/5793)にあるとおり、UUIDの重複が重複する可能性はきわめて低いです。ですので、重複を考慮する必要はありません。

> DB側でユニーク制約をかけておいて、保存の例外時に retry するくらいのほうが見通しがいい

何らかの事情で誤ったデータ登録がされる可能性もあるので前半部には同意ですが、後半には異論があります。例外は出っぱなしで死なせておけば良いです。

(ただし、制約違反が起きた時の一般的な対応として要求があるならその処理まで不要とは言いません。)

UUIDの重複を前提としてコントローラにリトライのコードを書くぐらいならモデルで重複チェックした方が遙かにマシです。


CC BY-SA 3.0
2017-08-15T02:05:57.220
37200
2017-08-15T02:05:57.220
5793
37075
answer