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

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

ただいまの
回答率

90.48%

  • Ruby on Rails 4

    2456questions

    Ruby on Rails4はRubyによって書かれたオープンソースのウェブフレームワークです。 Ruby on Railsは「設定より規約」の原則に従っており、効率的に作業を行うために再開発を行う必要をなくしてくれます。

Rails:fields_forを使って関連先のテーブルへアップロードされたファイルを保存する方法

解決済

回答 2

投稿

  • 評価
  • クリップ 0
  • VIEW 831

Uchikoba

score 1

Railsにお詳しい方のお知恵をお借しください……

親テーブル(workitems)の編集フォームからアップロードされたファイルを、
子テーブル(destination)へ保存しようとしているのですが、なかなか上手く行きません。

アップロードされるファイルはCSV(text/csv)のみです。

paperclipが有効という情報を見つけ、あちこち参考にしつつ試したのですが、
「contenttypeが不正」というエラーが回避できず……

仕方なく自力でbinary型の列へ保存する方法を行おうとしてるのですが、
こちらでも躓いてしまい途方に暮れております。

Model(~/app/models/workitem.rb , ~/app/models/destination.rb)
class Workitem < ActiveRecord::Base
  # rails g scaffold workitem name:string
  has_one :destination, :dependent => :destroy
  accepts_nested_attributes_for :destination
end

class Destination < ActiveRecord::Base
  # rails g model destination name:string workitem_id:integer upload_file:binary
  belongs_to :workitem
end

View(~/app/views/workitems/_form.html.erb)
<%= form_for @workitem, multipart: true do |f| %>
  <%= f.fields_for @workitem.destination do |dest_f| %>
    <%= dest_f.file_field :upload_file %>
  <% end %>
<% end %>

Controller(~/app/controllers/workitems_controller.rb)
class WorkitemsController < ApplicationController
  def new
    @workitem = Workitem.new
    @workitem.build_destination
  end

  def create
    # あれこれ試してみて自力でパラメータを作ればどうにかなるかと以下のコードを書きました

    # workitem_paramsを退避。ファイルがアップロードされなければ
    # こいつがこのまま使用されるので問題ないはず
    temp_workitem_params = workitem_params
    destination_attributes = workitem_params[:destination_attributes]
    upload_file = destination_attributes[:upload_file]
    # => ActionDispath::Http::UploadedFile

    if upload_file != nil
      # ファイルがアップロードされてれば
      if @workitem.destination != nil
        # 既存のアップロードファイルがあれば消す
        @workitem.destination.destroy
      end

      # アップロードされたファイルの情報をworkitem_param[:destination_attributes]に仕込む
      content = {}
      content[:filename]    = upload_file.original_filename
      content[:upload_file] = upload_file.read

      temp_workitem_params.delete('destination_attributes')
      temp_workitem_params['destination_attributes'] = content
    end

    @workitem = Workitem.new(temp_workitem_params)
    respond_to do |format|
      if @workitem.save
        # この中身はそのまま
      end
    end
  end

  def update
    # create と同じく、workitem_paramsからDispatch::Http::UploadedFileの情報を展開し、
    # destination_attributesの中身を差し替えて@workitem.updateを行っています
  end

  private
  def workitem_params
    params.require(:workitem).permit(:name, destination_attributes: [ :upload_file, :filename, :workitem_id ])
  end
end

上記のような構成にてデータの更新を行おうとすると、
ActiveModel::ForbiddenAttributesError という例外が発生してしまいます。

ストロングパラメータはテーブルのものと同じにしてあるのですが、
どこが原因なのか皆目検討もつかず……

サーバ環境は

  • CentOS 6.6
  • ruby 2.0.0p645
  • Rails 4.2.1

となっております。

workitemが登録・更新されるタイミングでファイルのアップロードも行いたいため
色々と調べてはみたのですが、これ以上はどうしもうよなく……
不足している情報があれば分かる範囲でお出ししますので、よろしくお願いいたします。

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

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

check解決した方法

0

workitem_paramsから:destination_attributesだけを取り出してDestinationを登録、workitem_paramsがら:destination_attributesを消す という力技で解決いたしました。

class WorkitemsController < ApplicationController
  def new
    @workitem = Workitem.new
    @workitem.build_destination
  end

  def edit
    @workitem.build_destination unless @workitem.destination
  end

  def create
    @destination = nil

    temp_workitem_params   = workitem_params
    destination_attributes = temp_workitem_params[:destination_attributes]
    upload_file            = destination_attributes[:upload_file]
    if upload_file != nil
      content = {}
      content[:filename   ] = upload_file.original_filename
      content[:upload_file] = upload_file.read
      @destination = Destination.new(content)
    end
    # 後述する@workitem.saveに影響するのでdestination_attributesを削除
    temp_workitem_params.delete('destination_attributes');

    @workitem = Workitem.new(workitem_params)
    respond_to do |format|
      if @workitem.save
        if @destination
          @destination.workitem_id = @workitem.id
          @destination.save
        end
      end
    end
  end

  def update
    temp_workitem_params   = workitem_params
    destination_attributes = temp_workitem_params[:destination_attributes]
    upload_file            = destination_params[:upload_file]
    if upload_file != nil
      if @workitem.destination != nil
        @workitem.destination.destroy
      end

      content = {}
      content[:filename   ] = upload_file.original_filename
      content[:upload_file] = upload_file.read
      @destination = Destination.new(content)
      @destination.workitem_id = @workitem.id
      @destination.save
    end
    # 後述する@workitem.updateに影響するのでdestination_attributesを削除
    temp_workitem_params.delete('destination_attributes');

    respond_to do |format|
      if @workitem.update(temp_workitem_params)
      end
    end
  end
end

なんと泥臭い……(´・ω・`)

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

0

もしかして、
 @workitem = Workitem.new(temp_workitem_params)
のところでコケてませんか?

だとしたら、attr_acessibleの設定がないだけなのでは?

class Workitem < ActiveRecord::Base
  # rails g scaffold workitem name:string
  has_one :destination, :dependent => :destroy
  accepts_nested_attributes_for :destination
  attr_accessible :name
end

class Destination < ActiveRecord::Base
  # rails g model destination name:string workitem_id:integer upload_file:binary
  belongs_to :workitem
  attr_accessible :upload_file, :filename
end
こんな感じ。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2015/09/17 18:16

    ご回答ありがとうございます。

    Rails4では、attr_accessibleではなく下記のように、ストロングパラメータによるパーミッション設定を行わねばならないようでして、設定そのものはできていると思います。

    ```lang-Ruby
    class WorkitemsController < ApplicationController
    private
    def workitem_params
    params.require(:workitem).permit(:name, destination_attributes: [ :upload_file, :filename, :workitem_id ])
    end
    end
    ```

    キャンセル

  • 2015/09/17 19:05

    あ、Rails4でしたね・・・すみません。

    キャンセル

関連した質問

同じタグがついた質問を見る

  • Ruby on Rails 4

    2456questions

    Ruby on Rails4はRubyによって書かれたオープンソースのウェブフレームワークです。 Ruby on Railsは「設定より規約」の原則に従っており、効率的に作業を行うために再開発を行う必要をなくしてくれます。