質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Ruby on Rails

Ruby on Railsは、オープンソースのWebアプリケーションフレームワークです。「同じことを繰り返さない」というRailsの基本理念のもと、他のフレームワークより少ないコードで簡単に開発できるよう設計されています。

Active Record

Active Recordは、一つのオブジェクトに対しドメインのロジックとストレージの抽象性を結合するデザインパターンです。

Q&A

解決済

2回答

1743閲覧

可変個数の画像を1カラムにJSONで保存するためのベストプラクティスが知りたい

退会済みユーザー

退会済みユーザー

総合スコア0

Ruby on Rails

Ruby on Railsは、オープンソースのWebアプリケーションフレームワークです。「同じことを繰り返さない」というRailsの基本理念のもと、他のフレームワークより少ないコードで簡単に開発できるよう設計されています。

Active Record

Active Recordは、一つのオブジェクトに対しドメインのロジックとストレージの抽象性を結合するデザインパターンです。

0グッド

1クリップ

投稿2019/05/21 16:57

編集2019/05/22 02:56

前提・実現したいこと

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 に書き込まれる値はどこに保持されているのでしょうか

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

MySQL に JSON で保存するにはどうすればいいんでしょうか?

素直に、「画像専用のモデル(画像1枚で1レコード)を立てる」という形にして、Campaignからはhas_manyでそのモデルをもたせる、という形にするほうが、Carrierwave的にもDBの正規化の面からも都合がいいと思います。

投稿2019/05/21 23:48

maisumakun

総合スコア145183

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2019/05/22 00:07 編集

回答ありがとうございます has_many は使っていないですが 画像ごとに CampaignImage という CarrierWave の Uploader をマウントしたクラスは作っています 作ってアップロードしてURLを取り出すだけですが… ちなみに ActiveRecord ではなく ActiveModel に対しても has_many ってできるんでしょうか その場合 foreign_key とかはどう扱えばいいのでしょうか まとめてJSONで保存して画像毎にテーブルを持ちたくない理由としては 保存したデータを読み出すAPI側では画像1つ1つは参照せずに全部JSONで返すだけ (JSONの中身を分解して見るのはアプリ側と今作ってる画面のみ) でAPIの負荷をなるべく抑えたいという要件もあって 画像ごとに別のテーブルに保存して毎回JSONを生成するより はじめからJSONでもっておいた方がパフォーマンス面でメリットがあるのです…
guest

0

ベストアンサー

とりあえずカラムの保存したければ
before_validation の最後で
self[:image] = @image
とすれば MySQL には入ると思います

  • [] : カラムのデータ
  • @変数: モデル内のローカル変数(同名であってもカラムとは実態が別)
  • .image, .image= :単なるメソッドでどの実体を読み書きするかは実装によります

デフォルトはカラムの読み書き(Serialize がかかれてると型変換もやってくれる)だけど
今回それをオーバーライドしてローカル変数に読み書きするようにしたために
カラムはずっと変化しない(new の場合 nil のまま) なのでエラーになったと思われます

投稿2019/05/25 11:50

編集2019/05/25 11:56
mypt

総合スコア170

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2019/05/28 03:55

ありがとうございます 保存できました! しかもちょうどわかってなかった部分の解説までしていただいて助かります
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問