Ruby on Railsでcsvダウンロード機能の作り方
Ruby on Railsを使ってcsvファイルのダウンロード機能を作りたくて調べました。
大枠の手順
- routesを設定
- csvを受け付けるアクションをコントローラーに設定
- 対象データを抽出しcsvフォーマットのファイルへ渡す
- CSVデータを作成する
- 画面にダウンロードボタンを作成する
コントローラーにアクションを定義する
# controller/users_controller.rb def index @users = User.all respond_to do |format| format.html format.csv do filename = ERB::Util.url_encode('ユーザー一覧.csv') send_data render_to_string, filename: filename, type: :csv end end end
ERB::Util.url_encode
は引数に渡された文字列をURLエンコードしてくれる。これによって日本語や半角スペースをよしなにしてくれる
send_data
は動的に生成されたデータをダウンロードする。
render_to_string
は表示結果を文字列として取得するメソッド。
なので、これらを組み合わせる事でCSVファイルに出力する為の文字列を作ってダウンロードを可能にしている。
参考: https://docs.ruby-lang.org/ja/latest/class/ERB=3a=3aUtil.html
参考: http://railsdoc.com/references/send_data
参考: http://railsdoc.com/references/render_to_string
View
# views/users/index.haml = link_to 'CSVダウンロード' users_path(format: :csv)
ポイントはリンクを生成するpathにフォーマット: csvを指定してあげます。これによってコントローラー側で切り分けれるようになります。
csvファイル
# views/users/index.csv.ruby require 'csv' require 'nkf' csv_data = CSV.generate do |csv| csv << %w(id 名前 年齢) @users.each do |user| csv << [ user.id, user.name, user.age ] end end NKF::nkf('--sjis -Lw', csv_data)
CSV.generate
は文字列csv形式=カンマ区切りの文字列を生成してくれます。
NKF::nkf
は文字コードを強制的に変換する為のプログラムです。
【Rails】Strong Parametersで`param is missing...`エラーになる
こんちには。opiyoです。
ActionController::ParameterMissing in ImagesController#create param is missing or the value is empty: image def image_paprams params.require(:image).permit(:name, :picture) end end
こんな感じでエラーになるのだが、params
が空っぽだよと。
実際にparams[:image]
ってやるとnil
が返ってくる。
が、すげー凡ミスでpermit
て定義している:name
をview側に書いてなかった。
だから、来るはずのデータが来てなくてエラーになったのではなかろうかと思ってる。
<%= form_for(@image) do |f| %> <div class="form-group"> <%= f.text_field :name %> # ここを書いてなかったけど、書いたらエラーにならなくなった <%= f.file_field :picture %> <%= f.submit "画像を投稿する" %> </div> <% end %>
そんな感じ。
Docker For MacでPostgreSQL9.5系の環境を作ってみた!
こんばんは。opiyoです。
brew
でインストールするとなんだかPostgreSQL9.6系がインストールされてしまうのだけど、本番環境と同じバージョンをローカルに使いたくてウズウズしていたのだが...
Docker
使えば良いじゃんってアドバイス貰ったので教えてもらった
Docker For Macのインストール
https://www.docker.com/docker-mac
docker-compose.ymlを作る
docker-compose.ymlの場所を作る
$ mkdir -p ~/compose/postgresql95/ $ vi docker-compose.yml
PostgreSQL9.5を配置する場所を作る
$ mkdir ~/postgres95
docker-compose.ymlを修正
postgresql: image: postgres:9.5.8-alpine environment: POSTGRES_USER: taku POSTGRES_INITDB_ARGS: "--encoding=UTF8 --no-locale" LANG: ja_JP.UTF-8 ports: - "5432:5432" volumes: - /Users/taku/postgres95:/var/lib/postgresql/data #「/Users/taku/postgres95」の箇所を、さっき作った場所を指定する restart: always
やることは、
POSTGRES_USER
を各自設定するvolumes
のパスをさっき作った場所を指定する
image
の部分には、こちらのサイトに書いてあるように好きなバージョンを書けばおk
https://hub.docker.com/r/library/postgres/tags/
自動起動されているpostgresqlを止める
本当は9.6系と9.5系を共存させたいのだけど、上手くいかないので既にPostgreが動いている場合は止める。
私の場合は、brew
でインストールしてたので簡単だった。
$ brew services list Name Status User postgresql started taku $ brew services stop postgresql $ brew services list Name Status User postgresql stopped
パスを通す
$ sudo vi ~/.bash_profile export PGHOST=localhost $ env | grep HOST PGHOST=localhost
これは教えてもらうがままにやったけど、何してるのかいまいち分かってないぞ。
構築と起動
$ docker-compose up -d
でpsql -l -p5432
とかやればアクセスできるはず!
めちゃくちゃ簡単でした。
その他Dockerコマンド
ステータス
$ docker ps
停止
$ docker stop CONTAINER_NAMES
コンテナに入る
$ docker exec -it CONTAINER_NAMES /bin/bash
こんな感じ。
【Rails】ファイルのフルパス、ファイル名を取得する
過去データとかでファイルを一括で読み込みたい場合で使える技です。
> files = Dir.glob("/Users/taku/rails/gist/test/*.xls") => ["/Users/taku/rails/gist/test/1.xls", "/Users/taku/rails/gist/test/2.xls", "/Users/taku/rails/gist/test/3.xls", "/Users/taku/rails/gist/test/4.xls", "/Users/taku/rails/gist/test/5.xls"]
これでfiles
をグルグル回して一つ一つのファイルを読み込んでやれば色々処理できますね。
取り込み処理も出来るし、csvを作ったりも出来るし。
glob
を使うと拡張子で絞り込めるので、結構便利ですね。
> files = Dir.entries("/Users/taku/rails/gist/test/") => [".", "..", ".DS_Store", "1.csv", "1.txt", "1.xls", "1.xlsx", "2.csv", "2.txt", "2.xls", "2.xlsx", "3.csv", "3.txt", "3.xls", "3.xlsx", "4.xls", "5.xls"]
単純にディレクトリの中にあるファイル名だけ取得したい場合はentries
を使うそうです。
【Ruby】配列の中身が重複しているかをチェックする方法(select、find)
csvのデータを取り込み別のcsvへ吐き出す処理をしていたのですが、値が重複していることに気がつきました。
こんな感じ。
data = [] inport = CSV.read("./inport.csv") inport.each do |c| data << c end CSV.open("./export.csv", "w") do |export| data.each do |d| export << d end end
この中で取り込んだデータが重複しているかどうかチェックする方法が分からず、グルグル回さないとダメなのかと思っていたのですが良い方法がありました。
> ff6 = [["Tina", 1],["Rokku", 2],["Edoga", 3],["Masshu", 4]] => [["Tina", 1], ["Rokku", 2], ["Edoga", 3], ["Masshu", 4]] > ff6.select{|f| f[0] == "Masshu"} => [["Masshu", 4]] irb(main):003:0>
select
を使うと合致したものだけを取ってきてくれます。
なので重複しているかチェックしたい場合は、size
を使って数を調べてやれば良いです。
> ff6.select{|f| f[0] == "Masshu"}.size => 1 > if ff6.select{|f| f[0] == "Masshu"}.size > 0 > # 処理 > end
めでたし、めでたし
追記
find
を使った方が早いとのこと。
> ff6 => [["Tina", 1], ["Rokku", 2], ["Edoga", 3], ["Masshu", 4]] > ff6.find {|ff| ff[0] == "Masshu" } => ["Masshu", 4] > ff6.find {|ff| ff[0] == "Masshu" }.size => 2
select
との違いは、見つけた最初の配列を一つだけ返す。ってことですかね。
例えば、
# 「カイエン 4」を追加します > ff6 = [["Tina", 1],["Rokku", 2],["Edoga", 3],["Masshu", 4], ["Kaien", 4]] => [["Tina", 1], ["Rokku", 2], ["Edoga", 3], ["Masshu", 4], ["Kaien", 4]] # selectの場合 > ff6.select {|ff| ff[1] == 4 } => [["Masshu", 4], ["Kaien", 4]] # findの場合 > ff6.find {|ff| ff[1] == 4 } => ["Masshu", 4]
最初に見つけた一つを返すんだから、そりゃー早いは。
今回やりたいことは、重複しているかのチェックだから1件でもあればNG。
ってことでfind
を使うことにした。
【bootstrap3】ナビゲーションバー(Navbar)で起きたレイアウト崩れと右寄せへの対応方法
こんばんは。opiyoです。
結論としては勝手な解釈をして余計なことしてた!に尽きるのだけど、同じようなハマり方すると時間もったい無いので共有です。
<header> <nav class="navbar navbar-inverse"> <div class="container-fluid"> <div class="navbar-header"> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbarEexample1"> <span class="sr-only">Toggle navigation</span> <% if logged_in? %> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> <% else %> <span class="icon-bar"></span> <span class="icon-bar"></span> <% end %> </button> <a class="navbar-brand" href="#"> <%= link_to "RChannel", root_path, id: 'logo' %> </a> </div> <div class="collapse navbar-collapse" id="navbarEexample1"> <ul class="nav navbar-nav navbar-right"> <% if logged_in? %> <li class="inline-block"><%= link_to "キーワード登録", new_user_keyword_path(user_id: @current_user.id) %></li> <p class="navbar-text inline-block"><%= @current_user.name %> さん</p> <li class="inline-block"><%= link_to "マイリスト", my_list_topic_path(id: @current_user.id) %></li> <li class="inline-block"><%= link_to "ログアウト", logout_path, method: :delete %></li> <% else %> <li class="inline-block"><%= link_to "アカウント登録", new_user_path %></li> <li class="inline-block"><%= link_to "ログイン", login_path %></li> <% end %> </ul> </div> </div> </nav> </header>
ポイントはサンプル通りそのまま記載してください!
で僕が何をしていたのかご覧ください。
画面小さくした時に文字が隠れない
header側にもリンクが必要だと思ったんだよ。
<div class="navbar-header"> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbarEexample4"> <span class="sr-only">Toggle navigation</span> <% if logged_in? %> <span class="icon-bar"><%= link_to "キーワード登録", new_user_keyword_path(user_id: @current_user.id) %></span> <span class="icon-bar"><%= link_to "マイリスト", my_list_topic_path(id: @current_user.id) %></span> <span class="icon-bar"><%= link_to "ログアウト", logout_path, method: :delete %></span> <% else %> <span class="icon-bar"><%= link_to "アカウント登録", new_user_path %></span> <span class="icon-bar"><%= link_to "ログイン", login_path %></span> <% end %> </button> <a class="navbar-brand" href="#"> <%= link_to "RChannel", root_path, id: 'logo' %> </a> </div>
最初こんな感じで.navbar-header
のspan
タグにもリンクが必要だと思って記載していたのですが、これやるとレイアウトが崩れます!
なので、span
タグには何も書いてはいけません!
左寄せが出来ない
<div class="collapse navbar-collapse" id="navbarEexample1"> <ul class="nav navbar-nav"> <% if logged_in? %> <li class="inline-block navbar-right"><%= link_to "キーワード登録", new_user_keyword_path(user_id: @current_user.id) %></li> <p class="navbar-text inline-block navbar-right"><%= @current_user.name %> さん</p> <li class="inline-block"><%= link_to "マイリスト", my_list_topic_path(id: @current_user.id) %></li> <li class="inline-block"><%= link_to "ログアウト", logout_path, method: :delete %></li> <% else %> <li class="inline-block navbar-right"><%= link_to "アカウント登録", new_user_path %></li> <li class="inline-block navbar-right"><%= link_to "ログイン", login_path %></li> <% end %> </ul> </div>
サンプル見ると、p
タグにnavbar-right
設定しているから真似してたのだけど全然右寄せにならない。
で、Chromeで確認してみると
分かりづらいけど、既に領域が固定されているからfloat
かけたって無駄。
なので、上の要素div
タグに対して.navbar-right
を設定してやることでうまくいった!
まとめ
勝手な解釈はせずに、基本に忠実にやりましょう!
【Rails】XX件以上登録できないようにする独自バリデーションメソッドを設定する方法
こんばんは。opiyoです。
Railsアプリで「XX以上は登録できない!」というバリデーションを設定したかったので、やり方を調べてみました。
やりたいこと
Keyword
というテーブルにデータを登録する処理で、5つまでしか登録できないようにバリデーションを設定します。
もし5つ以上の登録を行おうとした場合はsave
でエラーになるようにします。
Model
class Keyword < ApplicationRecord validates :title, presence: true validates :url, presence: true validate :check_count def check_count errors.add(:keyword, "は5つまで登録可能です。") if Keyword.count >= 5 # errors.add(:keyword, "は5つまで登録可能です。") if Keyword.where(user_id: self.user_id).count >= 5 end end
モデルにバリデーションメソッドを定義します。
validate
にメソッド名を記述し、そのメソッド内でKeyword
の件数を取得しif
文を記述します。
もし5件以上あれば、errors
の中に名前とエラーメッセージを追加します。
またコメントアウトしている部分ですが、self
を使うことで自分が登録したデータのみを対象とすることが出来ます。こうやって使うことが基本になりそうですね。
また、errors
の正体についてはActiveModel::Errors
で詳細はこちらを。
Controller
class KeywordsController < ApplicationController def create @keyword = Keyword.new(keyword_params) @keyword.check_count if @keyword.save redirect_to root_url else render 'new' end end private def keyword_params params.require(:keyword).permit(:title, :url) end end
コントローラーではKeyword
をインスタンス化して、メソッドを呼び出すだけです。
5件以上ある場合はerrors
にエラーが追加されているので、save
する時に失敗します。
という流れです。
【Bootstrap】navbarの背景色、文字色の悩みはこれ一発で解決だわー
何か作る時bootstrapにお世話になることあると思うのですが、cssいじっただけだと何故か上手くいかない。
例えば今回のnavbar
ですね。
ナビゲーションメニューがかっこよく決まらないとモチベーションが全く上がらないので、いろいろ探していたらあったわー。神様が。 https://work.smarchal.com/twbscolor/
自分の好きなカラーコードを入力したら勝手にcssのコード吐き出してくれる。
scss
sass
less
css
全部あるよ。
haml
.navbar.navbar-default.navbar-static-top .container %button.navbar-toggle(type="button" data-toggle="collapse" data-target=".navbar-responsive-collapse") %span.icon-bar %span.icon-bar %span.icon-bar %a.navbar-brand(href="#") ホーム .navbar-collapse.collapse.navbar-responsive-collapse %ul.nav.navbar-nav %li= link_to "Link 1", "/path1" %li= link_to "Link 2", "/path2" %li= link_to "Link 3", "/path3"
scss
$bgDefault : #dd4444; $bgHighlight : #e8350e; $colDefault : #ecf0f1; $colHighlight : #ecdbff; $dropDown : false; .navbar-default { background-color: $bgDefault; border-color: $bgHighlight; .navbar-brand { color: $colDefault; &:hover, &:focus { color: $colHighlight; }} .navbar-text { color: $colDefault; } .navbar-nav { > li { > a { color: $colDefault; &:hover, &:focus { color: $colHighlight; }} @if $dropDown { > .dropdown-menu { background-color: $bgDefault; > li { > a { color: $colDefault; &:hover, &:focus { color: $colHighlight; background-color: $bgHighlight; }} > .divider { background-color: $bgHighlight;}}}}} @if $dropDown { .open .dropdown-menu > .active { > a, > a:hover, > a:focus { color: $colHighlight; background-color: $bgHighlight; }}} > .active { > a, > a:hover, > a:focus { color: $colHighlight; background-color: $bgHighlight; }} > .open { > a, > a:hover, > a:focus { color: $colHighlight; background-color: $bgHighlight; }}} .navbar-toggle { border-color: $bgHighlight; &:hover, &:focus { background-color: $bgHighlight; } .icon-bar { background-color: $colDefault; }} .navbar-collapse, .navbar-form { border-color: $colDefault; } .navbar-link { color: $colDefault; &:hover { color: $colHighlight; }}} @media (max-width: 767px) { .navbar-default .navbar-nav .open .dropdown-menu { > li > a { color: $colDefault; &:hover, &:focus { color: $colHighlight; }} > .active { > a, > a:hover, > a:focus { color: $colHighlight; background-color: $bgHighlight; }}} }
【Rails】ファイルをデータベースに保存する方法
ファイルを保存する方法はRailsチュートリアルとかでは、CarrierWave
というgemを使う。
だがちょっとしたアプリとか作りたい時には、DBに保存した方が都合がいい時もあるはず。
gemを使う方法は別の機会でまとめるとして、今日はDBに保存する方法を。
モデルを作る
$ rails g model Image name:string picture:binary invoke active_record create db/migrate/20171215092903_create_images.rb create app/models/image.rb
# migrate class CreateImages < ActiveRecord::Migration[5.0] def change create_table :images do |t| t.string :name t.binary :picture t.timestamps end end end
$ rails db:migrate == 20171215092903 CreateImages: migrating ===================================== -- create_table(:images) -> 0.3369s == 20171215092903 CreateImages: migrated (0.3370s) ============================
コントラーラーを作る
$ rails g controller Images Running via Spring preloader in process 7555 create app/controllers/images_controller.rb invoke erb create app/views/images invoke helper create app/helpers/images_helper.rb invoke assets invoke coffee create app/assets/javascripts/images.coffee invoke scss create app/assets/stylesheets/images.scss
# images_controller.rb class ImagesController < ApplicationController def new @image = Image.new @image.name = "hogehoge" end def create @image = Image.new(image_paprams) @image.name = params[:image][:picture].original_filename @image.picture = params[:image][:picture].read if @image.save redirect_to root_path else render 'new' end end private def image_paprams params.require(:image).permit(:name, :picture) end end
ビューを作る
# new.html.erb <%= form_for(@image) do |f| %> <div class="form-group"> <%= @image.name %> <%= f.file_field :picture %> <%= f.submit "画像を投稿する" %> </div> <% end %>
↓htmlに変換されると...
<form class="new_image" id="new_image" enctype="multipart/form-data" action="/images" accept-charset="UTF-8" method="post"><input name="utf8" type="hidden" value="✓"><input type="hidden" name="authenticity_token" value="ZgO8WthU8KtI/rspO/oVgHavcwUUVQl1jDwm0DQAsZXwCHYLO02KUqcX+Kt54gKlUlQP4sqDe3NUltVWjtZQDw=="> <div class="form-group"> hogehoge <input type="file" name="image[picture]" id="image_picture"> <input type="submit" name="commit" value="画像を投稿する" data-disable-with="画像を投稿する" disabled=""> </div> </form>
まとめ
こんな感じでしょうか。 とりあえず言いたいことは、
binary
型のカラムをモデルに用意します。form_for
でf.file_field
を使います。params[:image][:picture].read
で画像データが保存できる
form_for
がよしなにやってくれるのは分かるのだけど、いまいち理解が浅いので色々動かしてみて勉強しよう。
で、実際に登録された画像データはこんな感じです。
$ rails c > Image.all Image Load (1.6ms) SELECT "images".* FROM "images" => #<ActiveRecord::Relation [#<Image id: 1, name: "17704-koala-bear-pv.jpg", picture: "\xFF\xD8\xFF\xE0\x00\x10JFIF\x00\x01\x01\x01\x00H\x00H\x00\x00\xFF\xDB\x00C\x00\n\a\a\b\a\x06\n\b\b\b\v\n\n\v\x0E\x18\x10\x0E\r\r\x0E\x1D\x15\x16\x11...", created_at: "2017-12-15 09:52:29", updated_at: "2017-12-15 09:52:29">]>
【Ruby on Rails】月の最初の日、最終日はどうやって取得する?
画面から入力された日付の月が何日までなのか取得する方法が分からなかったので調べてみました。
月の最終日
irb> Date NameError: uninitialized constant Date Did you mean? Data from (irb):9 from /Users/taku/.rbenv/versions/2.3.3/bin/irb:11:in `<main>' irb> require "date" => true irb> date = Date.today => #<Date: 2017-12-13 ((2458101j,0s,0n),+0s,2299161j)> irb> date.end_of_month NoMethodError: undefined method `end_of_month' for #<Date: 2017-12-13 ((2458101j,0s,0n),+0s,2299161j)> from (irb):13 from /Users/taku/.rbenv/versions/2.3.3/bin/irb:11:in `<main>'
むむむ何でだ。
end_of_month
というメソッドを使うと月の最終日が取れる。取れたんだけど...
調べてみると、end_of_month
はRailsで定義されたメソッドみたい。
なのでrails c
で検証スタート。
> rails c > date = Date.today => Wed, 13 Dec 2017 > date.end_of_month => Sun, 31 Dec 2017 > date.end_of_month.to_s => "2017-12-31"
月の最初の日
月の最初の日を求める時はbeginning_of_month
を使う
> date = Date.today => Wed, 13 Dec 2017 > date.beginning_of_month => Fri, 01 Dec 2017 > date.beginning_of_month.to_s => "2017-12-01"
その他のちょっとしたあれこれ
> Date.today.year => 2017 > Date.today.month => 12 > Date.today.day => 13 > Date.today.wday => 3 # 「0」が日曜日なので「3」は水曜日