概要
みなさんこんにちはcandleです。今回はrailsのform_forを使って、railsプロジェクトのpublicディレクトリにpdfファイルをアップロードしてみましょう。
form_forは基本的にデータベースのカラムに依存したフォームなので、pdfをアップロードするには一手間掛ける必要があります。
簡単なやり方として、form_tagがありますが、こちらは少し古いのでform_forを使って画像アップロードのフォームを作成しましょう。
前提
今回もtwitterbootstrap gemとscaffoldを使って説明します。別段、bootstrap gemが入っている必要がありませんが、見栄えが多少異なります。
rails4のtwitterbootstrap gem でscaffoldを作る方法は下のURLにあるので、もし、同じように進行したい場合は下の記事を参考にしてください。
https://joppot.s3.amazonaws.com/2014/05/27/1455
プロジェクトがすでに作成されている
scaffoldでデータベースを用意する
Myfile scaffoldを作成して、pdfをアップロードしてみましょう。
プロジェクトの作成は省きます。気になる方は上のURLの記事を見てください。
Myfileモデルのscaffoldを作成する
早速、scaffoldを作ります。下のコマンドを実行しましょう。
rails g scaffold Myfile title:string filename:string comment:string
マイグレーションファイルが作成されますので、それをデータベースに反映させます。
rake db:migrate
twitterbootstrap gemのスタイルを反映させる
もしも、twitterbootstrap gemがインストールされているなら、次のコマンドを入力して、テーマを反映させます。
bootstrapのレイアウトを適応させます。あまり良い選択肢ではないかもしれませんが、app/views/layouts/application.html.erbに上書きします。
rails g bootstrap:layout application
もちろん、application.html.erbを上書きしてよいのか聞いてきますので許可しましょう。
bootstrapのテーマをMyfileコントローラーのviewに反映させるには下のコマンドを打ちます。
今回はMyfileモデルを作ったので、Myfilesを指定します。
rails g bootstrap:themed Myfiles
これを実行すると、テーマが自動的に書き込まれます。
実行すると分かる様に、すでにscaffolldで作成したファイルが存在するので、コンフリクト(衝突)が起きてしまいます。上書きをそれぞれ許可しましょう。質問されたら「y」と答えれば良いです
これでスキャホールドが完成しました。
pdfがアップロードできる様に編集する
サーバを起動します。
bundle exec rails s
下のURLに移動します。
http://localhost:3000/myfiles/
移動したら、「New」ボタンを押しましょう。下の様な画面が現れると思います。
このフォームで編集すべき場所は2つあります。
1つは、pdfファイルをアップロードするフィールドを作る事
2つ目はfilenameは自分で登録するのではなく、pdfファイルの名前を入れるので、このフィールドは必要ない事です。
Myfile viewを編集する
ではviewから編集していきましょう。
/app/views/myfiles/_form.html.erbを開きます。
ファイルの下の部分を
<div class="form-group"> <%= f.label :filename, :class => 'control-label' %> <div class="controls"> <%= f.text_field :filename, :class => 'form-control' %> </div> </div>
下の様に変更します。
<div class="form-group"> <%= f.label :file, :class => 'control-label' %> <div class="controls"> <%= f.file_field :file %> </div> </div>
上の変更点は2つです。1つは「f.text_field」を「f.file_field」に変更した事。
もう1つはハッシュを「:filename」から「:file」に変更したことです。
なぜ、ハッシュを変えたか説明します。
form_forはデータベースに紐づいたフォームです。ハッシュ:filenameはデータベースのfilenameカラムに紐づいています。仮に、ハッシュを:filenameのままファイルをアップロードすると、恐らく、pdfファイルデータをそのままデータベースに収めようとします。もちろん、エラーがでてしまいます。
そこで、データベースのカラムに存在しない仮のハッシュ:fileを指定します。
これでmyfileのviewの変更はおしまいです。
Myfile Modelを編集する
先ほどviewで作成した:fileはデータベースのカラムにありません。なので、例えば、ストロングパラムスを使いたい場合は何らかの設定をする必要があります。
やりたいことはこういうことです。「データベースにはカラムはないけれど、form_forで投げてきたpdfファイルをストロングパラムスで受け入れたい」
railsは便利で、データベースには収めないけど、値を受ける様に設定できます。
/app/models/myfile.rbを開きます。Myfileクラスの中に下の内容を書き込みます。
attr_accessor :file
こんな感じですね。
モデルの編集は以上です。
Myfileコントローラーでフォームの値を受け取る
今度は/app/controllers/myfiles_controller.rbを開きます。
myfiles_controller.rbの一番上に、下のコードを宣言しておいてください。後々使います。
require 'Kconv'
myfile_params関数を編集する
ストロングパラムスで受け入れる要素を変更します。
フォームから直接:filenameを受けなくなったので、
下の内容を
def myfile_params params.require(:myfile).permit(:title, :filename, :comment) end
下のように:fileに変更します。
def myfile_params params.require(:myfile).permit(:title, :file, :comment) end
先ほど、modelで:fileをatter_accessor足し加えたのでエラーはでません。
createアクションを編集する
下の部分を
def create @myfile = Myfile.new(myfile_params) respond_to do |format| if @myfile.save format.html { redirect_to @myfile, notice: 'Myfile was successfully created.' } format.json { render action: 'show', status: :created, location: @myfile } else format.html { render action: 'new' } format.json { render json: @myfile.errors, status: :unprocessable_entity } end end end
下の様にします。
def create @myfile = Myfile.new(myfile_params) file = myfile_params[:file] file_name = file.original_filename @myfile.filename= file.original_filename result = uploadpdf(file,file_name) respond_to do |format| if result=="success" && @myfile.save format.html { redirect_to @myfile, notice: 'Myfile was successfully created.' } format.json { render action: 'show', status: :created, location: @myfile } else deletepdf(file_name) format.html { render action: 'new' } format.json { render json: @myfile.errors, status: :unprocessable_entity } end end end
最初のfile変数への代入は先ほど設定したストロングパラムスから:fileだけを取り出して代入しています。
@myfile.fileへの代入はpdfのファイル名を収めています。
uploadpdf関数は自前で作りました。下の関数をmyfileコントローラーのprivate関数の中に入れてください。
def uploadpdf(file_object,file_name) ext = file_name[file_name.rindex('.') + 1, 4].downcase perms = ['.pdf'] if !perms.include?(File.extname(file_name).downcase) result = 'アップロードできるのはpdfファイルのみです。' elsif file_object.size > 10.megabyte result = 'ファイルサイズは10MBまでです。' else File.open("public/#{file_name.toutf8}", 'wb') { |f| f.write(file_object.read) } result = "success" end return result end
こんな感じですね。
このuploadpdf関数はpublicフォルダの中にpdfをアップロードしています。
もう1つprivateの中にdeletepdf関数を書いておきましょう。これは、データベースへデータの保存が失敗した時、アップロードしたpdfファイルの削除をする関数です。
def deleteipdf(file_name) File.unlink "public/"+file_name.toutf8 end
以上でpdfのアップロードができるようになりました。
早速やってみましょう。
pdfをアップロードする。
新しくファイルをアップロードする画面に行きます。必要項目を入力して、pdfファイルを選びます。
「Create Myfile」を押すと、画像がpublicフォルダにpdfがアップロードされて、データベースのfilenameカラムにpdfのファイル名が収まります。
うまくいきましたね。
まとめ
pdfファイルの削除や更新はまた別にコーディングする必要があります。ただ、これの応用なので頑張ってください。