前提・実現したいこと
Rails で言語ラベルつきの可変個数の画像アップローダーからきた画像データを
AWS S3 にアップロードしつつ MySQL の1カラムにURLを JSON 形式で保存したいです
Rails 的には1フォーム:1カラムに対応させてと分けたほうが都合がいいのですが
API側の都合で入力内容を1カラムにまとめたいです
発生している問題
ActionDispatch 型でとんできたパラメーターを
S3 にアップロードして URL を取得する部分はようやく動くようになったのですが
フォームからきたハッシュをコントローラー上でどう扱って最終的にどう JSON にしていいのかがわかりません
試したこと
view
<%= form_for @campaign do |f| %> <%= f.fields_for :image do |f| %> <%= f.file_field :default %><%= image_tag f.object.image.default %> <%= f.file_field :ja %> <%= image_tag f.object.image.ja %> :
のようにかいたところ
"image"=>{"default"=>#<ActionDispatch::Http::UploadedFile:...}
という形でとんできます
これを
@campaign = Campaign.new(params.require(:campaign).permit(image: I18n.available_locales.map(&:to_s)))
と最初コントローラー内でハッシュでデータをもとうとしたんですが
途中でエラー等がでてそのままビューに戻ってきたときに
@campaign.image がハッシュだと .default がないといわれてしまうのでオブジェクトで持たないと再表示できません
そこで以下のようなハッシュをオブジェクト化するためだけのクラスを定義して
class MultiLocale include ActiveModel::Model @@langs = ['default'] + I18n.available_locales.map(&:to_s) attr_accessor *@@langs # ここがよく意味がわかってないのですが # こうかいて serialize を定義すると勝手に JSON で保存してくれるらしい? class << self def dump(obj) obj.to_json if obj end def load(source) self.new(JSON.parse(source)) if source end end
Campaign モデル内で
serialize :image, MultiLocale def image=(val) super p image end
とパラメーターをうけとったときに MultiLocale オブジェクトとして保存するセッターをかいたんですが
デバッグ表示されたデータは
{"default" => {"tempfile"=>[], "original_filename"=>"150x150.png", "content_type"=>"image/png", "headers"=>"Content-Disposition: form-data; name=\"campaign[image][default]\"; filename=\"150x150.png\"\r\nContent-Type: image/png\r\n"}
とハッシュ型になってしまい ActionDispatch 型ではなくなってしまいます
ActionDispatch 型でないと CarrierWave に渡せないので
MultiLocale クラスに initializer を追加して
def image=(val) @image = MultiLocale.new(val) p @image end
というセッターを定義したところようやく @image の中身が
<MultiLocale @default=#<ActionDispatch::Http::UploadedFile:...>}
という形でパラメータを受け取ることができたので
before_validation 内で
tmp = {} @image.each{|k,v| image = CampaignImage.new # CarrierWave マウントクラス image.image = v if image.save tmp[k] = image.image.url else errors.add(:image, I18n.t('.invalid_image')) end } @image = MultiLocale.new(tmp)
とアップロードとURLの取得を行って成功した場合 @image の中身は
<MultiLocale @default="https://S3のURL" >
となり、バリデーションエラーでビューに戻ったとき、アップロードされた画像が表示できるようになりました
ただ途中でバリデーションエラーが出た場合等に ActionDispatch と変換された URL 文字列が混在することになり、
(たとえば 2 つめの画像保存に失敗した場合)
<MultiLocale @default="https://S3のURL" @ja=#<ActionDispatch::Http::UploadedFile:...> >
という状態になって ActionDispatch のままのものは画像が表示されません
①この状態でビューに戻った場合にもう1度 ActionDispatch から form にデータをセットする方法はないでしょうか?
②またこの状態で保存しようとすると
validates :image, presence: true
のバリデーションにひっかかって画像がないというバリデーションエラーになってしまいます
def image @image end
とゲッターを追加するとバリデーションは通るようになりますが今度は MySQL エラーで image の default value がないといわれます
@image に保存したい内容を MultiLocale 型でもっているのですが
どうも MySQL に保存するときにこの値をみてくれていないようなのです
MySQL に JSON で保存するにはどうすればいいんでしょうか?
画像以外のカラムはセッターをかいていなくて、MutiLocale 型から自動的に JSON で保存してくれますが
画像の場合だけセッターをかいてしまってるせいで保存されないみたいなのです
self.image = @image
をよんでもセッターをオーバーライドしてしまっているので @image が更新されてしまうだけで
MySQL にセットされる値をセットするにはどうすればいいんでしょうか
Rails にまだそれほどなれていなくて完全に自己流なので、明らかに変なことをやってる気がするのですが
ベストプラクティスを教えていただきたいです
とくに カラムのデータ @変数 セッターゲッター []でのアクセス あたりがあいまいで区別ができていないために
どこでどのデータにどの型が入っているかが把握できていなくてはまっています
まとめると
① ActionDispatch 型でもっている画像データをアップロードフォームに再セットしたい
(ローカルのファイルを勝手にセットすることはできないけど
Blob オブジェクトを作ることができればファイルフィールドにセットすることは可能みたいなのですが
どういう JS を生成すればいいかがわかりません)
② カラムと同名の @変数でもっている値を save 時に MySQL カラムに保存するにはどうすればいいでしょうか
③ そもそも途中で MultiLocale 型という独自モデルを定義して保持するというのが適切なやり方なのでしょうか
以上 3 点よろしくおねがいします
補足情報(FW/ツールのバージョンなど)
Rails 4.2.3 です
##追記
def image=(val) @image = MultiLocale.new(val) end
をオーバーライドしなければデフォルトではどういう挙動をするのでしょうか
@変数 にかきこめばそれ(をシリアライズしたもの)がカラムになる?
と思っていたのですがどうやら違うようです
save のときに MySQL に書き込まれる値はどこに保持されているのでしょうか
回答2件
あなたの回答
tips
プレビュー
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
退会済みユーザー
2019/05/22 00:07 編集