- 投稿日:2019-10-12T23:45:29+09:00
Rails、Slimでflashメッセージがうまくだせない
Slimで新規登録に成功したらフラッシュメッセージを表示してindexに戻りたいのに次のように書いたらエラーが出る。
classを書かずにやると素テキストで成功メッセージが出るのでコントローラー側は大丈夫なはず。
app/controllers/blogs_controller.rbdef create @blog = Blog.create(blog_params) redirect_to blogs_path flash[:success] = 'add success' endindex.html.slim= flash[:success], class: 'alert alert-primary' h1 Blog articles = link_to '新規登録', new_blog_path, class: 'btn btn-primary' - @blogs.each do |blog| br = blog.id br = blog.title br = blog.articleerrorsyntax error, unexpected ':' ..._safe((flash[:success], class : 'alert alert-primary'))).to_... ... ^
いろいろぐぐり、試行錯誤した。
classの書き方が違っていた。
link_toのクラスの書き方が、「class: 'btn btn-primary'」だったから同じだと思っていたが違うみたい。ネットでいろいろぐぐったけど、Slimでflashのclassの書き方の参考が見つけられなかった。
これでエラーが取れたけど、ずっとフラッシュの緑枠がある。.alert.alert-success = flash[:success] h1 Blog articles勘違いしていた。if文を入れないと常にフラッシュが出てしまう。
flashでるときだけ値をコントローラーから勝手に渡してくれるような気がしていた。
if文を書く。
これでOK。- if flash[:success] .alert.alert-success = flash[:success] h1 Blog articles
- 投稿日:2019-10-12T21:50:43+09:00
Gmailを用いた ActionMailer の使い方
はじめに
ActionMailerの実装は割と簡単にできたのですが、エラーコードが出ずにGmailの設定でかなりの時間を費やしてしまいました。
次にやるときには時間がかけずにすむようにまとめておきたいと思います。コーディング
コマンド(自動生成)
rails g mailer UserMailer create app/mailers/user_mailer.rb create app/mailers/application_mailer.rb invoke erb create app/views/user_mailer create app/views/layouts/mailer.text.erb create app/views/layouts/mailer.html.erb invoke test_unit create test/mailers/user_mailer_test.rb create test/mailers/previews/user_mailer_preview.rbMailer
app/mailers/application_mailer.rbclass ApplicationMailer < ActionMailer::Base default from: "from@example.com" layout 'mailer' endapp/mailers/user_mailer.rbclass UserMailer < ApplicationMailer default from: '~~~@gmail.com' # ここのメソッド名をメール文面の view ファイルの名前にする。 # user と url は呼び出し元の コントローラーのメソッドから渡される。 def user_create(user, url) # @user, @url に値を入れて view に渡す。 @user = user @url = url mail(to: "user.name", subject: "ユーザーを新規登録しました。") endController
user_controller.rbclass UsersController < ApplicationController def index end def new @user = User.new end def create @user = User.new(user_params) if @user.save @url = request.url + "/" + @user.id.to_s NotificationMailer.user_create(@user, @url).deliver redirect_to(user_path(@user)) else render :new end end def show @user = User.find(params[:id]) end def edit @user = User.find(params[:id]) end def update @user = User.find(params[:id]) if @user.update(user_params) @user = current_user @url = request.url NotificationMailer.user_update(@user, @url).deliver redirect_to(user_path(@user)) else render :new end end endView
メール本文のテンプレートはデフォルトで二種類の形式が提供されています。
views/user_create/user_create.html.erb<h1>UserMailer#user_create</h1> <p> <%= @user %> 様 </p> <p> <%= @url %>, find me in app/views/user_mailer/user_create.html.erb </p>views/user_create/user_create.text.erbUserMailer#user_create <%= @user %> 様 <%= @url %>, find me in app/views/user_mailer/user_create.text.erbGmail googleの設定(重要)
google 二段階認証プロセスの設定
https://myaccount.google.com/signinoptions/two-step-verification/enroll-welcomegoogle アプリパスワードの作成
https://security.google.com/settings/security/apppasswordsconfig/initializers/mail.rbActionMailer::Base.delivery_method = :smtp ActionMailer::Base.smtp_settings = { # address のところは変更しないので注意。自分はここでハマりました。 address: 'smtp.gmail.com', domain: 'gmail.com', port: 587, # user_name は自分のメールアドレスを記載。 user_name: 'Gmail のメールアドレス', # password は作成したアプリパスワードを記載。 password: 'Gmail のパスワード', authentication: 'plain', enable_starttls_auto: true }まとめ
駆け出しのエンジニアですので、何か不備がありましたらご連絡をお願いします。
参考
https://railsguides.jp/action_mailer_basics.html
https://qiita.com/hirotakasasaki/items/ec2ca5c611ed69b5e85e
https://thr3a.hatenablog.com/entry/20171202/1512175059
- 投稿日:2019-10-12T21:15:10+09:00
文字列中の文字の出現回数を調べる(Ruby)
最近必要になったので備忘録程度に。
ioowrrrrnxibop上記のような文字列中に文字が何回出現しているか調べます。
次のようなコードで実装できます。
str = "ioowrrrrnxibop" count_hash = str.split("").each_with_object(Hash.new(0)){ |char,hash| hash[char] += 1 } # count_hash : {"i"=>2, "o"=>3, "w"=>1, "r"=>4, "n"=>1, "x"=>1, "b"=>1, "p"=>1}(2019/10/13) 0:51追記
コメントでご指摘いただきましたので追記します。
count_hash = str.split("").each_with_object(Hash.new(0)){ |char,hash| hash[char] += 1 }は次のように書くこともできます。
# charsを用いた場合 count_hash = str.chars.each_with_object(Hash.new(0)){ |char,hash| hash[char] += 1 } # each_charとwith_objectを用いた場合 count_hash = str.each_char.with_object(Hash.new(0)){ |char,hash| hash[char] += 1 }出現回数順に並べた二次元配列を作成するときは下記のコードでできます。
array = count_hash.sort_by{ |key,value| [-value,key] } # array : [["r", 4], ["o", 3], ["i", 2], ["b", 1], ["n", 1], ["p", 1], ["w", 1], ["x", 1]]今回は出現回数で降順に、出現回数が同じ場合はアルファベット順で昇順に並んだ配列を作っています。
transposeすれば出現回数だけの配列を取り出せます。
new_array = array.transpose # new_array[0] : ["r", "o", "i", "b", "n", "p", "w", "x"] # new_array[1] : [4, 3, 2, 1, 1, 1, 1, 1]参考記事
- 投稿日:2019-10-12T21:13:34+09:00
Rails ファイル添付機能 複数モデルでの共通化
はじめに
DRYの考え方を意識しはじめ、効率的なコーディングだと感じたので備忘録としてまとめます。
構成
UserモデルとPostモデルの両方にファイル添付機能を持たせたいとき、
Attachment_fileモデルに添付ファイルを格納して、
中間テーブルとしてUser_attachment_fileモデルとPost_attachment_fileモデルを設ける。
- Attachment_fileモデル
- Userモデル
- User_attachment_fileモデル
- Postモデル
- Post_attachment_fileモデル
Gemfile
下記を書いて、bundle install
gem 'carrierwave'コーディング
Model
- Attachment_fileモデル
- Userモデル
- User_attachment_fileモデル
- Postモデル
- Post_attachment_fileモデル
attachment_file.rbmount_uploader :file, FileUploader after_destroy :destory_intermediate has_many :user_attachment_files has_many :users, through: :user_attachment_files has_many :post_attachment_files has_many :posts, through: :post_attachment_files def destory_intermediate user_attachment_files.destroy if user_attachment_files.present? post_attachment_files.destroy if post_attachment_files.present? enduser.rbclass User < ActiveRecord::Base has_many :user_attachment_files, dependent: :destroy has_many :user_files, through: :user_attachment_files, dependent: :destroy enduser_attachment_file.rbclass UserAttachmentFile < ActiveRecord::Base belongs_to :user belongs_to :attachment_file endpost.rbclass Post < ActiveRecord::Base has_many :post_attachment_files, dependent: :destroy has_many :post_files, through: :post_attachment_files, dependent: :destroy endpost_attachment_file.rbclass PostAttachmentFile < ActiveRecord::Base belongs_to :post belongs_to :attachment_file endController
Attachment_fileモデル
Attachment_fileモデル
attachment_files_controller.rbclass AttachmentFilesController < ApplicationController def create @attachment_file = AttachmentFile.create!(file_params) respond_to do |format| format.html format.json {render json: @attachment_file } end end def destroy @attachment_file = AttachmentFile.find(params[:id]) if @attachment_file.destroy render :json => { result: 'success' } else render :json => { result: 'error' } end end private def file_params params.require(:attachment_file).permit(:id, :file) end endView
Dropzoneを使っています。ここでは詳細は割愛します。
(Dropzone.jsで複数ファイルアップロード後、削除したいファイルを指定したい:https://qiita.com/saekis/items/207379a056af73f143b7)
users/new.html.slimtr th = label :user, :user_file td #user_file_uploader.dropzone / ポイント table.files - for file in @user.attachment_files do tr id="file_row_#{file.id}" td = file.original_filename = hidden_field_tag nil, file.id, name:'user[attachment_file_ids][]' td = link_to file.file_url, download: file.original_filename do = fa_icon "download" |ダウンロード td = link_to '#', onclick: "#", file_id: file.id, remote: true do |削除Table
中間テーブル(User_attachment_file、Post_attachment_file)を持っているため、UserモデルとPostモデルではカラムを設ける必要はありません。
- Userモデル
- User_attachment_fileモデル
- Postモデル
- Post_attachment_fileモデル
- Attachment_fileモデル
schema.rb# Userモデル create_table "users", force: :cascade, options: do |t| t.datetime "created_at", null: false t.datetime "updated_at", null: false end # User_attachment_fileモデル create_table "user_attachment_files", force: :cascade, options: do |t| t.integer "user_id", limit: 4, null: false # ポイント t.integer "attachment_file_id", limit: 4, null: false # ポイント end # Postモデル create_table "posts", force: :cascade, options: do |t| t.datetime "created_at", null: false t.datetime "updated_at", null: false end # Post_attachment_fileモデル create_table "post_attachment_files", force: :cascade, options: do |t| t.integer "post_id", limit: 4, null: false # ポイント t.integer "attachment_file_id", limit: 4, null: false # ポイント end # Attachment_fileモデル create_table "attachment_files", force: :cascade, options: do |t| t.string "file", limit: 255 # ポイント t.string "original_filename", limit: 255 # ポイント t.datetime "created_at", null: false t.datetime "updated_at", null: false endまとめ
先輩のコードを見てこの方法を知りました。
人のコードを読むことの重要性を強く感じたので、コーディング → リファクタリング のサイクルを大切にしたいです。
- 投稿日:2019-10-12T20:34:46+09:00
可変クラスの作り方 Rails Slim
- 投稿日:2019-10-12T19:51:23+09:00
rootへのルーティング設定方法
目的
アプリの開発で仕様するrootメソッドの使い方をアウトプットを目的に備忘録的に残します。
ルーティング設定を行う
Railsアプリを作成、起動後「http://localhost:3000/」 にアクセスすると「public.index.html」に記載された内容が呼び出されています。
ただ、この状態はルートへアクセスしたときに用意したhtmlファイルを返しているだけです。もしルートにアクセスした場合に特定のアクションを実行させたい場合、「root」メソッドを使ったルーティングを設定する必要があります。
そこで、ルーティングを設定して特定のアクションを実行し、viewを表示する方法を紹介していきます。ルーティングの設定のため「config/routes.rb」ファイルを編集します。
indexアクションのビューをrootに設定されるように追記します。config/routes.rbroot to: 'home#index'まとめ
rootメソッドを定義することによって特定のアクションを実行することができます。
- 投稿日:2019-10-12T19:37:34+09:00
はじめてのPostgreSQL
この記事が対象とする読者
- PostgreSQLをはじめて使う人
Homebrew
を使ったインストールの仕方を簡潔にまとめます。インストールの流れ
1. ターミナルからインストール
まずは、以下のコマンドを入力・実行します。
$ brew install postgresql2. インストールの確認
正常にインストールできているかどうかを確認します。
バージョンを表す-V
は大文字なので注意です。$ postgres -V postgres (PostgreSQL) 11.53. PostgreSQLを起動
データベースとして使用するためには以下の起動のコマンドも必要です。
$ brew services start postgresql ==> Successfully started `postgresql` (label: homebrew.mxcl.postgresql)ここまで完了すればデータベースが作成できるようになるはずです!
さいごに
PostgreSQL初心者による初心者のためのQiitaでした。
簡単な工程なのですが、検索しながらやっていたら迷ってしまったので共有しておきます。少しでも参考になればうれしいです。
- 投稿日:2019-10-12T18:59:30+09:00
GAE(FE)にDocker環境上のRuby on Railsをデプロイする
はじめに: Dockerで開発したら手軽にデプロイまでしたい。
Ruby on Railsの開発で今や主流のDocker環境。
ネット上に環境構築の情報は豊富に転がっているし、local開発は出来たし、Herokuにもデプロイできた。でもやっぱりHerokuだとイマイチ速度が出ないし、
何よりAWSとかGCP使ってるほうがカッコイイ気がする!(小並感)圧倒的に情報不足なDockerに乗っけたままAWSやGCPにデプロイをする方法。
今回は既にHerokuで運用中のwebサービスをGAE環境に移し替えたのでその際に行った手順と、躓いた点を書いていこうと思います。自分自身も右往左往しながら、なんとかデプロイできたー!!動いたー!!
という感じなので、間違っている点や、改善点などが有りましたら是非色々とご教示いただけると助かります。前提
- 既にDocker+ docker-composeで環境構築済み、動作確認済みのRuby on Railsアプリケーションがある
- GCPアカウント及びプロジェクトが作成済
- Cloud SDKが導入済み
【参考】
Dockerを使ってRuby on Rails環境の構築をしてみる
https://qiita.com/me-654393/items/d11b871ce8d76e153b21Cloud SDK のインストール
https://cloud.google.com/sdk/downloads?hl=jaApp Engineにアプリを作成する
まず最初に、ターミナルからGAEアプリを作成します。
$ gcloud app create You are creating an app for project [project-name]. WARNING: Creating an App Engine application for a project is irreversible and the region cannot be changed. More information about regions is at <https://cloud.google.com/appengine/docs/locations>. Please choose the region where you want your App Engine application located: [1] asia-east2 (supports standard and flexible) [2] asia-northeast1 (supports standard and flexible) [3] asia-northeast2 (supports standard and flexible) [4] asia-south1 (supports standard and flexible) [5] australia-southeast1 (supports standard and flexible) [6] europe-west (supports standard and flexible) [7] europe-west2 (supports standard and flexible) [8] europe-west3 (supports standard and flexible) [9] europe-west6 (supports standard and flexible) [10] northamerica-northeast1 (supports standard and flexible) [11] southamerica-east1 (supports standard and flexible) [12] us-central (supports standard and flexible) [13] us-east1 (supports standard and flexible) [14] us-east4 (supports standard and flexible) [15] us-west2 (supports standard and flexible) [16] cancel Please enter your numeric choice: 2 Creating App Engine application in project [project-name] and region [asia-northeast1]....done. Success! The app is now created. Please use `gcloud app deploy` to deploy your first app.asia-northeast1が東京リージョンですので、選択します。
※[project-name]となっている部分は各々作成したGCPプロジェクト名が入ります。app.yamlを作成する
続いて、Dockerfileと同階層にapp.yamlを作成します。
app.yamlの説明に関してはここでは省略します。
【参考】https://cloud.google.com/appengine/docs/standard/go/config/appref?hl=jaapp.yamlentrypoint: bundle exec rackup --port $PORT env: flex runtime: custom env_variables: SECRET_KEY_BASE: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX(各々のsecret key baseを入れる)SECRET_KEY_BASEの取得方法は、
コンソール上で、$ docker-compose exec web bundle exec rails secret表示される文字列をコピペすればOKです。
Railsサーバの起動ポートを8080に合わせる
GAEでは、8080番に対してアクセスするので、3000番などに設定している方は8080番に変更しましょう。
Dockerfile(例)FROM ruby:2.6.3 RUN apt-get update -qq && \ apt-get install -y build-essential \ libpq-dev \ nodejs RUN mkdir /app_name ENV APP_ROOT /app_name WORKDIR $APP_ROOT ADD ./Gemfile $APP_ROOT/Gemfile ADD ./Gemfile.lock $APP_ROOT/Gemfile.lock RUN bundle install ADD . $APP_ROOT # 8080番でポートを起動する CMD /bin/sh -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 8080 -b '0.0.0.0'"docker-compose.yml(例)version: "3" services: web: environment: TZ: Asia/Tokyo build: . volumes: - .:/app ports: - 8080:8080 tty: true stdin_open: true volumes: db-volume:基本的な準備はここまで。
いざデプロイ!
さて、待ちに待ったデプロイ。この瞬間が一番ドキドキします。
ターミナルを開いて、$ gcloud app deploy descriptor: [/project-name/app.yaml] source: [/projects/project-name] target project: [project-name] target service: [default] target version: [20190000t123456] target url: [https://project-name.appspot.com] Do you want to continue (Y/n)? Y(←上記の内容で問題なければ、Yを入力します。)ビルドが始まり、しばらく待ち、
Updating service [default] (this may take several minutes)...done. Setting traffic split for service [default]...done. Deployed service [default] to [https://project-name.appspot.com] You can stream logs from the command line by running: $ gcloud app logs tail -s default To view your application in the web browser run: $ gcloud app browseと、表示されれば成功。
$ gcloud app browseで確認しましょう。
トラブルシューティング
「Switch to inspect mode.」と怒られる件について
Updating service [default] (this may take several minutes)...failed. ERROR: (gcloud.app.deploy) Error Response: [9] Application startup error: Switch to inspect mode.色々ググったけど今ひとつ情報源に当たれずにめちゃくちゃ困ってた。
8080番でRailsが起動しておらず、GAEがアクセスできていないってことだと思う。原因はdocker-compose側でサーバーを起動していたこと。
docker-compose.yml(例)version: "3" services: web: build: . # ここがアウト command: /bin/sh -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 8080 -b '0.0.0.0'" volumes: - .:/app_name ports: - "8080:8080" links: - dbこれだとこのエラーが出るみたい。
原因はいまいちよくわかっていないが、もしかしたらdocker-compose読んでいないのかも?8080番ポートの下りで記載したDockerfileのように、
DockerfileCMD /bin/sh -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 8080 -b '0.0.0.0'"で、8080を開いてあげるのがいいのかも。
【参考】
A weekend, a Rails app, a Kubernetes and an Azure.
https://medium.com/@michiels/a-weekend-a-rails-app-a-kubernetes-and-an-azure-d330b003d7c2最後に:Herokuの方がわかりやすいけど、やっぱりGAE早い
今回は実行速度が気になったので、HerokuからGAEに乗り換えることを決意したわけですが、
予想通りGAEの方が速度が出ました。特にRailsの中身をいじること無く、20程度速度が速度が上昇しました。
改善案の部分でサーバー応答時間の短縮(TTFB)が表示されなくなったのがでかい気がします。おまけ:宣伝
今回HerokuからGAEに移行したのはスポチューバーTVという、野球の技術指導メディアです。
スポーツ教育×Techの、主に野球の分野を伸ばしていけたらなと思っているので、興味のある方は是非御覧ください。
https://spotuber-tv.com/参考
- https://medium.com/@michiels/a-weekend-a-rails-app-a-kubernetes-and-an-azure-d330b003d7c2
- https://qiita.com/ClapAndWhistle/items/23e3b4323fd06342b7c4
- https://cloud.google.com/ruby/rails/appengine?hl=ja
- https://qiita.com/takamario/items/33adc033090c31aeb702
- https://qiita.com/kshibata101/items/441de47fcb2d94875e85
- https://qiita.com/shwld/items/e86ee3f642c7857dd56e
- 投稿日:2019-10-12T18:21:13+09:00
Sessionメソッドの使い方、宣言方法
目的
ログイン機能を実装するときにSessionメソッドを使用するが、
機能の流れをおさらいするために備忘録として残しておく。セッションとは?
・主にログイン機能に使用されるもので、ログイン状態を持続させるためにある機能。
・ステートフルな通信を実現するために必要な機能。セッションという仕組みがなけれはページを移動するたびにログインし直さなければならない。
ログイン機能を実装するためRailsではあらかじめセッションを実装するためのメソッドが用意している。
また、セッションの情報はRailsの標準では、ブラウザ側のクッキーに保存される。Sessionメソッドの使い方
まずは、ユーザーを登録するためにユーザーモデルを準備する。
ユーザーモデルには、名前とメールとパスワードが登録できるようにします。gemをGemfileに追加
パスワードを暗号化するための
(コメントアウトされているためコメントインする必要がある)Gemfilegem 'bcrypt', '~> 3.1.7'コメントインが完了したらgemをインストールする必要がある。
bundle installUserモデルを作成
rails g model Users name:string email:string password_digest:stringpassword_digestというカラムにはパスワードが入ります。
こちらはgemであるbcryptの仕様ですが、このカラムにするとパスワードが暗号化されて保存されます。migrationファイルが作成されたので、データベースに反映しましょう。
rails db:migrateこれでユーザーモデルの作成は完成です。
次に作成したユーザモデルにパスワードの暗号化を有効にするための設定を加えます。
app/models/user.rbhas_secure_password
Sessionコントローラーを作成
ログイン機能を作成するためセッションコントローラーを作成する。
rails g controller sessionsapp/controllers/session_sontroller.rbclass SessionsController < ApplicationController def new end def create user = User.find_by(email: params[:session][:email].downcase) if user && user.authenticate(params[:session][:password]) session[:user_id] = user.id redirect_to user_path(user.id) else render 'new' end end endログインボタンを押した後に、セッションに情報が保存されているか判断しています。
user = User.find_by(email: params[:session][:email].downcase)params[:session][:email]で、ユーザのポストでした値を取り出し、メールアドレスには小文字しか入力できないため.downcaseで小文字に変換しています。
その後find_byメソッドで入力したemailをカラムに保存されたemailが一致した場合にユーザーに代入しています。次に、
if user && user.authenticate(params[:session][:password])&&を使って、条件を2つ指定しています。
・user:userの中身があるかどうか
・user.authenticate:userに代入されたレコードのパスワードがポストした値と一致しているかパスワードが登録しているユーザー情報と一致していたらセッションにユーザーIDを登録しています。
session[:user_id] = user.idsession[:名前]は名前をつけて、セッションを登録できます。
また、elseの場合、
render 'new'パスワードが登録しているユーザ情報と一致していなかった場合、入力フォームに再度飛ぶように設定されている。
これでセッションを作成することができました。
まとめ
セッションを作成する際に必要なものは以下である。
・パスワードを暗号化するためのgem:bcrypt
・gem:bcryptで暗号化したパスワードを保存するカラム:password_digest
・パスワードの暗号化を有効にするための設定:has_secure_password
・セッションを作成するためにメソッド:session[:名前] = params[:~]
- 投稿日:2019-10-12T18:01:52+09:00
MacでRailsにgem mysql2をインストールする時のトラブルシューティング
はじめに
2019年10月現在MacでRailsアプリ開発時にDBにMySQLを選択する場合、デフォルトではmysql2というgemをインストールします。
その時にいくつかエラーに出会ったので、まとめておきます。実行環境
- macOS Mojave v10.14.6
- ruby v2.6.4
- Rails v6.0.0
MySQLのクライアントが必要
MacにMySQLクライアントをインストールしていない場合、以下のようなエラーが表示されます。
Fetching mysql2 0.5.2 Installing mysql2 0.5.2 with native extensions Gem::Ext::BuildError: ERROR: Failed to build gem native extension. current directory: /Users/hogehoge/sample-app/vendor/bundle/gems/mysql2-0.5.2/ext/mysql2 /Users/hogehoge/.rbenv/versions/2.6.4/bin/ruby -I /Users/hogehoge/.rbenv/versions/2.6.4/lib/ruby/2.6.0 -r ./siteconf20191012-40688-181m253.rb extconf.rb checking for rb_absint_size()... yes checking for rb_absint_singlebit_p()... yes checking for rb_wait_for_single_fd()... yes checking for -lmysqlclient... no ----- mysql client is missing. You may need to 'brew install mysql' or 'port install mysql', and try again.Homebrewなどでインストールしておきましょう。
なお、2019年10月現在gemが対応しているバージョンは以下のようですので、採用バージョンに応じたものをインストールしましょう。This gem is tested with the following MySQL and MariaDB versions:
MySQL 5.5, 5.6, 5.7, 8.0
MySQL Connector/C 6.0 and 6.1 (primarily on Windows)
MariaDB 5.5, 10.0, 10.1, 10.2, 10.3(https://github.com/brianmario/mysql2 READMEより引用)
# MySQL最新バージョン(8.0) $ brew install mysql # 5.xの場合 $ brew install mysql@5.x5.x系の場合はmysqlコマンドを使えるよう以下のようにPATHを通しておきましょう。
$ export PATH="/usr/local/opt/mysql@5.7/bin:$PATH"bundle install時のlinkerエラー
無事MySQLクライアントをインストールできていても、以下のようなエラーが起こる場合があります。
Fetching mysql2 0.5.2 Installing mysql2 0.5.2 with native extensions Gem::Ext::BuildError: ERROR: Failed to build gem native extension. current directory: /Users/hogehoge/sample-app/vendor/bundle/gems/mysql2-0.5.2/ext/mysql2 /Users/hogehoge/.rbenv/versions/2.6.4/bin/ruby -I /Users/hogehoge/.rbenv/versions/2.6.4/lib/ruby/2.6.0 -r ./siteconf20191012-62886-155hohe.rb extconf.rb checking for rb_absint_size()... yes checking for rb_absint_singlebit_p()... yes checking for rb_wait_for_single_fd()... yes ----- Using mysql_config at /usr/local/bin/mysql_config ----- checking for mysql.h... yes checking for errmsg.h... yes checking for SSL_MODE_DISABLED in mysql.h... yes checking for SSL_MODE_PREFERRED in mysql.h... yes checking for SSL_MODE_REQUIRED in mysql.h... yes checking for SSL_MODE_VERIFY_CA in mysql.h... yes checking for SSL_MODE_VERIFY_IDENTITY in mysql.h... yes checking for MYSQL.net.vio in mysql.h... yes checking for MYSQL.net.pvio in mysql.h... no checking for MYSQL_ENABLE_CLEARTEXT_PLUGIN in mysql.h... yes checking for SERVER_QUERY_NO_GOOD_INDEX_USED in mysql.h... yes checking for SERVER_QUERY_NO_INDEX_USED in mysql.h... yes checking for SERVER_QUERY_WAS_SLOW in mysql.h... yes checking for MYSQL_OPTION_MULTI_STATEMENTS_ON in mysql.h... yes checking for MYSQL_OPTION_MULTI_STATEMENTS_OFF in mysql.h... yes checking for my_bool in mysql.h... no ----- Dont know how to set rpath on your system, if MySQL libraries are not in path mysql2 may not load ----- ----- Setting libpath to /usr/local/Cellar/mysql/8.0.17_1/lib ----- creating Makefile current directory: /Users/hogehoge/sample-app/vendor/bundle/gems/mysql2-0.5.2/ext/mysql2 make "DESTDIR=" clean current directory: /Users/hogehoge/sample-app/vendor/bundle/gems/mysql2-0.5.2/ext/mysql2 make "DESTDIR=" compiling client.c compiling infile.c compiling mysql2_ext.c compiling result.c compiling statement.c linking shared-object mysql2/mysql2.bundle ld: library not found for -lssl clang: error: linker command failed with exit code 1 (use -v to see invocation) make: *** [mysql2.bundle] Error 1 make failed, exit code 2以下がこのエラーの原因で、ざっくり言うとgemのビルド時に、必要なopensslライブラリを見つけられないために発生しています。
ld: library not found for -lssl詳細な原因は以下記事が大変参考になります。
https://qiita.com/HrsUed/items/ca2e0aee6a2402571cf6解決策としては、bundle install時に以下オプションでpathを指定する必要があります。
- --with-cppflags
- --with-ldflags
brew info
でインストールされているopensslを確認すると、自分の環境のLDFLAGSとCPPFLAGSのpathが確認できます。$ brew info openssl@1.1 openssl@1.1: stable 1.1.1d (bottled) [keg-only] ... For compilers to find openssl@1.1 you may need to set: export LDFLAGS="-L/usr/local/opt/openssl@1.1/lib" export CPPFLAGS="-I/usr/local/opt/openssl@1.1/include" ...こちらのpathを
bundle config
で設定してからインストールしましょう。$ bundle config --local build.mysql2 "--with-cppflags=-I/usr/local/opt/openssl@1.1/include" $ bundle config --local build.mysql2 "--with-ldflags=-L/usr/local/opt/openssl@1.1/lib" $ bundle install以上、自分が遭遇したエラー内容でした。
- 投稿日:2019-10-12T17:45:55+09:00
Railsチュートリアル 第10章<復習>
第10章の復習メモです。
個人的に重要と思ったことを書きます。前回と同様、以下3つの視点で書きます。
- 分かったこと
- 分からなかったこと
- 今回はスルーしたこと
分かったこと
PATCHリクエストについて
ユーザの新規作成画面(
app/views/users/new.html.erb
)と、編集画面(app/views/users/edit.html.erb
)は、画面項目が同じなため、フォームを部分テンプレートに共通化した。app/views/users/_form.html.erb<%= form_for(@user) do |f| %> <%= render 'shared/error_messages', object: @user %> <%= f.label :name %> <%= f.text_field :name, class: 'form-control' %> <%= f.label :email %> <%= f.email_field :email, class: 'form-control' %> <%= f.label :password %> <%= f.password_field :password, class: 'form-control' %> <%= f.label :password_confirmation %> <%= f.password_field :password_confirmation, class: 'form-control' %> <%= f.submit yield(:button_text), class: "btn btn-primary" %> <% end %>共通化により懸念されるのが、
- リクエスト先URLの違い
- リクエストのメソッドの違い
これらの違いがあるので、普通に考えたら、分岐の処理を入れる必要がある。しかし、これについてはRailsが上手くやってくれる。
Railsは、form_for(@user)を使ってフォームを構成すると、@user.new_record?がtrueのときにはPOSTを、falseのときにはPATCHを使います。
DBへのデータ一括登録
画面やコンソールから一件ずつ登録せずとも、DBにデータを一括登録する方法がある。
手順は以下の通り。
- Gemfileにgemを追加する
- 登録したいデータを用意する
- DBに反映させる
手順1. Gemfileにgemを追加する
Faker gem
を追加するsource 'https://rubygems.org' gem 'rails', '5.1.6' gem 'bcrypt', '3.1.12' gem 'faker', '1.7.3' # ← これを追加 . . .手順2. 登録したいデータを用意する
db/seeds.rb
ファイルに、登録したいデータを記載するdb/seeds.rbUser.create!(name: "Example User", email: "example@railstutorial.org", password: "foobar", password_confirmation: "foobar") 99.times do |n| name = Faker::Name.name email = "example-#{n+1}@railstutorial.org" password = "password" User.create!(name: name, email: email, password: password, password_confirmation: password) endExample Userという名前とメールアドレスを持つ1人のユーザと、それらしい名前とメールアドレスを持つ99人のユーザーを作成している。
手順3. DBに反映させる
現在登録されているデータを消しておきたい場合、
$ rails db:migrate:resetを実行する。
手順2をDBに反映させるには、
$ rails db:seedを実行する。
ページネーション
一覧画面で、表示件数を区切って出力できる。手順は以下の通り。
- Gemfileにgemを追加する
- ビュー、コントローラを編集する
手順1. Gemfileにgemを追加する
will_paginate gem
、bootstrap-will_paginate gem
を追加するsource 'https://rubygems.org' gem 'rails', '5.1.6' gem 'bcrypt', '3.1.12' gem 'faker', '1.7.3' gem 'will_paginate', '3.1.6' # ← これを追加 gem 'bootstrap-will_paginate', '1.0.0' # ← これを追加 . . .手順2. ビュー、コントローラを編集する
ビューに処理を追記
app/views/users/index.html.erb<% provide(:title, 'All users') %> <h1>All users</h1> <%= will_paginate %> <ul class="users"> # ← これを追加 <% @users.each do |user| %> <li> <%= gravatar_for user, size: 50 %> <%= link_to user.name, user %> </li> <% end %> </ul> <%= will_paginate %> # ← これを追加Railsチュートリアルの説明を引用
このwill_paginateメソッドは少々不思議なことに、usersビューのコードの中から@usersオブジェクトを自動的に見つけ出し、それから他のページにアクセスするためのページネーションリンクを作成しています。
コントローラの処理を変更
app/controllers/users_controller.rbclass UsersController < ApplicationController before_action :logged_in_user, only: [:index, :edit, :update] . . . def index @users = User.paginate(page: params[:page]) # ← ここを変更 end . . . endRailsチュートリアルの説明を引用
paginateでは、キーが:pageで値がページ番号のハッシュを引数に取ります。User.paginateは、:pageパラメーターに基いて、データベースからひとかたまりのデータ (デフォルトでは30) を取り出します。
paginateを使うことで、サンプルアプリケーションのユーザーのページネーションを行えるようになります。具体的には、indexアクション内のallをpaginateメソッドに置き換えます。ここで:pageパラメーターにはparams[:page]が使われていますが、これはwill_paginateによって自動的に生成されます。モデルの論理値属性
モデルの属性に、論理値(boolean)を設定できる。
migrationファイルの作成
$ rails generate migration add_admin_to_users admin:booleanmigrationファイルの編集
default: false
を追加することで、全レコードfalse(0)で登録できる。db/migrate/[timestamp]_add_admin_to_users.rbclass AddAdminToUsers < ActiveRecord::Migration[5.0] def change add_column :users, :admin, :boolean, default: false end end属性の状態を確認
<インスタンス>.<属性>?
メソッドで、属性の状態を確認できる。$ rails console --sandbox >> user = User.first >> user.admin? => false分からなかったこと、今回はスルーしたこと
- Strong Parameters
- テスト全般
- アプリケーションの仕様、ロジックの詳細
- 投稿日:2019-10-12T17:43:10+09:00
【53日目】掲示板にタグを設定しよう!② タグ検索機能の実装、
タグ検索の実装
昨日の続きとなります。
タグの初期データを用意したり、個別の掲示板にタグを設定して保存したり、掲示板詳細画面に関連づけられたタグを表示する機能については【52日目】を参照してください。今日の内容は下記のとおりです。
○ 掲示板一覧でタグによる絞り込み機能の実装
・ セレクトボックスの作成方法
○ ヘッダーメニュー(グローバルメニューの作成)
・ ヘルパーメソッドの自作方法
・ HTMLタグの自作方法
・ ルーティングの自作方法掲示板一覧画面にタグのセレクトボックスを用意し、タグを入力して、該当のタグのある掲示板だけ残すようにしていきます。
掲示板一覧にタグのセレクトボックスを追加する(view)
indexのviewを編集して、掲示板一覧からタグを選び、選んだタグに関連づけられた掲示板のみを表示するようにします。
タグの選択は、プルダウン式のセレクトボックス(select_tagヘルパー使用)によって実装します。
セレクトボックスを選んだ瞬間に掲示板が絞り込まれるのは、「onchange」という属性によってJSを実行します。index.html.erb<%= form_tag boards_path, method: :get, class: 'boards__searchForm' do %> # form_tag, アクションへのパス, HTTPメソッド, HTMLオプション # classはセレクトボックスの位置を調整するためのHTMLオプションで、あとでCSSを編集する。 <%= select_tag :tag_id, # セレクトボックスのname属性がtag_idになる。 options_from_collection_for_select(Tag.all, :id, :name, params[:tag_id]), #ヘルパーによってセレクトボックスの選択肢となるオプション要素を複数作成している #選択肢としたいオブジェクトのリスト, オプションのvalue, オプションの表示名, 何を選択状態とするの指定 # 第四引数の指定によってGET通信でURLに含まれるクエリパラメータのtag_idを選択状態とすることができ、検索後も選択した状態に出来る。 { #ハッシュの中身全部が第三引数 prompt: 'タグで絞り込み', #空白の時に表示する文字 class: 'form-control boards__select', # クラス属性に設定する値 onchange: 'submit(this.form);' #選択されるたびに実行するJSのコードを記載している #onchange属性はセレクトボックスに限らず、値が変わった際に実行されるJSのコードを設定できるイベント属性。 #ここでは、何か選択肢から選んだ時点でフォーム(this.form)を作成するようになっている } %> <% end %>CSSの編集
.boards__searchForm { display: inline-block; } .boards__select { display: inline-block; width: auto; }これによって、掲示板一覧画面の上部にタグのセレクトボックスが表示されます。
ここでタグを選ぶことによって、選んだタグのtag_idをindexアクションに送ることができる。
ただし、現状のコントローラーにはviewから渡ってきたtag_idを使って検索する機能がないので、コントローラーを編集します。tag_idを用いた検索機能の実装(controller)
tag_idに該当するタグの取得 => タグオブジェクトから関連する掲示板を取得する。
boards_controller.rbdef index @boards = params[:tag_id].present? ? Tag.find(params[:tag_id]).boards : Board.all # tag_idがあるかを確認(present? ?) # tag_idがある場合には、tag_idのパラメータを持つタグを検索(.find)し、 # 該当するタグにhas_manyでアソシエーションされている掲示板のリストを取得している(親モデル.<has_manyアソシエーション名>) # tag_idがない場合は(present? ? 存在する場合の処理 : 存在しない場合の処理)全ての掲示板(Board.all)を表示する # @boardsを参照するまではBoard.allを取得することはない。 @boards = @boards.page(params[:page]) # 上記で定義された@boardsに対して、page内に表示する件数の制限をかけてから、取得する。 # 例えば10件とした場合、10件分だけのSQLが発行される。ためBoard.allとしてもパフォーマンスに悪影響しない endヘッダーメニューの追加
ブートストラップのデザインを使用し(もちろんCSSでもいい)viewを作成し、ページを遷移しても常に表示されるメニューを作成する。
touch app/views/application/_header.html.erbを編集していく。
ここではブートストラップのナビゲーションバーのデザインを使用し、部分的に修正している。
修正箇所にコメントを付記する。
独自のヘルパーを用いる部分があるので、この後ヘルパーを定義する。_header.html.erb<nav class="navbar navbar-expand-lg navbar-dark bg-dark"> <a class="navbar-brand" href="#">BoardApp</a> #タイトル名を変更 <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria- controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navbarSupportedContent"> <ul class="navbar-nav mr-auto"> # リンク部分の修正。header_link_iteというへルパーを作って使用する。 # link_toとしないのは、現在表示中のページのみをアクティブ(リンクが白くなる)にするために設定を加えたいから。 # header_link_item('表示名', リンク先のパス) とすることでリンクを張れる。 <%= header_link_item('Home', root_path) %> #root_pathはrootingを追加しないと存在しないので後で追加します。ホーム画面です。 <%= header_link_item('Boards', boards_path) %> <li class="nav-item dropdown"> <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> Dropdown </a> <div class="dropdown-menu" aria-labelledby="navbarDropdown"> <a class="dropdown-item" href="#">Action</a> <a class="dropdown-item" href="#">Another action</a> <div class="dropdown-divider"></div> <a class="dropdown-item" href="#">Something else here</a> </div> </li> <li class="nav-item"> <a class="nav-link disabled" href="#">Disabled</a> </li> </ul> <form class="form-inline my-2 my-lg-0"> <input class="form-control mr-sm-2" type="search" placeholder="Search" aria-label="Search"> <button class="btn btn-outline-success my-2 my-sm-0" type="submit">Search</button> </form> </div> </nav>ヘルパーメソッドの作成
今回はグローバルメニューに使う=全ページで使うヘルパーになるので
app/helpers/application_helper.rbの中に設定する。
特定のコントローラーでしか使用しない場合は、boards_helper.rbのように区別した方が用途がわかりやすくて間違いにくい。
application_helper.rbmodule ApplicationHelper def header_link_item(name, path) class_name = 'nav-item' # ブートストラップのデザインに合わせるためのクラス class_name << ' active' if current_page?(path) # 表示中のパスと引数のパスが同一であるかを判定する # 表示するパスと引数のパスが同じであれば、クラスの属性に「active」を追加する、というもの。 content_tag :li, class: class_name do # content_tag(HTMLタグ名, HTMLクラス)で任意のHTMLタグを作成できる。 # ここでは上で作成した「class_name」を指定している。 link_to name, path, class: 'nav-link' # content_tagのブロック内に書いたHTMLが作成したタグの中で展開される。 # ここでは仮引数で受け取ったとおりにリンクを貼るアンカータグを埋め込むことになる。 end end endroot_pathの作成
次に、ルーティングを追加する。
ここではまだページは作成していない。routes.rbroot 'home#index' # パス名 'コントローラー名#アクション名'ヘッダーのview(パーシャル)の作成
ヘッダーのパーシャルをrenderで呼び出すだけ。
application.html.erb<%= render 'header' %>この後認証機能の学習に進みますが、かなりジャンルが変わるのでここで一旦投稿します。
- 投稿日:2019-10-12T16:39:21+09:00
【Ruby】=と==、&と&&、|と||の違い
RubySilverの勉強でつまづいたのでメモします。
=と==の違い
・=が左辺に代入
・==が同じ
です。example.html.slim- dinner = @dinner.vegetables? ? "食べたくない" : "食べる" = dinnerexample.rbdef hoge return false if @dinner.vegetables == "きゅうり" return false if @dinner.vegetables == "レタス" return false if @dinner.vegetables == "なす" true end&と&&の違い
・&が積集合(共通する要素を取り出す)
・&&がtrue/falseを返す(全部true(存在する)ならtrue出力)
です。a = [1, 2, 3] b = [2, 3, 4] # 共通する要素を出力 a & b => [2, 3] # 最後に評価されたb(true)を出力 a && b => [1, 2, 3] # aが偽のためnil(右辺を見ない) a = nil a && b => nil|と||の違い
・|が和集合
・||がtrue/falseを返す(true(存在する)の時点でtrue出力)
です。a = [1, 2, 3] b = [2, 3, 4] # いずれかに含まれる要素を出力 a | b => [1, 2, 3, 4] # 最初に評価されたa(true)を出力 a || b => [1, 2, 3] # aが偽のためbを出力(右辺まで見る) a = nil a || b => [2, 3, 4]
- 投稿日:2019-10-12T15:41:25+09:00
rbenvを使ってUbuntu 18.04にRubyをインストールする
はじめに
UbuntuにRubyをインストールしたときの記録です。
各種バージョン
Ubuntu 18.04 LTS
Ruby 2.6.5インストール方法
rbenvのインストール
rbenvはRubyのバージョン管理ツールです。rbenvを使ってRubyをインストールします。
まずは、関連パッケージをインストール
$ sudo apt update $ sudo apt install autoconf bison build-essential libssl-dev libyaml-dev libreadline6-dev zlib1g-dev libncurses5-dev libffi-dev libgdbm5 libgdbm-devrbenvをGitHubからcloneする
$ git clone https://github.com/rbenv/rbenv.git ~/.rbenv
~/.rbenv/bin
にパスを通す$ echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrcrbenvをセットアップ
$ ~/.rbenv/bin/rbenv initセットアップを自動で行うために
.bashrc
に追記。
メッセージには.bash_profile
に追記するように出るが、GUI版Ubuntuの場合は.bashrc
に追記。$ echo 'eval "$(rbenv init -)"' >> ~/.bashrcシェルを再起動 or 以下のコマンドを実行。
$ source ~/.bashrc
rbenv-doctor
を使ってrbenvの設定ができたか確認する。$ curl -fsSL https://github.com/rbenv/rbenv-installer/raw/master/bin/rbenv-doctor | bash
ruby-build
がインストールされていない場合は、以下が表示されるChecking `rbenv install' support: not found Unless you plan to add Ruby versions manually, you should install ruby-build. Please refer to https://github.com/rbenv/ruby-build#installationruby-buildのインストール
GitHubからrbenvのプラグインとしてインストール
$ mkdir -p "$(rbenv root)"/plugins $ git clone https://github.com/rbenv/ruby-build.git "$(rbenv root)"/plugins/ruby-buildもう一度
rbenv-doctor
を実行する$ curl -fsSL https://github.com/rbenv/rbenv-installer/raw/master/bin/rbenv-doctor | bashこれで、
rbenv install
を実行できるようになるRubyのインストール
インストールできるバージョンをチェック
$ rbenv install -lバージョンを指定してインストール
$ rbenv install 2.6.5Rubyのデフォルトバージョンを指定
$ rbenv global 2.6.5確認して終了
$ ruby -v ruby 2.6.5p114 (2019-10-01 revision 67812) [x86_64-linux]参考サイト
- 投稿日:2019-10-12T15:34:09+09:00
Rubyの例外のうち StandardError に属さないものを発生させる実験
Rubyの例外クラスは全て
Exception
のサブクラスだが、そのうちプログラムの継続実行が困難なエラーはStandardError
のサブクラスではなく、例外の種類を省略したrescue
では捕捉しないようになっている。制御構造 例外処理: begin - Rubyリファレンスマニュアル
例外の一致判定は,発生した例外が rescue 節で指定したクラスのインスタンスであるかどうかで行われます。
error_type が省略された時は
StandardError
のサブクラスである全ての例外を捕捉します。Rubyの組み込み例外は(SystemExit
やInterrupt
のような脱出を目的としたものを除いて)StandardError
のサブクラスです。test_rescue.rbbegin raise StandardError, 'test' rescue => e p e end #=> #<StandardError: test> begin raise Exception, 'test' rescue => e p e end # Traceback (most recent call last): # test_rescue.rb:9:in `<main>': test (Exception)もちろんこの例のように
raise
時に例外の種類を明示すれば簡単に発生させられるが、それ以外の場合では(一部を除いて)滅多に見ることがない。実際の例を見てみたくなったので試してみる。例外の一覧
実際にコードを実行して一覧を手に入れる。
list_exceptions.rbpp ObjectSpace.each_object(Class) .select { |cls| cls <= Exception } .reject { |cls| cls <= StandardError } .group_by(&:superclass)結果を継承関係で木構造にまとめると次の通りになる。
Exception
NoMemoryError
ScriptError
LoadError
Gem::LoadError
Gem::ConflictError
Gem::MissingSpecError
Gem::MissingSpecVersionError
NotImplementedError
SyntaxError
SecurityError
SignalException
Interrupt
SystemExit
Gem::SystemExitException
SystemStackError
fatal
MonitorMixin::ConditionVariable::Timeout
Gem
やMonitorMixin
にも例外が存在するようだが、その他はリファレンスマニュアルの組み込みライブラリの内容と一致する。例外発生実験
raise
で明示せず出せる例外を試していく。コードは全てbegin
~rescue
~end
にしてあり、rescue Exception => e
と直せば例外を捕捉して表示する。
Exception
全ての例外の祖先のクラスです。
恐らく継承のために用意しているもので、明示せずに出せるものではないと思う。
NoMemoryError
メモリの確保に失敗すると発生します。
という単純明快な例外。プログラムを継続実行しづらいことも納得しやすい。
しかしメモリを大量に消費しないといけないので、他のプロセスにも影響が出そうで気軽に試すのは怖い。以下はWindows Subsystem for Linux (WSL)上で影響なく動いたが、実行は自己責任で。
no_memory_error.rbbegin String.new(capacity: 2 ** 40) # 1 TiB rescue => e p e end # Traceback (most recent call last): # no_memory_error.rb: failed to allocate memory (NoMemoryError)
ScriptError
スクリプトのエラーを表す例外クラスです。
以下の例外クラスのスーパークラスです。
LoadError
NotImplementedError
SyntaxError
これらの例外が発生したときは Ruby スクリプト自体にバグがある可能性が高いと考えられます。
これも恐らく継承のために用意しているもので、明示せずに出せるものではないと思う。
LoadError
Kernel.#require
やKernel.#load
が失敗したときに発生します。ライブラリ名を間違えたりすれば出るので、よくお世話になる。
load_error.rbbegin require 'this/file/does/not/exist' rescue => e p e end # Traceback (most recent call last): # 2: from load_error.rb:2:in `<main>' # 1: from $(gem environment gemdir)/rubygems/core_ext/kernel_require.rb:54:in `require' # $(gem environment gemdir)/rubygems/core_ext/kernel_require.rb:54:in `require': cannot load such file -- this/file/does/not/exist (LoadError)
NotImplementedError
実装されていない機能が呼び出されたときに発生します。
恐らくユーザー(やライブラリ)が使うために用意された例外。明示する以外に出す方法は無いと思う。Rubyのソースコードで
NotImplementedError
やrb_eNotImpError
を検索すると、いくつかの場所で使われていることがわかる。not_implemented_error.rbbegin require 'digest' puts Digest::Base.hexdigest('test') rescue => e p e end # Traceback (most recent call last): # 2: from not_implemented_error.rb:3:in `<main>' # 1: from not_implemented_error.rb:3:in `hexdigest' # not_implemented_error.rb:3:in `digest': Digest::Base is an abstract class (NotImplementedError)これまで未実装の部分やオーバーライド前提のメソッドに
raise 'Not implemented.'
なんて仕込んできた(→到達するとRuntimeError
になる)が、こっちの例外を使うのが正しかった?
SyntaxError
ソースコードに文法エラーがあったときに発生します。
これもよくお世話になる。とはいえ試すとなると、コード読み込み時に文法チェックに引っかかったら実行できないので、
eval
内でわざと間違えることにする。syntax_error.rbbegin eval ')' rescue => e p e end # Traceback (most recent call last): # 1: from syntax_error.rb:2:in `<main>' # syntax_error.rb:2:in `eval': (eval):1: syntax error, unexpected ')' (SyntaxError)terminal$ ruby -c syntax_error.rb Syntax OK $ ruby syntax_error.rb Traceback (most recent call last): 1: from syntax_error.rb:2:in `<main>' syntax_error.rb:2:in `eval': (eval):1: syntax error, unexpected ')' (SyntaxError)
SecurityError
セキュリティ上の問題が起きたときに発生します。
セキュリティモデルも参照してください。
使ったことのない仕組みなのでピンとこないが、とりあえず
eval
で試してみる。さっきと同じ文法エラーのコードを与えても、「汚染させた」場合はそもそも実行されなくなる。security_error.rbbegin $SAFE = 1 eval ')'.taint rescue => e p e end # Traceback (most recent call last): # 1: from security_error.rb:3:in `<main>' # security_error.rb:3:in `eval': Insecure operation - eval (SecurityError)
SignalException
捕捉していないシグナルを受け取ったときに発生します。
Signal.#trap
で割り込みシグナルをハンドリングしないと発生する。
Process.#kill
で自身のプロセスに SIGTERM を送ってみる。signal_exeption.rbbegin Process.kill(:TERM, Process.pid) rescue => e p e end # Terminatedリファレンスマニュアルに書いてあるが、全てのシグナルに対応するわけではない。例えば SIGSEGV を送ると例外すら出ずRubyがコアダンプして異常終了する。
Interrupt
SIGINT シグナルを捕捉していないときに SIGINT シグナルを受け取ると発生します。 SIGINT 以外のシグナルを受信したときに発生する例外については
SignalException
を参照してください。こちらはコード実行を Ctrl+C で中断したりすれば出るのでよく見かける。
interrupt.rbbegin Process.kill(:INT, Process.pid) rescue => e p e end # Traceback (most recent call last): # 1: from interrupt.rb:2:in `<main>' # interrupt.rb:2:in ``': Interrupt
SystemExit
Ruby インタプリタを終了させるときに発生します。
例外自身のページよりも終了処理のページのほうが説明が詳しい。
関数
Kernel.#exit
やKernel.#abort
、メインスレッドに対するThread.kill
などはSystemExit
例外を発生させますということは、スクリプトの途中で
exit
しても捕捉すれば終了を取り消せる。逆に、例外として異常終了させることはできなさそう。
SystemStackError
システムスタックがあふれたときに発生します。
典型的には、メソッド呼び出しを無限再帰させてしまった場合に発生します。
再帰の練習などしていれば見るはず。
system_stack_error.rbdef factorial(n) return 1 if n == 0 n * factorial(n - 1) end begin p factorial(-1) rescue => e p e end # Traceback (most recent call last): # 10080: from system_stack_error.rb:8:in `<main>' # 10079: from system_stack_error.rb:4:in `factorial' # 10078: from system_stack_error.rb:4:in `factorial' # 10077: from system_stack_error.rb:4:in `factorial' # 10076: from system_stack_error.rb:4:in `factorial' # 10075: from system_stack_error.rb:4:in `factorial' # 10074: from system_stack_error.rb:4:in `factorial' # 10073: from system_stack_error.rb:4:in `factorial' # ... 10068 levels... # 4: from system_stack_error.rb:4:in `factorial' # 3: from system_stack_error.rb:4:in `factorial' # 2: from system_stack_error.rb:4:in `factorial' # 1: from system_stack_error.rb:4:in `factorial' # system_stack_error.rb:4:in `factorial': stack level too deep (SystemStackError)
fatal
インタプリタ内部で致命的なエラーが起こったときに発生します。
致命的なエラーとは、例えば以下のような状態です。
- スレッドのデッドロックが発生した
- -x オプションや -C オプションで指定されたディレクトリに移動できなかった
- -i オプション付きで起動されたが、 パーミッションなどの関係でファイルを変更できなかった
通常の手段では、 Ruby プログラムからは fatal クラスにはアクセスできません。
小文字で始まっているけれどもクラス。モジュール全体を探しても小文字始まりはこれしか無い。普通にコードに
fatal
と書いても、変数かメソッドと判断されて例外を参照できない。ary = ObjectSpace.each_object(Module).select { |mdl| /^[a-z]/ =~ mdl.to_s } e = ary.first raise e, 'test'致命的なエラーとして挙げられているデッドロックを起こしてみる。(参考:The Backyard - DeadLockInRuby)
fatal.rbbegin q = Queue.new Thread.new { q.deq }.join rescue => e p e end # Traceback (most recent call last): # 1: from fatal.rb:3:in `<main>' # fatal.rb:3:in `join': No live threads left. Deadlock? (fatal) # 2 threads, 2 sleeps current:0x00007fffcf775500 main thread:0x00007fffcf3b7470 # * #<Thread:0x00007fffcf3e72c8 sleep_forever> # rb_thread_t:0x00007fffcf3b7470 native:0x00007f9e3def0700 int:0 # fatal.rb:3:in `join' # fatal.rb:3:in `<main>' # * #<Thread:0x00007fffcf7ac188@fatal.rb:3 sleep_forever> # rb_thread_t:0x00007fffcf775500 native:0x00007f9e39f90700 int:0 # depended by: tb_thread_id:0x00007fffcf3b7470 # fatal.rb:3:in `pop' # fatal.rb:3:in `block in <main>'
- 投稿日:2019-10-12T15:18:03+09:00
Docker環境でAtCoder〜Ruby編
AtCoderのRuby環境って特殊だよね?
AtCoderのRubyのバージョンは2.3.3です。ちょっと古いですね。AtCoderの時だけrbenvを使って2.3.3に戻してもいいですけど、Dockerを使ってもいいなぁとぼんやり思っているここの私!
というわけで、DockerでRubyのAtCoder環境を整えてみました。Dockerfileを書いてみた
DockerfileFROM ruby:2.3.3 ENV LANG="C.UTF-8" ENV USER yourname ENV HOME /home/${USER} RUN useradd -m ${USER} RUN gpasswd -a ${USER} sudo RUN echo "${USER}:password" | chpasswd RUN gem install at_coder_friends USER ${USER} WORKDIR ${HOME} CMD ["/bin/bash"]途中でインストールしているgemの
at_coder_friends
というのは、 AtCoderツールを自作した話 で @nejiko96 さんが作ってくださったAtCoderのテストツールです。テストだけではなく、回答のスケルトンも作ってくれたり、このツールから回答フォームにsubmitできたりとものすごく便利です。現在はRubyとC++に対応している模様です。
Usageなどが書かれたGitHubのリポジトリはこちら> AtCoderFriendsで、ビルドして実行してAtCoderを楽しみましょう!
docker build --tag at-coder . docker run --rm -it -v path/to/atcoder_problems:/home/yourname at-coder参考URL
https://qiita.com/Riliumph/items/3b09e0804d7a04dff85b
https://qiita.com/nejiko96/items/0cd23ac2c033864ef341
- 投稿日:2019-10-12T14:51:07+09:00
文系大学生がアプリ開発よりも機械学習の方が自学自走しやすいと考える理由について
はじめに
最近の風潮として、アプリの開発を一人で自学自走は頑張れば可能と言う考えが蔓延っていて、機械学習はなんか難しそうみたいな考え方があるように見受けられます。確かにアプリの開発を一人で自学自走する人はとてもすごいと思いますが、自分はアプリ開発を一人でやりきるのはとても難しく、また機械学習の自学自走はそれに比べると難しくないと考えています。
自分の感覚で言うと
・アプリ開発自走→天才じゃないと無理ゲー
・機械学習自走→頑張れば可能
という感じです(これは自分の感覚なので、個人差はあると思います)。その理由について述べていきたいと思います。
自分のそれぞれの経験について
まずその前に自分の(お前の経歴なんか興味ねえよって人はすっ飛ばして見て下さい)
アプリ開発
・実際に去年の3月にtechcampさんの方に通って勉強→3ヶ月ほどやるもそこで挫折
・今年の3月にpreogate→railsチュートリアルの流れで勉強をするも、railsチュートリアルで挫折機械学習
・去年の末から初めて今のところ挫折なし
詳しくは以下を参照
https://qiita.com/HayatoYamaguchi/items/1c20595c5e6dac4530dcこんな感じでやっています。確かに機械学習の方が本腰を入れて行なっていたという面だったり実際に一度アプリ開発でプログラミングを学んだ後だからというものもあるかもしれませんが、それを加味しても自分は機械学習の方が自学自走しやすいと考える理由をいかに述べたいと思います。
1、参照するページが多く、さらに自分がどこまで反映させたのかがわかりにくい
プログラミング初心者あるあるとして、まずは写生をしますがこの写生内容は往々にして「,」と「.」のミスなどの文字やスペルミスや、括弧やendの位置などのミスが起こりやすいです。その中でエラーメッセージを見てみると、そのメッセージが起きたサイトに言ってもエラーの根本的な原因はそのページではないなどのことが往々に発生します(例えばビューファイルのエラーなのに実際はコントローラーの中の変数の受け取りに問題があるなど)。そのためエラーの発掘が自分では困難になり、挫折しやすいです。一方機械学習の場合は最初はクラス分けを行わなければjupyter notebookなどの一つのファイルで完結します。そのため、エラーの行を見ればその行や周辺を直せば大体のエラーは解決します。
2、アプリ開発には必要な言語や概念が多すぎる
機械学習においてプログラミング面で必要なのはpythonやそれに付随する各種ライブラリ(numpy,pandas,sklearnなど)のみです。
しかし、アプリ開発を学ぼうとすると、例えばrailsだったらrailsチュートリアルを完遂して自分でポートフォリオを作ろうとすると、・htmlcss
・ruby
・javascript
・SQL
・Gitなどが必要で、データベースとの連携方法やさらにテストの実装についても理解も必要なことがあります。そのため何もやったことがない初心者が始めようとすると、「なんかごちゃごちゃしてるけど今は何をやればいいんだっけ、、、」という状態になりやすいです。
ここまで見ると機械学習の方がはるかに簡単そう、、、って思いそうなので、一応機械学習の方が難しい点も述べたいと思います。
機械学習の方が難しい点について
ある程度の数学や統計が必要である
アプリ開発にはほぼ一切数学の知識は求められません。
一方機械学習ではそうはいかず、ある程度アルゴリズムの中身を理解するとなると、大学基礎レベルの数学は必要になります。そのため数学アレルギーの人には厳しいです。論理的な式がより求められる
機械学習ではモデルを作る前にデータの変形などをする必要があります。そのため、「この条件の時にここを変形する」などの少し論理的なコードを書く力が必要です。例えばこれは自分の昔の記事から引っ張ってきたものですが、条件を指定して削除するなどの少し複雑なものになっています。これは簡単な例ですが、論理的な式を求められることが多いです。」
Xmat = Xmat.drop(Xmat[(Xmat['TotalSF']>5) & (Xmat['SalePrice']<12.5)].index)終わりに
これはあくまでもサンプル数1の考え方ですが、実際にこのような考え方を投稿している人がいなかったので投稿させてもらいました。何をやるかを考えている人には参考にしてもらえると嬉しいです。
- 投稿日:2019-10-12T12:38:31+09:00
VisualStudioCode でRubyの開発環境を作る
VisualStudioCode でRubyの開発環境を作る
利用環境
2019/10/12現在の最新バージョンを用いている。
rubyinstaller-devkit-2.6.5-1-x64
VisualStudioCode Ver1.39.1
Visual Studio Code Ruby Extension参考
基本的な手順はここのブログで提供されている情報のとおりである。
https://impsbl.hatenablog.jp/entry/RubyInVSCode1
バージョンの違い等もあるので、取得した画面ショットとともに、ここに整理する。手順
1.VisualStudioCodeのインストール手順は省略する。
2.ruby with Devkitのインストール
2019/10/12現在の最新バージョン rubyinstaller-devkit-2.6.5-1-x64
以下のとおり3.VisualStudioCodeへアドインのインストール
Visual Studio Code Ruby Extension
コンソールで、rubyが使えることを確認する。
4.Rubyの必要環境をgemでインストールする。
注意事項として、install?ruby-debug-ide は、最後にする。(依存関係がある)gem install rubocop gem install debase gem install rcodetools gem install ruby-lint gem install reek gem install faster gem install debride gem install ruby-debug-ide5.Visual Studio Code Ruby Extensionの設定
以下の画面のとおりに設定する。
Lintについては、以下のとおりjsonを編集する。
{ "ruby.codeCompletion": "rcodetools", "ruby.format": "rubocop", "ruby.intellisense": "rubyLocate", "ruby.lint": { "reek": true, "rubocop": true, "ruby": true, //Runs ruby -wc "fasterer": true, "debride": true, "ruby-lint": true }, "ruby.locate": { "exclude": "{**/@(test|spec|tmp|.*),**/@(test|spec|tmp|.*)/**,**/*_spec.rb}", "include": "**/*.rb" } }動作確認
- 投稿日:2019-10-12T12:17:22+09:00
order("id ASC") <=> order("id DESC)の切り替え Rails
備忘録及びアウトプットの練習のために、自分が学んだこと・工夫したこと・苦労した事などを書いていきたいと思います。
今回、orderメソッドを使うことにより、テーブルが呼び出したデータのソートができるということで、
画面上のスイッチをクリックするだけで、投稿内容の降順・昇順を切り替えれるようにしてみました。1.ビューの実装
ビューファイルにスイッチ用のコードを書く
= link_to "↕️", "switch", class: "switch", method: :get画面上の↕️をクリックしたら、"switch"に移動するようにしています。
個別にcssを適用できるように、"switch"というクラスもつけています。2.ルーティングの設定
1でswitchというパスを指定したので、対応するルーティングを設定します
get 'switch' => 'tweets/#switch'パス’switch'からtweetsコントローラのswitchアクションを実行するようにしています
3.コントローラの設定
2で指定したswitchアクションを作ります。
@@order = 1 def index if @@order == 1 @tweets = Tweet.order("id ASC") else @tweets = Tweet.order("id DESC") end end def switch @@order *= -1 redirect_to :action => "index" end切り替えに使用するクラス変数@@orderを定義します。
indexアクション内で、@@orderが1かそうでないかで、order("id ASC")かorder("id DESC")かが変わるようなif文を書きます。
次に、@@orderを切り替えるためのswitchアクションを定義します。
switchアクションを実行するたび、
つまりビュー上の↕️をクリックするたびに、クラス変数@@orderに-1をかけるようにします。
redirect_to :action => "index"とすることで、スイッチを押すたびに別画面に遷移することがなくなるようにします。これで、投稿の順番を変えるスイッチの実装が出来ました。
ルーティングの設定やクラス変数の利用など、色んな項目の復習になりました。
記事の内容は少しづつ更新していこうと思います。
- 投稿日:2019-10-12T12:17:22+09:00
ワンクリックでorder("id ASC") <=> order("id DESC)の切り替え
はじめに
備忘録及びアウトプットの練習のために、自分が学んだこと・工夫したこと・苦労した事などを書いていきたいと思います。
今回、orderメソッドを使うことにより、テーブルが呼び出したデータのソートができるということで、
画面上のスイッチをクリックするだけで、投稿内容の降順・昇順を切り替えれるようにしてみました。1.ビューの実装
ビューファイルにスイッチ用のコードを書く
= link_to "↕️", "switch", class: "switch", method: :get画面上の↕️をクリックしたら、"switch"に移動するようにしています。
個別にcssを適用できるように、"switch"というクラスもつけています。2.ルーティングの設定
1でswitchというパスを指定したので、対応するルーティングを設定します
get 'switch' => 'tweets/#switch'パス’switch'からtweetsコントローラのswitchアクションを実行するようにしています
3.コントローラの設定
2で指定したswitchアクションを作ります。
@@order = 1 def index if @@order == 1 @tweets = Tweet.order("id ASC") else @tweets = Tweet.order("id DESC") end end def switch @@order *= -1 redirect_to :action => "index" end切り替えに使用するクラス変数@@orderを定義します。
indexアクション内で、@@orderが1かそうでないかで、order("id ASC")かorder("id DESC")かが変わるようなif文を書きます。
次に、@@orderを切り替えるためのswitchアクションを定義します。
switchアクションを実行するたび、
つまりビュー上の↕️をクリックするたびに、クラス変数@@orderに-1をかけるようにします。
redirect_to :action => "index"とすることで、スイッチを押すたびに別画面に遷移することがなくなるようにします。終わりに
これで、投稿の順番を変えるスイッチの実装が出来ました。
ルーティングの設定やクラス変数の利用など、色んな項目の復習になりました。
記事の内容は少しづつ更新していこうと思います。
- 投稿日:2019-10-12T11:43:51+09:00
Mac OS CatalinaにEmacsをインストールする方法
準備
- App Storeから最新のXcodeをインストール
- Homebrewをインストール
Homebrewのホームページにインストール方法が記載されています。
Full Disk Access権限にrubyを追加
- システム設定を開く
- Security & Privacyを開く
- Privacyタブをクリックして、左のメニューからFull Disk Accessをクリックする
- 左下のロックボタンをクリックして、ロックを解除する
- 右側の「+」ボタンをクリックする
/usr/bin/
に移動し、rubyを追加するEmacsをインストールする
brew install emacs
Emacsのバージョンを確認する
emacs --version GNU Emacs 26.3 Copyright (C) 2019 Free Software Foundation, Inc. GNU Emacs comes with ABSOLUTELY NO WARRANTY. You may redistribute copies of GNU Emacs under the terms of the GNU General Public License. For more information about these matters, see the file named COPYING.
- 投稿日:2019-10-12T11:23:13+09:00
シンボルリテラル
シンボルの生成方法
シンボルのリテラルは、文字列の先頭にコロン「:」を付加する。
また文字列の囲み文字を省略できるため省略するのが一般的となっている。sample1.rbfoo1 = :"foo1" #=> :foo1 foo2 = :"#{foo1}foo2" #=> :foo1foo2 foo3 = :'foo3' #=> :foo3 foo4 = :foo4 #=> :foo4 #パーセント記法を用いたシンボルの指定も可能。その場合は%似続けてsを指定する。 %s?foo1? #=> :foo1 %s[foo2] #=> :foo2 #文字列からシンボルを生成する方法はto_symメソッドを使用する。 v1 = "foo1" #=> "foo1" v2 = v1.to_sym #=> :foo1 v3 = v2.to_s #=> "foo1"オブジェクトの同値性と同一性
文字列とシンボルの違い
文字列リテラルでは、毎回新たにオブジェクトを生成するためオブイェクトIDが変わる。
シンボルリテラルでは同じオブジェクトを参照するのでオブジェクトIDは変わらない。sample2.rbp "foo1".object_id #=> 31141068 p "foo1".object_id #=> 31141044 p :foo1.object_id #=> 124532 p :foo1.object_id #=> 124532 p :foo2.object_id #=> 124628 #シンボルは内部の整数として扱われるので文字列に比べると処理は早くなる。 #単にラベルとして文字の列を扱いたい場合は、文字列よりシンボルのほうが効率が良くなる。 #オブジェクトの同値性と同一性判定 "foo1" == "foo1" #=> true "foo1".equal? "foo1" #=> false "foo1" == "foo1" #=> true "foo1".equal? "foo1" #=> false #==と同様の動作をするメソッドにeql?メソッドがあるがこれは型まで比較する。 "foo1".eql? "foo1" #=> true 1.0 == 1 #=> true 1.0.eql? 1 #=> false 1.0.eql? 1.0 #=> true破壊的メソッド(※シンボルリテラルではないが説明としてここに記載)
破壊的メソッドを使用すると、他の変数の参照先にも影響する。
sample3.rbv1 = "foo1" v2 = v1 p v1.chop #=> "foo" p v2 #=> "foo1" p v1.chop1 #=> "foo" p v2 #=> "foo"
- 投稿日:2019-10-12T11:13:20+09:00
webpacker の check_yarn_integrity オプションについて調べてみた
はじめに
Rails アプリで webpacker を利用するケースが増えていると思います。Rails 5.1以上のアプリでは、
rails new myapp --webpack
というオプションをつける形でも webpacker をインストールできるようになっていますよね。しかし個人的に、使い方を理解しきれていないことが多いです。そこで理解するためのとっかかりとして、
config/webpacker.yml
のオプションを調べてみようと思い、その中のcheck_yarn_integrity
というオプションについて手始めに調べてみました。何をするオプションなのか
webpacker の README を抜粋します。
By default, in development, webpacker runs a yarn integrity check to ensure that all local JavaScript packages are up-to-date. This is similar to what bundler does currently in Rails, but for JavaScript packages. If your system is out of date, then Rails will not initialize. You will be asked to upgrade your local JavaScript packages by running yarn install.こちらを意訳してみました。
- development 環境では、ローカルの全ての JavaScript パッケージが最新であるかを yarn integrity check する
- Rails の
bundler
がやっていることと似てるが、これは JavaScript 用- システムが古くなっていると、Rails は初期化されない
yarn install
でローカルの JavaScript パッケージをアップグレードするか尋ねられるようになる実施していること
実際関連しているコード部分を抜粋すると以下になります。
config.webpacker.check_yarn_integrity = true
にしておけば、rails c
やrails s
といった Rails アプリケーションを起動する際の初期化時に、yarn check --integrity
を実行し、エラーが出たら以下のようなエラーメッセージを出力してくれるようです。======================================== Your Yarn packages are out of date! Please run `yarn install` to update. ======================================== To disable this check, please add `config.webpacker.check_yarn_integrity = false` to your Rails development config file (config/environments/development.rb).不要な場合は
config.webpacker.check_yarn_integrity = false
にすれば OK のようです。
パッケージが古くなっていたらエラーで知らせてくれるということなので、パッケージを最新の状態に保つ手助けになりそうですね。おわりに
JavaScript のパッケージ管理は複雑で理解が難しいですね。根気よく学習していきたいと思います。
参考
- 投稿日:2019-10-12T08:27:39+09:00
Railsのトランザクションと例外処理
Railsのトランザクションと例外処理がよく分からなかったので、調べました。
app/controllers/user_controller.rbdef create ActiveRecode::Base.transaction do # createではなく、create!にすると保存できなかったときに例外が発生します。 @user = User.create!(user_params) recue => e # バリデーションエラーだけ拾いたい場合は次の行 # (ActiveRecord:RecodeInvalid => e) # error処理 end endrecueはphpでいうとexceptionみたいな感じでした。
参考サイト
rails save! create! update!のバリデーション例外を捕捉する - Qiita
- 投稿日:2019-10-12T08:14:05+09:00
Railsのhas_oneで関連テーブルの保存
has_oneで関連させて保存させる方法につまったので書いておきます。
他のやり方(createを2回使う)もあったのですが、create!しても例外をキャッチできない?感じだったので、こちらの書き方にしました。app/controllers/users_controller.rbdef create @user = User.create(user_params) # この行のcreate_articleは状況に応じてprofile部分を読みかえて使います。 @profile = @user.create_profile(profile_params) endapp/models/user.rbhas_one :profileapp/models/profile.rbbelongs_to :user
- 投稿日:2019-10-12T02:19:12+09:00
ORDER BYを使わずにfind_eachっぽくデータを処理してメモリを節約する方法
ActiveRecord::Batches#find_eachについて
ActiveRecordの
find_each
は、メモリを節約しながら大量のデータを処理するためのメソッドです。
ActiveRecordのリファレンスには以下のようなことが書かれています。find_each(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil)
find_eachはデータベースのレコードをループ処理します。
大量のレコードが存在するとき、allを用いては、すべてのオブジェクトを一度にインスタンス化するため、メモリ効率がとても悪いです。
一方、find_eachのようなバッチ処理メソッドを使用すると、レコードをバッチで処理するため、メモリ消費が大幅に節約できます。
find_eachは、デフォルトで:batch_sizeが1000なので、1000件ずつのレコードを分割してループします。
https://api.rubyonrails.org/classes/ActiveRecord/Batches.html#method-i-find_eachつまり、10,000件のレコードを取得してcsvに加工したりといった処理を行いたいとき、find_eachを用いれば10,000件のレコードがメモリにのらずに済むということです。
find_eachを用いてActiveRecordが生成するクエリは、以下のようになります。User.find_each do |user| # do_something endSELECT `users`.* FROM `users` ORDER BY `users`.`id` ASC LIMIT 1000 SELECT `users`.* FROM `users` WHERE `users`.`id` > 1000 ORDER BY `users`.`id` ASC LIMIT 1000 SELECT `users`.* FROM `users` WHERE `users`.`id` > 2000 ORDER BY `users`.`id` ASC LIMIT 1000 SELECT `users`.* FROM `users` WHERE `users`.`id` > 3000 ORDER BY `users`.`id` ASC LIMIT 1000 ... (usersが全件取得できるまで繰り返す。10,000件存在していれば10回)
LIMIT 1000
とレコードを限定して取得し、WHERE users.id > 1000
のように条件指定することで、漏れ・ダブリなくレコードを取得するようになっています。LIMITを1000以外にしたい場合は、リファレンスにあるように、
find_eah(batch_size: 2000)
のように呼び出せば良いです。しかし、上記を見ると、
ORDER BY users.id ASC
という意図しないソートがクエリに含まれています。
リファレンスを確認すると、次のような注意書きがあります。NOTE: It's not possible to set the order. That is automatically set to ascending on the primary key (“id ASC”) to make the batch ordering work. This also means that this method only works when the primary key is orderable (e.g. an integer or string).
どうやら、find_eachは主キーでソートしてからLIMITをかけて分割取得することで、漏れ・ダブリなくデータを処理するといった実装になっているようです。
よって、
select(users.id AS user_id)
などと一緒に使うとエラーが出ます(id
というカラム名である必要がある)。
また、他のカラムでORDERしていると、それは無視されます。indexによっては、ORDERが入ることでクエリが遅くなってしまうことがあると思います。
ORDERは入れたくないが、分割して処理することでメモリ消費を削減したいとき、どう実現するかを考えてみました。実現方法
単純ですが、
id
をすべて取ってきて、each_slice
で任意の数ずつwhere
で取ってくることで、ORDERなしでfind_eachっぽく処理するようにしてみました。ids = User.pluck(:id) ids.each_slice(1000) do |id| User.where(id: id).each do |user| # do_somethins end endこれで発行されるクエリは以下のとおりです。
SELECT `users`.`id` FROM `users` SELECT `users`.* FROM `users` WHERE `users`.`id` IN (1, 2, 3, 4, 5, 6, 7, 8, ..., 1000) ... (全件取得するまで)これでORDERなしで、メモリを節約できました。
問題点
find_eachは安全のために
ORDER BY users.id ASC
しているわけで、例えばクエリ実行のたびに取得順序が変わるような場合は当然これは使用できません。
また、ids = User.pluck(:id)
でidを全件取ってくるため、ここでパフォーマンスが落ちることもあります。
結局、find_eachでORDERが入ってもいいように、indexを適切にできるならそうしたほうが良さそうです。
- 投稿日:2019-10-12T00:21:30+09:00
【52日目】掲示板にタグを設定しよう①!(seedについて、チェックボックスの作成、タグの保存・表示)
掲示板へのtag付け機能の追加
seedを使ってTagを追加しておく
seedとは
seedとは初期データのことで、システムの運用前に初期データとなるマスターデータ(あらかじめ用意するベースとなるデータ)を投入したり、開発段階で動作確認のために初期データを投入したりします。
他にもユーザー登録機能のある場合に、アドミン権限をユーザーにrollカラムをつける形で付与している場合などには、データベースをリセットする場合に備えて初期データとしてユーザー情報を投入する場合があるようです。seedファイルはdb/seeds.rbにcreateメソッドやrubyスクリプトによって記述します。
データベースに投入するにはrails db:seedをすれば良いです。seedファイルを使ったTagの初期データ作成
createメソッドでは、配列 []の中にハッシュ{key: 値}形式で複数のデータを同時に作成することができる。
seeds.rbTag.create([ { name: 'Ruby' }, { name: 'Ruby on Rails4' }, { name: 'Ruby on Rails5' }, { name: 'Python2' }, { name: 'Python3' }, { name: 'Django2' }, ])seedsファイルが編集できたらデータを投入する。
docker-compose exec web rails db:seedフォームにチェックボックスを作成
_form.html.erb<div class="form-group"> <span>タグ</span> <%= f.collection_check_boxes(:tag_ids, Tag.all, :id, :name) do |tag| %> <div class="form-check"> <%= tag.label class: 'form-check-label' do %> <%= tag.check_box class: 'form-check-input' %> <%= tag.text %> <% end %> </div> <% end %> </div>まず、多対多アソシエーションの設定によって、boardオブジェクトにはtag_idsプロパティが、tagオブジェクトにはboard_idsプロパティが追加されている。
ここでは、boardに登録されているtagの数分のチェックボックスを作成することになるため、フォームヘルパーのcollection.check.boxesメソッドを用いる。collection_check_boxesメソッドの構造確認
collection_check_boxes(第一引数, :第二引数, 第三引数, :第四引数, :第五引数 )第一引数 データを保存するテーブル(フォームヘルパー内の場合省略可能)
第二引数 データを保存するカラム
第三引数 第二引数のカラムに渡してあげるデータのリスト
ここで渡したデータの数分のチェックボックスが作成される。
第四引数 チェックボックスのvalueに設定されるプロパティ
第五引数 チェックボックスのラベル名に設定されるプロパティ(text)
=>この第二引数で渡したデータリストの数分、ブロック内が繰り返し実行される。
ブロック内にはチェックボックスを生成するHTMLが記述されていて、データの数分のチェックボックスが生成される。<%= tag.label class: 'form-check-label' do %> # タグラベルでチェックボックスとラベル名を覆う <%= tag.check_box class: 'form-check-input' %> # チェックボックス自体 <%= tag.text %> # 第五引数に指定したラベルが表示されるboards_controllerを修正して、チェックボックスを保存できるようにする
ストロングパラメータに対して、チェックボックスのパラメータ(第四引数)に指定したtag_idsを許可すれば保存できるようになる。
ただし、tag_idsは配列の形で複数渡ってくるので、[]をつけて配列であることを明示する必要がある。boards_controller.rbdef board_params params.require(:board).permit(:name, :title, :body, tag_ids: []) end掲示板詳細画面で関連づけられたタグを表示する
_detail.html.erb<% @board.tags.each do |tag| %> <span class="badge badge-primary"><%= tag.name %></span> <% end %>アソシエーションにおいては、「親モデル.」で、アソシエーション先の子モデルにアクセスできます。
つまり1行目はアソシエーションに設定したboard.tagsをループして、タグの数だけタグ名を表示するようにしている。
2行目では、ブートストラップのバッジのデザインを使用して、掲示板のタイトルの下に表示するようにしている。