QA@IT

Paperclipで生成する画像のファイル名を記録したい

2996 PV

Paperclipで生成する画像ファイル名に:hashを含めています。この:hashの生成規則(:hash_data)は今後変える予定があるので、保存時の実際の画像ファイル名を記録したいと思っています。以下が使用しているhas_attached_file関数になります。

  has_attached_file :picture,
    hash_secret: 'sample',
    hash_data: ":class/:attachment/:id",
    path: "/:style/:hash.:extension",
    url: ":s3_domain_url"

そこで当該モデルに画像ファイル名を保存するためのpicture_save_file_nameというカラムを作り、以下のコードを当該モデルに挿入したのですが、saveがループして正常に動作しませんでした。どのようにすれば正常に記録できるでしょうか?

  after_save :update_picture_save_file_name
  def update_picture_save_file_name
    self.picture_save_file_name = File.basename(self.picture.url)
    self.save!
  end

saveの数をカウントしsaveがネストしている時は処理しないという方法もあるとは思うのですが、スマートでは無い気がして躊躇しています。

ご助言頂けると嬉しいです。

回答

Paperclip は使ったことないので間違っていたらすみませんが、問題を整理すると、

  1. id を含む値を DB に保存したい
  2. id は保存後にしかわからない
  3. 保存後のコールバックで再度保存すると、コールバックが無限ループしてしまう

ということだと思います。
解決策を 2 つ考えてみました。
(Paperclip を使った検証はしていないので、動作しないかもしれません)

コールバックをスキップする

update_column というメソッドを使うと、バリデーションとコールバックをスキップして DB に値を保存します (updated_at も更新されません)。これを after_save の中で利用すれば、再度 after_save が実行されることはないので無限ループにはなりません。

参考: http://api.rubyonrails.org/classes/ActiveRecord/Persistence.html#method-i-update_column

after_save :update_picture_save_file_name
def update_picture_save_file_name
  update_column :picture_save_file_name, File.basename(self.picture.url)
end

ただし、この場合 save するごとに INSERT か UPDATE が 2 回発行されるので、やや効率が悪いです。

id を保存前に決定する

id を保存前に決定できれば、保存前に picture_save_file_name が決定できるので、INSERT か UPDATE は 1 回で済みます。
MySQL で AUTO_INCREMENT な列を使ってる場合はこの方法はとれませんが、たとえば PostgreSQL を使っているなら、下記のように書けます。

# PostgreSQL の nextval 関数を使って、使用可能な id の値を取得
# 主キーのシークエンスの名前 (posts_id_seq) は適宜変更してください
def self.next_id
  connection.execute("SELECT nextval('posts_id_seq')").first["nextval"].to_i
end

before_save do
  self.id ||= self.class.next_id
  self.picture_save_file_name = File.basename(self.picture.url)
end
編集 履歴 (0)
  • 問題を一般化して下さった上、的確なご回答ありがとうございます。
    無事update_columnを使用し問題が解決しました。恐縮です、もっとAPI Document読み込んでみます・・・!
    二つ目の方法、確かに綺麗ですね。複数のセッションが走ってても大丈夫とのことで、色々使えそうです。
    この度は本当にありがとうございました!精進します!
    -
ウォッチ

この質問への回答やコメントをメールでお知らせします。