- 投稿日:2019-11-15T23:50:36+09:00
とりあえずこれだけ。Rails ToDoアプリ作成【CRUD】勉強会 #1
初心者用Railsアプリ練習帳
勉強会を開き、Rails初学者に日頃教えています。初心者向け勉強回資料用もかねて作成。
ある程度基礎を学習していると、読みながら真似はできるけど、いまいち全体の知識がつながっていないので自分で作ることができない…そんな状態になりがちでした。
対象者
- Railsでなんでもいいからの教材をやった人
- Rails基礎はなんとなくわかってきた人
- Railsで自分でアプリ作るのはまだ無理そうという人
- Rails以外のフレームワークならわかる人
※ 正確さより概念とアプリ作成の流れを重視します。
コンセプト
他にも言っている方がいますが、勉強会で教えている経験から初学者が同じ教材を何周もするのは上達が遅いように感じています。
知識はあるけど自分で一から作るのは無理そう。
エラーが出て対処出来ないということになりがちでした。
こういう場合、実際に手を動かし、自分の頭で考えて簡単なアプリを作る必要があるけど、
- いきなりだと何を作ればいいのかわからない…
- 一連のアプリを作っていく順番がわからない…
- エラーでても対処できない…
- 簡単なのから作ってみればいいと言うが何が簡単なものなのか分からない…
- 何個もアプリ自分で作るといいらしいが何を作れば練習やスキル向上なるか分からない…
そんな人が未経験からプログラミングをやる人には何人もいました。
現状の私の考えですが、世の中にある教材は見た目とか完成度ないと売れないし、強い人達からツッコまれるので分かりやすさより精確さが必要とされる教材になってしまうようにおもいます。なので、後で身につければ良いこともあれもこれも付け足しており、本当にコレだけ!
という事項が初心者には見えにくくなっている気がします。それをうけて今回、ムダなものとにかく剥ぎまくって、コアな部分だけを数多く練習すればみんな出来るようになるのではないかと考え、練習問題を作りました。
今後も感想とかもらって継続的にブラッシュアップしていければと思っています。手順
- とりあえずToDoアプリをなぞって作ってみる(少しだけでも勉強した人が前提) ←今回はココ
- 超シンプルアプリを量産。手を動かして覚える!概念つかむ!
- データベースから欲しいデータを取得する!千本ノックのようにやりまくります!
2,3は個人的に開く勉強会で解説します。
とりあえずToDoアプリをなぞって作ってみる
環境構築はローカルでもいいですけど、WindowsやMacで違うので初心者はcloud9で作っていいと思います。
構築方法は今回省略以下のコマンドでアプリを作成。プロジェクト名になります。なんでもいいです。
今回、私はrails-todo1
にしました$ rails new rails-todo1沢山のファイルが作られる。終わったらプロジェクトのディレクトリに移動
$ cd rails-todo1サーバーを起動させて表示させてみる
$ rails sこんな画面が出たらOK
これから中身を作っていくので一応流れを確認
今回新規作成で一から作っていきますが、だいたいこんな感じで作ると考えていただければよいかと思います。
新しいアプリ(ページ)を作る手順
全体の流れで考えること
- 作るページはどんなページで作る?
- データベースに保存するデータは何か?
- モデルは作ったか?
- マイグレーションファイルを生成&データベースに反映
generate modelコマンドするとmigrationファイルも作られる。
カラム(データベースの列)を追加したり、データベースの中身を変えたいときにもマイグレーションファイル使う
rails db:migrate
でマイグレーションファイルの内容をデータベースに反映ページを表示するのか?データを登録・更新をするのか?
表示するならGET
データの登録ならPOST
データの更新ならPATCH (PUT)
データの削除ならDELETE
ルーティングを決める
URLにパラメータをいれる必要があるのか?(例えば各ユーザーの情報ページを表示するならparams[:id]のように書く。ならばURLに〇〇〇/:idのようにidを含める必要がある)
URLにパラメータいらない(新規登録や全データ表示など)
処理を行うコントローラーは作ったか
コントローラーのアクションは作ったか
GET?POST?PATCH?DELETE?
かを確認してつくるビューファイルはいるか?
さっそく簡単なアプリ作成していく
1.どんなもの作るか
2.データベースに保存するデータは何か
- テーブル名:Todo
- カラム名:title
- デフォルトで作られるカラム(ID, 作成日, 更新日)
3. モデルは作ったか
今回は、まだ何もつくってないので当然必要。
今回作るToDoアプリでは、Todoテーブルのデータ取得・新規登録・データ更新・データ削除
を行うのでModelを1つ作成しますというわけで、モデル作成。
$ rails generate model Todo title:string2.で書いたように必要なテーブルが
Todo
必要なカラムがtitle
なので、このように書いています。①
rails generate model Todo
でモデルファイルが作られる
②title:string
部分は追加したいカラム名と型。
この部分は書かなくても手動で記述すれば作れるのですが、コマンドでこのように打ったほうが楽ですdb/migrate/~~~~~~~~~_create_todos.rbclass CreateTodos < ActiveRecord::Migration[5.2] def change create_table :todos do |t| t.string :title t.timestamps end end end補足:モデルとは
普通はデータベースからデータを取得するときには
SQL
というものを書かないといけなくて初心者にはそれなりに複雑。
Railsではmodelというものを使うとSQL書かなくてもデータ取得できて便利。4. マイグレーションファイルを生成&データベースに反映
今回新規作成なのでモデルをつくるとmigrationファイルも作られるのでコマンドでmigrationファイルを作る必要ありません。
マイグレーションファイルの内容をデータベースに反映させるコマンド打つ
$ rails db:migrate補足:migrationファイル作る必要あるときは
migrationファイルとは:
データベースのテーブルの作成、変更を記述するためのファイルで、rails db:migrate
を実行することでデータベースに反映させる。
【コマンド詳細】カラムの追加。今回は実行必要なし
$ rails generate migration add_password_digest_to_users password_digest:stringこんな感じのコマンド入れてカラムの追加とかやります。
今回は当然新規必要なし
5. ページを表示するのか?データを登録・更新をするのか?
→ 表示するならGET
→ データの登録ならPOST
→ データの更新ならPATCH (PUT)
→ データの削除ならDELETE
Todoを追加するための機能を持つページを「表示」します。
ということでGET
メソッドです6. ルーティングを決める
ルーティングは
どのURLにアクセスすると、どのコントローラーとアクションを使うのか定義するものです。Railsには
resources
という便利機能があってルーティング書くのを便利にできる機能があるんですが、今回は原理を理解するために今回は一つ一つルーティングを記述していきましょう。config/routes.rbget '/todos', to: 'todos#index', as: 'todos' post '/todos', to: 'todos#create'ルーティングを確認する
コンソールに↓を入力するとルーティングを確認できる。
$ rails routesPrefix Verb URI Pattern Controller#Action todos GET /todos(.:format) todos#index POST /todos(.:format) todos#createもしくは
/rails/info/routes
をアドレスバーにつけると少し見やすい画面になる
http://sample.com/rails/info/routes
みたいな感じ
他タブで開いておくといちいちコマンド入れなくても確認できるので便利!現状では
/todos
にGETを設定してtodoコントローラー
のindexアクション
の処理を使えと指定している補足:_pathについて
$ rails routes
だとPrefix
がtodos
という名前になっていてURLに名前を付けて便利に使えるようになっている。これに_path
を追加すると使えます。
todos
+_path
でtodos_path
です。
todos_path
と書くと/todos
と書いたのと同じことになります。
結構使いますので覚えておくといいです。7. 処理を行うコントローラーは作ったか
コントローラーは大雑把に
ルーティング
とモデル
とビュー
をつなぐ役割、データ返す役割をしているものです。コントローラーは何をするか雑に説明
細かく言うとわかりにくくなるので大雑把に。
- ルーティングで該当のURLにマッチしたら、コントローラーのアクションに処理が飛んでくる。そして処理を行う。
- その際に
params
のデータもらうこともありTodo.new
使ってビューファイルで記述するform_with
(form_forも含む)で使うフォームでデータを送信するために必要なモデルの部品みたいなものが入った箱作ったりする。
- ビューで使うならインスタンス変数に格納する
Todo.find(params[:id])
みたいに自分で作ったModel
使ってデータベースの値を取得する。
Todo.find(params[:id])
はTodo.new
で作った部品に、取得したデータも含んだものだと思っておけばいいです。※下図参照- ビューで使うならインスタンス変数に格納する
- インスタンス変数はビューで使用可能なのでページを操作によってデータ表示やフォームの送り先を変えられる動的なページを作れる
初心者のうちはデータ取得したり、データを加工する、ビューで表示するためのデータを
@todo
みたいなインスタンス変数を作る!
みたいな感じでいいと思います。コントローラー作成コマンド
以下コマンドで、Controllerを作成します。
とりあえずコントローラーを生成する
$ rails g controller Todosこのコマンドならtodosというコントローラー作成されるので、
todos_controller.rb
というファイルができます。8. コントローラーのアクションは作ったか
app/controllers/todos_controller.rbclass TodosController < ApplicationController def index @todos = Todo.all end endコントローラーの処理を書いてみました。
Todoテーブルに入っているデータを全件取得(allメソッドを使う)してインスタンス変数の@todos
に入れています。9. ビューファイルはいるか?
ビューファイルは表示部分です。
htmlっぽい書き方だけどコントローラーなどで処理したインスタンス変数などを使って静的なページでなく動的なページを作ることができます。さきほど、
@todos
というインスタンス変数をコントローラーのindexアクションに書いたので、これを作って全データを表示してみましょう。app/views/todos/index.html.erb<h1>ToDo一覧</h1> <table> <thead> <tr> <th>タスク</th> </tr> </thead> <tbody> <% @todos.each do |todo| %> <tr> <td><%= todo.title %></td> </tr> <% end %> </tbody> </table>作成した画面
まだデータの登録がないので全件取得しても表示するものがないので、このような表示になっています。
こんな1~9までのような順序でアプリの作成(もしくは追加)をしていくことになります。
では、次にTodoの全件表示しかできない状態なので
次は新規ToDo追加できる機能を追加します新規追加機能の作り方
次に新規登録できる機能を作ります。
今度は1~9まの順序で機能を追加していきましょう。1.どんなもの作るか
ToDoテーブルにデータを新規追加する機能
テキストフィールドに入力した文字をボタン押すとデータベースに登録2.データベースに保存するデータは何か
テーブル名:Todo
カラム名:title
デフォルトで作られるカラム(ID, 作成日, 更新日)
title
だけデータベースに保存すればいいことになります。3. モデルは作ったか
モデルはTodoつかうので新しく作成の必要なし
4. マイグレーションファイルを生成&データベースに反映
モデルはTodo使う。カラムの追加も必要ないのでマイグレーションファイル作る必要も、データベースに反映する必要もない。
5. ページを表示するのか?データを登録・更新をするのか?
カラム名で
title
をデータベースに登録する処理を追加することになります。ページの表示をしているわけでも、情報の更新をしているわけでもなく、情報の削除でもありません。
こういうデータの登録処理は
POST
で行いましょう。6. ルーティングを決める
- メソッド:
POST
- ルーティング:
/tasks
- コントローラー&アクション
tasks#create
ルーティングを確認
実はすでに追加してしまっていたのですが、今回使うルーティングは↓の二つ目です。
routes.rbRails.application.routes.draw do get '/todos', to: 'todos#index', as: 'todos' post '/todos', to: 'todos#create' endページを表示しないで新規登録を行うのでPOSTですね。
7. 処理を行うコントローラーは作ったか
新しいモデルを作ったわけでも、わざわざ新しいコントローラーをつくる必要もありません。
todo
テーブルのToDoのデータ追加処理を追加するのでコントローラーはtodos_controller.rb
に追記すればOKです。8. コントローラーのアクションは作ったか
データ追加処理を記述したアクションを
todos_controller.rb
に追記していきましょう。コントローラーにアクションを追加
create
を追加しました。新規登録を行うためのアクションになります。app/controllers/todos_controller.rbclass TodosController < ApplicationController def index @todos = Todo.all @todo = Todo.new # フォーム作るために必要 end def create @todo = Todo.create(todo_params) redirect_to todos_path end end
redirect_to todos_path
としているのは、ページを表示しているわけでないのでリダイレクトしないと処理が終わっても処理が固まった感じになってしまうからです。処理としては正常に終了してるけど、↓のように何も起こってない…みたいな感じになる。
正常に登録はできているのでリロードするとちゃんと表示されることになる。
get '/todos', to: 'todos#index', as: 'todos'
のas: 'todos'
と書くと/todos
というURLにtodos
という名前を付けて使えるようにしています。この名前に、
ルーティングの名前(todosなど)
+_path
でtodos_path
のようにするといちいちURLを書く必要がなくなります。注意点として、Formで入力した情報を新規登録や更新する際はセキュリティ対策として
Strong Parameters
を使わないとエラーでますので追加します。Strong Parameters
セキュリティ対策で、入れないとエラーになります。
難しいこと省いて、フォームから受け取ったデータを渡すと、許可したデータだけ返却されるくらい覚えておけばとりあえずは良いと思います。そして追加したのが
todo_params
メソッド。また、private
を使うと、todo_paramsメソッドが外部から使えないようにできるのでセキュリティを高まります。
難しいことはいいので、privateの下にStrong Parameters
いれるんだなくらいで今はいいです。app/controllers/todos_controller.rbclass TodosController < ApplicationController def index @todos = Todo.all end def create @todo = Todo.create(todo_params) redirect_to todos_path end private def todo_params params.require(:todo).permit(:title) end end9. ビューファイルはいるか?
データベースでのデータの追加処理では使いません。
ただし、送信するためのテキスト入力フォーム
と送信ボタン
をindex.html.erb
に登録したいと思います。テキスト入力フォームと送信ボタン
index.html.erb<h1>ToDo一覧</h1> <table> <thead> <tr> <th>タスク</th> </tr> </thead> <tbody> <% @todos.each do |todo| %> <tr> <td><%= todo.title %></td> </tr> <% end %> </tbody> </table> <h2>ToDo追加</h2> <%= form_with model: @todo do |f| %> <div><%= f.label :title %></div> <div><%= f.text_field :title %></div> <div><%= f.submit %></div> <% end %>フォームを作るために
form_with
を使っています。
また、使うmodelを指定する必要があるので@todo
を指定してます。
コントローラーのindexアクションで追加します。
f.label
のようにすると「Title」というラベル(文字表示)され、
f.text_field
は文字を入力するフォームを作ることができる。
f.submit
は送信ボタン。
todos_controller
のindex
にフォームを作るための部品を用意するため@todo = Todo.new
を追加app/controllers/todos_controller.rbclass TodosController < ApplicationController def index @todos = Todo.all @todo = Todo.new # フォーム作るために必要 end end登録機能完成
データベースに登録する機能が完成しました!
今度は更新と削除
次は更新機能を実装します。
必要機能
- 情報更新ページ(今回はeditアクションとedit.html.erbにて)
- ルーティングを追加する
- editアクションで編集する情報を表示する todos_controller.rb
- ビューで見た目を作る edit.html.erb
- 情報更新処理
- updateアクションで情報の更新処理 todos_controller.rb
更新情報編集ページ用ルーティング追加
routes.rbRails.application.routes.draw do get '/todos', to: 'todos#index', as: 'todos' post '/todos', to: 'todos#create' get '/todos/:id/edit', to: 'todos#edit', as: 'edit_todo' end
get '/todos/:id/edit', to: 'todos#edit', as: 'edit_todo'
を追加しました。
todos_controller.rb
のedit
アクションを使用します。ルーティングの名前はedit_todo
とでもしておきます。更新情報編集ページ用コントローラー
editアクションは、情報編集画面(/todos/:id/edit)にアクセスすると処理されるアクションで、ビューで見た目を作るときに必要なデータの取得します。
editアクションを情報を追加します。
todos_controller.rbdef edit @todo = Todo.find(params[:id]) end
@todo
はインスタンス変数。@
とつけることでビューでもデータを使えるようになります。find
はデータベースのid
カラムからデータを探し出すときに使います。
- idはtodoを追加したときに自動的に採番されていきます。データベースの中身は下の表みたいになる。
- 編集画面は(/todos/:id/edit)のようにルーティングで設定した。
- 例えばURLが
http://sample.com/todos/1/edit
だとするとparams[:id]
は1
http://sample.com/todos/3/edit
だとするとparams[:id]
は3
http://sample.com/todos/1/edit
だとすると@todo = Todo.find(1)
と書いているのと同じになる試しに登録したデータベースの内容が↓になります。
id title created_at updated_at 1 テスト 2019-10-18 04:05:36.776003 2019-10-18 04:05:36.776003 2 買い物 2019-10-18 04:05:43.090180 2019-10-18 04:05:43.090180 3 勉強 2019-10-18 04:05:51.098753 2019-10-18 04:05:51.098753
@todo = Todo.find(params[:id])
でid
が1
だったら取得するデータをbyebugというデバッグツールで確認してみると
#<Todo id: 1, title: "テスト", created_at: "2019-10-18 04:05:36", updated_at: "2019-10-18 04:05:36">
というデータが取れます。
ここで注意点
教えていて勘違いしている方が多かったので補足。
今回
title
カラムのデータを取得したいわけです。
@todo = Todo.find(params[:id])
をするとtitle
のデータだけ
を取得しているのではありません。意図的にデータを絞り込まなければ、今回の場合
id
,title
,created_at
,updated_at
1レコード(1行)分のデータ取得されます。
@todo = Todo.find(params[:id])
idは1とすると正:
#<Todo id: 1, title: "テスト", created_at: "2019-10-18 04:05:36", updated_at: "2019-10-18 04:05:36">
誤:
#<Todo title: "テスト">
自分が必要だと思っているデータだけ取得できてると勘違いしている??ように思えましたので補足。
こらへんはSQLを理解していないから起こることなのかとも思います。
気を付けましょう。更新処理アクション追加
ルーティングとコントローラーに追記していきます。
resources使うともう少しシンプルに書けますが、今回は原理を知るためしっかりと書いていきます。routes.rbRails.application.routes.draw do get '/todos', to: 'todos#index', as: 'todos' post '/todos', to: 'todos#create' get '/todos/:id/edit', to: 'todos#edit', as: 'edit_todo' patch '/todos/:id', to: 'todos#update' # 更新処理用ルーティング追加 endapp/controllers/todos_controller.rbdef update @todo = Todo.find(params[:id]) @todo.update(todo_params) redirect_to todos_path endresouses使えるならこんな感じでシンプルに書ける
<-- resousesを使えるときなら --> <h1>ToDo編集</h1> <%= form_with model: @todo do |f| %> <div><%= f.label :title %></div> <div><%= f.text_field :title %></div> <div><%= f.submit %></div> <% end %> <%= link_to '戻る', todos_path %>今回原理をよく理解するためにこのようにURLとメソッドもちゃんと書きました。
#{ }
は文字列として変数を埋め込めるRubyの記法です。
変更するToDoデータのid
を指定すれば同じことを行えます。edit.html.erb<h1>ToDo編集</h1> <%= form_with(model: @todo, url: "/todos/#{@todo.id}", method: :patch) do |f| %> <div><%= f.label :title %></div> <div><%= f.text_field :title %></div> <div><%= f.submit %></div> <% end %> <%= link_to '戻る', todos_path %>編集ページへのリンクを作る
変更箇所付近だけ抜粋。
link_to
と~~~~_path
を使ってやるとこんな風に簡単に作れる。index.html.erb<% @todos.each do |todo| %> <tr> <td><%= todo.title %></td> <td><%= link_to '編集', edit_todo_path(todo) %></td> </tr> <% end %><td><%= link_to '編集', edit_todo_path(todo.id) %></td>ついでに、ちゃんと省略して書かないとこういう書き方になる。
todo
→todo.id
Railsがいい感じで判断してくれるのでこのようになりますが、原理は覚えておくほうがいいと思います。
htmlだと↓のようなものが作られる
<a href="/todos/1/edit">編集</a>
削除機能実装
削除機能はわかってしまえば他と大して変わらないので楽です。
1. ルーティング追加
2. コントローラーにdestroy
アクション追加
3. ビューに削除ボタンのリンク追加routes.rbRails.application.routes.draw do get '/todos', to: 'todos#index', as: 'todos' post '/todos', to: 'todos#create' get '/todos/:id/edit', to: 'todos#edit', as: 'edit_todo' patch '/todos/:id', to: 'todos#update' delete '/todos/:id', to: 'todos#destroy' end
@todo = Todo.find(params[:id])
で削除する項目(idで指定された項目)を@todoに入れる- destroyメソッドで削除する
- 元のページに戻る
app/controllers/todos_controller.rbdef destroy @todo = Todo.find(params[:id]) @todo.destroy redirect_to todos_path end
<td><%= link_to '削除', "/todos/#{todo.id}", method: :delete %></td>
を追加。
method: :delete
でメソッドを指定。(指定しないとデフォルトのGETになります。)
ルーティングでdeleteに設定したURLはdelete '/todos/:id', to: 'todos#destroy'
なので対応させるために"/todos/#{todo.id}"
このように書きました。index.html.erb<% @todos.each do |todo| %> <tr> <td><%= todo.title %></td> <td><%= link_to '編集', edit_todo_path(todo) %></td> <td><%= link_to '削除', "/todos/#{todo.id}", method: :delete %></td> </tr> <% end %>削除機能が完成しました。
見た目は最悪ですが、ToDo登録・更新・削除機能が完成しました。
- 投稿日:2019-11-15T23:27:45+09:00
Windows環境でVSCodeでのRuby on Railsの開発環境構築手順
※ Rubyの推奨環境はLinux?とはいえWindowsしか持ってないよ!って人向けです
1.Rubyのインストール
Rubyで開発する上で、最も必要なものはRubyのインストールなので
https://rubyinstaller.org/downloads/
からRuby + Devkitをダウンロードするダウンロードしたらexeファイルを実行してインストールをする
インストールしたら、
ruby -v
コマンドを実行、バージョンが表示されたならばOK
バージョンが表示されなければ、Rubyをインストールしたパスの下の/binフォルダにパスを通す2.Git bashのインストール
https://gitforwindows.org/
からgitをダウンロードし、インストールする3.VSCodeのインストール、環境整備
https://code.visualstudio.com/
より、VisualStudioCodeをダウンロードし
exeファイルを実行してインストールするEXTENSIONSを選択し、Rubyを選択してこれもインストールする
その後、VSCode左下の歯車をクリック
検索欄に、「terminal.integrated.shell.windows」と入力して
表示される「Edit in setting.json」をクリックする"terminal.integrated.shell.windows": "C:\\Program Files\\Git\\bin\\bash.exe"と入力する
すると、VSCode下部のターミナルがGit Bashに変更される
- 投稿日:2019-11-15T22:35:15+09:00
Ruby合宿2019夏に参加してきました
この度,8月19日~23日に島根県で開催されたRuby合宿2019夏に参加してきました.
本記事では,Ruby合宿の魅力を伝え,読者の皆さんに興味を持ってもらうことを目的としています.こんな人に読んでほしい
- プログラミングのイベントに参加してみたい
- 共同開発をしてみたい
- Rubyが好き
- Ruby合宿に興味はあるけど不安が大きい
- 参加したいけどプログラミングに自信がない
心配しなくて大丈夫
私自身も合宿に参加するまで不安でいっぱいでした.でも,全然大丈夫!
プログラミングの経験が少ない人もたくさん参加します.
さらに,合宿中は講師の方々から非常に手厚いサポートを受けられます.
Ruby合宿とは
Ruby合宿では、数名のチームでテーマに沿ったプログラムを協力して作成します.
(公式サイトより)
対象
学生(大学・高専・専門学校・高校等)または25歳未満の求職中の人で、以下の条件を満たす人
- チーム開発に興味のある人
- 将来,ソフト系IT産業で働きたいと考えている人
- 主催者が行うメール等でのアンケートにご協力いただける人
(公式サイトより)
費用
7,000円
私が参加したRuby合宿2019夏のテーマは,「島根県の名所をテーマとしたインタラクティブアニメーションアプリ」でした.
なんじゃそりゃ
要するにゲームです.(笑)みんなでゲーム開発!!楽しみ!!!
どんな感じでゲームを作るの?
合宿の参加者には,ゲームのみならず,何かを開発した経験のある人はほとんどいません.
私たちのグループも,”バリバリゲーム作れます!”なんて人はいませんでした.作るゲームを決める
グループで作りたいゲームについての意見を出し合って,おおまかなゲームの内容を決めます.
私たちのグループは「宝探しアクションゲーム」にしました.ゲームを作る
グループで役割分担してゲームを作っていきます.
プログラムを書く人,画像や音声を探す人,画像を編集する人などなど発表会
合宿最終日にはグループで作った作品の発表会を行います.
発表会には,島根の企業の方々もたくさんいらっしゃいました.
その他良かったこと
- まつもとゆきひろ氏の生講演会
- Ruby合宿Tシャツがもらえる
- IT企業の方と交流できる
- IT企業を訪問できる
正しい生活習慣が身につく合宿とは関係ないですが,帰りに松江駅で食べた出雲そばがとてもおいしかったです.(オススメ)
さいごに
この記事を最後まで読んでくださりありがとうございました.
あなたが少しでもRuby合宿に興味を持ってくださると幸いです.Ruby合宿への参加申し込みはRuby合宿公式サイトでお願いします.
- 投稿日:2019-11-15T21:41:23+09:00
クソコードあるあるを詰め込んだ「ひどいFizzBuzz」を書きました。
タイトルの通りです。
コードレビューの練習などにお使いください。hidoi_fizzbuzz.rbprint "数字を入力してください:" # 入力された数字をnに代入する。 n = gets.chomp.to_i result = "" number = 0 # 3の倍数かどうかを判定する。余りが0ならtrue、そうでなければfalse。 def fizz?(number) result = true if number % 3 == 0 then result = true else result = false end return result end # 3の倍数かどうかを判定する。余りが0ならtrue、そうでなければfalse。 def judge_buzz?(n) result = true if n % 5 == 0 then result = true else result = false end return result end # def fizzbuzz?(n) # result = true # if n % 15 == 0 then # result = true # else # result = false # end # return result # end # fizzbuzzを表示 if fizz?(n) == true then if judge_buzz?(n) == true then result = "fizzbuzz" puts result else result = "fizz" puts result end elsif judge_buzz?(n) == true then result = "buzz" puts result else puts n end(ちなみに僕はエンジニア歴1年程度のクソエンジニアです。こんなクソコードを実務で書いてしまわないように日々勉強しております・・・)
- 投稿日:2019-11-15T20:56:27+09:00
Rails Tutorialの知識から【ポートフォリオ】を作って勉強する話 #14 ユーザ投稿表示, ページネーション編
こんな人におすすめ
- プログラミング初心者でポートフォリオの作り方が分からない
- Rails Tutorialをやってみたが理解することが難しい
前回:#13 パスワード再設定編
次回:準備中今回の流れ
- 投稿用のモデルをつくる
- サンプル投稿を表示する
- テストをつくる
Tutorial13章(第4版)に突入しました。
今回はユーザ投稿の表示とページネーションを実装します。
(投稿機能は#15(準備中)で紹介します。)投稿用のモデルをつくる
ここでの手順は以下の通りです。
- Micropostモデルをつくる
- Userモデルを編集する
- バリデーションを追加する
- Micropostモデルを改良する
- エラー時の日本語化を行う
Micropostモデルをつくる
まずは投稿のためのMicropostモデルをつくりましょう。
ユーザが投稿できる項目は以下の通りです。
- 動画視聴時間
- メモ
- 画像
加えて投稿はユーザがいて初めて成立するので、Userモデルに所属させます。
そのためには生成時にuser:referencesを加えます。
以上を踏まえてMicropostモデルを生成しましょう。bash$ rails g model Micropost time:integer memo:text picture:string user:references投稿を表示する際、twitterのように最新のものが上部に来てほしいものです。
その準備として、マイグレーションにインデックスを加えます。db/migrate/[timestamp]_create_microposts.rbclass CreateMicroposts < ActiveRecord::Migration[5.2] def change create_table :microposts do |t| t.integer :time t.text :memo t.string :picture t.references :user, foreign_key: true t.timestamps end add_index :microposts, [:user_id, :created_at] end enduser_idは投稿したユーザ、create_atは投稿時間を管理しています。
これらを複合キーとすることで、望み通り取り出すことが可能です。Userモデルを編集する
MicropostモデルはUserモデルに所属しました。
この実装はbelongs_to :userにより、user_idとして形になりました。
今度はUserモデルにMicropostモデルを所有してもらいましょう。app/models/user.rbclass User < ApplicationRecord has_many :microposts # 中略 endhas_manyによりUserはMicropostと1対多の関係になりました。
こうすることでmicropostsを指定する時、こんな書き方が可能です。user = User.new user.microposts慣習的にも正しいので、以上の作業は忘れずに行いましょう。
バリデーションを追加する
ここでは以下のバリデーションを追加します。
- user_idが空の場合、Micropostを生成しない
- いずれの値も空の場合のみ(user_idを除く)Micropostを生成しない
app/models/micropost.rbclass Micropost < ApplicationRecord belongs_to :user validates :user_id, presence: true validates :memo, length: { maximum: 255 } validates :only_user_id, presence: true private def only_user_id time.presence or memo.presence or picture.presence end endいずれかという条件を実装するためにonly_user_idメソッドを定義しました。
バリデーションで『いずれか』を実装する詳しい方法↓
「いずれかのカラムが空でなければ良い」というバリデーションMicropostモデルを改良する
ここでは以下の機能を追加します。
- 新しい投稿を先にソートする
- ユーザが削除されたら投稿も削除する
app/models/micropost.rbclass Micropost < ApplicationRecord belongs_to :user default_scope { order(created_at: :desc) } # 中略 endapp/models/user.rbclass User < ApplicationRecord has_many :microposts, dependent: :destroy # 中略 endエラー時の日本語化を行う
あとはエラー時の言語を日本語にしましょう。
日本語化にはgemと設定が必要です。#6やRailsのバリデーションエラーのメッセージの日本語化を参考に設定を行なってください。お済みの方は以下のようにファイルを編集してください。
config/locales/models/ja.ymlja: activerecord: models: user: ユーザ micropost: 投稿 attributes: user: name: 名前 email: メールアドレス password: パスワード password_confirmation: パスワード(再入力) micropost: time: 記録時間 memo: メモ picture: 画像 user_id: ユーザID only_user_id: 入力欄後ほどビューをつくります。
その際エラー表示を確認するので、ひとまずここで終了です。
最後にデータベースを更新します。bash$ rails db:migrate
サンプル投稿を表示する
Micropostモデルは整いました。次は投稿を表示しましょう。
ここでの手順は以下の通りです。
- サンプル投稿を生成する
- サンプル投稿を表示する
- ページネーションを実装する
サンプル投稿を生成する
サンプル投稿を生成するにはfakerを使用します。
gemfile+ gem 'faker'
bash$ bundle installそれではサンプル投稿を生成します。
ついでにサンプルユーザも生成しておきます。db/seeds.rbUser.create!( name: "Example User", email: "example@railstutorial.org", password: "foobar", password_confirmation: "foobar", activated: true ) 5.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, activated: true ) end users = User.order(:created_at).take(3) 50.times do memo = Faker::Lorem.sentence(6) users.each { |user| user.microposts.create!(memo: memo) } end最後にデータベースを再構築しましょう。
bash$ rails db:migrate:reset $ rails db:seedこれでサンプルユーザとサンプル投稿が生成されました。
サンプル投稿を表示する
生成を終えたら投稿を表示しましょう。
ここでの手順は以下の通りです。
- usersコントローラを編集する
- ビューを編集する
- ページネーションを追加する
usersコントローラを編集する
投稿を表示させるビューはshow.html.erbです。
つまりこれに対応するコントローラにMicropostを渡す必要があります。
対応するのはusersコントローラです。編集しましょう。app/controllers/users_controller.rbclass UsersController < ApplicationController # 中略 def show @user = User.find(params[:id]) @microposts = @user.microposts.page(params[:page]).per(10) endこの1文はページネーションを実装する記述です。
@microposts = @user.microposts.page(params[:page]).per(10)ページネーションしたい対象に.page〜以下を加えることで動作します。
per(10)は1ページの表示数を表します。今回は10にしました。ビューを編集する
続いてはビューです。
パーシャルを使用しつつ実装しましょう。app/views/users/show.html.erb<% provide(:title, @user.name) %> <div class="container show-container"> <div class="row"> <div class="col bg-primary"> form </div> <div class="col bg-secondary"> figure </div> </div> <div class="row"> <div class="col"> <%= render 'layouts/log' %> </div> </div> </div>app/views/layouts/_log.html.erb<h1 class="log-title col-2">Logs</h1> <% if @user.microposts.any? %> <div class="container"> <ol class="microposts"> <% @microposts.each do |micropost| %> <li id ="micropost-<%= micropost.id %>"> <span class="row log-list"> <span class="col-2 log-timestamp d-none d-md-inline-block log-timestamp-block"> <span class="log-timestamp"><%= time_ago_in_words(micropost.created_at) %>前</span> </span> <span class="col-md-10 col-log-memos"> <div class="log-time-and-edit"> <div class="row"> <span class="log-time col-3"><%= micropost.time %>分</span> <span class="col-7 log-timestamp log-timestamp-inline"><%= time_ago_in_words(micropost.created_at) %>前</span> <span class="log-edit col-2"><%= link_to image_tag('edit.png', class: "log-edit-image"), '#' %></span> </div> </div> <span class="log-memo"><%= micropost.memo %></span> <span class="log-picture"><%= micropost.picture %></span> </span> </span> </li> <% end %> </ol> </div> <%= paginate @microposts %> <% else %> <span>まだ投稿がありません</span> <% end %>app/assets/stylesheets/application.scss.erb// max-width = 767px @media (max-width: 767px) { .nav-item-extend { margin-top: 0.6rem; } .log-title { padding: 0; } .log-list { border-left: 1px solid $lantern-dark-white; } } @media (min-width: 768px) { .log-timestamp-inline { visibility: hidden; } } // 中略 // logs ol { padding: 0; list-style: none; } .log-title { margin: 1.5rem 0; text-align: center; } .log-list { margin-bottom: 1rem; .log-timestamp { color: gray; } .log-timestamp-block { text-align: center; padding-bottom: 2rem; margin-top: 0.1rem; border-right: 1px solid $lantern-dark-white; } .col-log-memos { padding: 0 1rem 0 1rem; .log-time-and-edit { .log-time { font-size: 1.2rem; color: $lantern-yellow; } .log-timestamp-inline { text-align: right; margin-top: 0.1rem; padding: 0; } .log-edit { text-align: right; margin-top: 0.1rem; .log-edit-image { width: 1rem; } } } .log-memo { } .log-picture { } } } // paginate .pagination { margin-top: 1.6rem; .page-item { .page-link { border: 1px solid $lantern-light-white; background-color: $lantern-light-white; color: $lantern-blue; } } .page-item.active .page-link { background-color: $lantern-blue; border-color: $lantern-blue; color: $lantern-light-white; } } // 中略_log.html.erbのこの部分もページネーションを実装するための記述です。
<%= paginate @microposts %>コントローラに引き続き、2つの記述が見受けられました。
これらはkaminariというgemにより動作します。
準備は以上です。後はgemを挿れるだけです。ページネーションを追加する
それではkaminariを追加し、ページネーションを動作させましょう。
Bootstrap4と日本語化の適用も行います。gemfile+ gem 'kaminari' + gem 'kaminari-bootstrap'
bash$ bundle install $ rails g kaminari:views bootstrap4config/locales/models/ja.ymlja: # 中略 views: pagination: first: "« 最初" last: "最後 »" previous: "‹ 前" next: "次 ›" truncate: "..."これらを終えるとビューはこんな感じになります。
PC版↓
スマホ版↓
参考になります↓
【Ruby on Rails】gem(Kaminari)でページネーション機能を追加してBootstrapを適用する。
【Rails初心者】ページネーションを実装して自分好みにデザインを変えるテストをつくる
最後にテストを完成させます。
ここでの手順は以下の通りです。
- FactoryBotを整える
- テストを書く
FactoryBotを整える
まずはテストを行う前の準備をしましょう。
今回新たにMicropostモデルが生成されました。
それに伴うテストを行いたいので、FactoryBotで導入しましょう。ここでの手順は以下の通りです。
- テスト用のMicropostモデルを生成する
- テスト用のUserモデルを編集する
spec/factories/microposts.rbFactoryBot.define do factory :memos, class: Micropost do trait :memo_1 do time { 240 } memo { "I just ate an orange!" } picture { nil } user_id { 1 } created_at { 10.minutes.ago } end trait :memo_2 do time { 180 } memo { "Check out the @tauday site by @mhartl: http://tauday.com" } picture { nil } user_id { 1 } created_at { 3.years.ago } end trait :memo_3 do time { 59 } memo { "Sad cats are sad: http://youtu.be/PKffm2uI4dk" } picture { nil } user_id { 1 } created_at { 2.hours.ago } end trait :memo_4 do time { 207 } memo { "Writing a short test" } picture { nil } user_id { 1 } created_at { Time.zone.now } end association :user, factory: :user end endspec/factories/users.rbFactoryBot.define do factory :user do name { "Michael Example" } sequence(:email) { |n| "michael_#{n}@example.com" } password { "password" } password_confirmation { "password" } activated { true } end factory :other_user, class: User do name { "Sterling Archer" } sequence(:email) { |n| "duchess_#{n}@example.gov" } password { "foobar" } password_confirmation { "foobar" } activated { true } end factory :no_activation_user, class: User do name { "No Activation" } sequence(:email) { |n| "no_#{n}@activation.co.jp" } password { "foobar" } password_confirmation { "foobar" } activated { false } end endmicroposts.rbではtraitを使って、Micropostモデル内を区切りました。
users.rbではsequenceを使って、メールアドレスの一意性を保つよう番号をつけました。
以上で準備は完了です。※ 変更により他のテストが失敗する可能性があるので、適宜変更を加えてください。
Micropostモデルのテスト
それではいよいよテストに入りましょう。
このテストでは以下を確認します。
- モデルが正しく生成されているか
- いずれの値も空の場合(user_idを除く)、Micropostは存在しないか
- カラムが最新のものから順に並んでいるか
- user_idが存在しないMicropostは存在しないか
- memoが255文字を超えないか
spec/models/micropost_spec.rbrequire 'rails_helper' RSpec.describe Micropost, type: :model do let(:user) { create(:user) } let(:micropost) { user.microposts.build(time: 240, memo: "Lorem ipsum", user_id: user.id) } describe "Micropost" do it "should be valid" do expect(micropost).to be_valid end it "should not be valid" do micropost.update_attributes(time: 1, memo: " ", picture: nil, user_id: user.id) expect(micropost).to be_valid micropost.update_attributes(time: nil, memo: " ", picture: nil, user_id: user.id) expect(micropost).to be_invalid end it "should be most recent first" do create(:memos, :memo_1, created_at: 10.minutes.ago) create(:memos, :memo_2, created_at: 3.years.ago) create(:memos, :memo_3, created_at: 2.hours.ago) memo_4 = create(:memos, :memo_4, created_at: Time.zone.now) expect(Micropost.first).to eq memo_4 end end describe "user_id" do it "should not be present" do micropost.user_id = nil expect(micropost).to be_invalid end end describe "memo" do it "should not be at most 255 characters" do micropost.memo = "a" * 255 expect(micropost).to be_valid micropost.memo = "a" * 256 expect(micropost).to be_invalid end end endUserモデルのテスト
このテストでは以下を確認します。
- ユーザが削除されたら投稿も削除されるか
spec/models/user_spec.rbrequire 'rails_helper' RSpec.describe User, type: :model do # 中略 it "destroys assosiated microposts" do user.microposts.create!(memo: "Lorem Ipsum") expect{ user.destroy }.to change{ Micropost.count }.by(-1) end以上でテストは終了です。
次回は投稿機能を実装します。前回:#13 パスワード再設定編
次回:準備中
- 投稿日:2019-11-15T20:10:33+09:00
メモ:minitest-reporters導入方法
書くことが無いので、rails testの結果が見やすくなるgem、「minitest-reporters」の導入方法をメモしておこうと思う。
導入方法
Gemfileの「group :test do」の中に以下のように導入。
group :test do gem 'minitest' gem 'minitest-reporters' end加えて、test_helper.rbに以下の二行を追加。
test_helper.rbrequire 'rails/test_help' require "minitest/reporters" #1行目 Minitest::Reporters.use! #2行目gem 'minitest'にどういう役割があるのか、調べてもイマイチよくわからなかったが...
どうやらRailsのテストの方法はいくつかあるようで、minitestというのはそのうちの一つらしい。
今のところはminitest-reportersの前提modならぬ前提gem的な存在だと思っておくことにする(適当)。
- 投稿日:2019-11-15T17:45:39+09:00
【Ruby復習】クラスとインスタンスとinitialize
RubyとRailsを1.5週間くらいで駆け抜けて勉強しました。
忘れていることばかりだと思うので、基礎の基礎からまとめていきます。
今回は、クラス・インスタンスの概念、および初期化メソッドのinitialize
を復習します!よろしくおねがいします!クラスとインスタンス(とメソッド)の概念
まず「クラスとインスタンスってなんですか??」という問いにぶつかります。
詳細な技術的説明は、他のQiita記事や専門書を参照いただいたほうがいいと思うのですが、私はざっくり以下のように理解しています。クラス =>
〇〇製造工場
例:火星人製造工場インスタンス =>
〇〇製造工場(=クラス)で製造された、個別の〇〇
例:火星人製造工場であれば、「火星人のボブ」とか「火星人のエリック」とか個性をもつようになったエイリアンメソッド =>
メソッドとは、クラスやインスタンスが「行える一連の動作・機能」
例:クラスである火星人製造工場は、「火星人の製造に必要な材料を外部から受け取る」という動作(=メソッド)が出来る。
そのインスタンスである「火星人ボブ」は「火を起こす」動作(=メソッド)ができたり、「UFOを飛ばす」動作(=メソッド)ができる。クラスの作り方
クラスは、論理上次のように作成することができます。
class AlienMars1 #クラス名(大文字スタート、キャメルケース)* endですが、↑だけでは十分とは言えません。より正確には次のように作る必要があります。
*キャメルケースとは、「AlienMars」のように単語の切れ目が大文字になった表記の仕方です。「キャメル」は、英語で「ラクダ」を意味するCamelのことです。単語内の大文字が、ラクダの背中のように見えることからですかね。
class AlienMars1 def initialize() #初期化メソッド #処理1 #処理2 end end見慣れない
initialize()
が出てきましたね。
これはインスタンス(=固有の火星人たち)の作り方を解説したあとのほうが分かりやすいので、一旦脇に置いておきます。インスタンスの作り方
インスタンスは次のように作成します。
(なお下記のクラス定義は、先程見たようにinitialize
がない、不十分なかたちです。とりあえずインスタンス作成を解説するために省いています)class AlienMars2 end #newメソッドで、AlienMars2クラスからmars_peopleインスタンス(火星人インスタンス)を作成 mars_people = AlienMars2.new上記のようにインスタンスの作成には、該当のクラスに対して
new
メソッドを使います。それでは、ベターにinitialize
メソッドを用いてクラスを作成するにはどうすればいいのでしょうか?
initialize
メソッド
initialize
メソッドは、初期化メソッドと呼ばれ、作成するインスタンスに初期値を設定するためのメソッドです。例えば、AlienMars2クラスからインスタンスとして作成される宇宙人たちは、それぞれ固有の特徴(名前や色、年齢など)を持っているはずです。initialize
メソッドは、始めから持っているべきそういった特徴を設定します。
・new
メソッドによってインスタンスが作成される時点で呼び出される
・new
メソッドの引数が、initialize
メソッドの引数として渡されるclass AlienMars2 #newメソッド時に呼び出され、渡された引数を初期値として設定 def initialize(name) @name = name end end #newメソッドで、mars_Bobインスタンスを作成。引数は'Bob' mars_Bob = AlienMars2.new('Bob') #newメソッドで、mars_Erichインスタンスを作成。引数は'Erich' mars_Erich = AlienMars2.new('Erich')新しいことが一つだけ出てきましたね。
@name
の@
はなんでしょうか??
@name
は、インスタンス変数と呼ばれるものです。
詳しくは次回の復習時に記載しようと思うのですが、概要としては以下です。・それぞれのインスタンスが固有に持つデータ(名前なり年齢なり)のために存在する(別の言い方をすれば、「インスタンス間で共有しないデータ」のため、ということです)
・インスタンスに属するメソッドであれば、どのメソッドからでも参照できるおつかれさまでした!
ここまでお付き合いいただき、ありがとうございました!
次回はクラス変数、インスタンス変数、クラスメソッド、インスタンスメソッドあたりの復習をしようと思います!
- 投稿日:2019-11-15T16:50:53+09:00
eager_load + find_by でクエリが2回発行される件の対策
結論
よくある1:NのRelationがある時
class User < ApplicationRecord has_many :comments end class Comment < ApplicationRecord belongs_to :user endUser.eager_load(:comments).find_by(id: 1)とかすると
1. User.find_by(id: 1) のSQL
2. User.eager_load(:comments) のSQL
の2回SQLが発行されるUser.eager_load(:comments).where(id: 1).load.firstとかするとクエリが1回になる。
(.load
は.to_a
とかでもOK)ruby 2.5.1 rails 5.0.6/6.0.1(下記ログは6.0.1のものです)で確認済み。
詳細
普通にやってみたりしたけど、どうしてもただのfindっぽいクエリと、
まとめて情報をとってくるクエリの2回が発行されたUser.eager_load(:comments).find(1) # SELECT DISTINCT `users`.`id` FROM `users` LEFT OUTER JOIN `comments` ON `comments`.`user_id` = `users`.`id` WHERE `users`.`id` = 1 LIMIT 1 # SELECT `users`.`id` AS t0_r0, `users`.`name` AS t0_r1, `users`.`created_at` AS t0_r2, `users`.`updated_at` AS t0_r3, `comments`.`id` AS t1_r0, `comments`.`user_id` AS t1_r1, `comments`.`created_at` AS t1_r2, `comments`.`updated_at` AS t1_r3 FROM `users` LEFT OUTER JOIN `comments` ON `comments`.`user_id` = `users`.`id` WHERE `users`.`id` = 1 AND `users`.`id` = 1 User.eager_load(:comments).find_by(id: 1) # SELECT DISTINCT `users`.`id` FROM `users` LEFT OUTER JOIN `comments` ON `comments`.`user_id` = `users`.`id` WHERE `users`.`id` = 1 LIMIT 1 # SELECT `users`.`id` AS t0_r0, `users`.`name` AS t0_r1, `users`.`created_at` AS t0_r2, `users`.`updated_at` AS t0_r3, `comments`.`id` AS t1_r0, `comments`.`user_id` AS t1_r1, `comments`.`created_at` AS t1_r2, `comments`.`updated_at` AS t1_r3 FROM `users` LEFT OUTER JOIN `comments` ON `comments`.`user_id` = `users`.`id` WHERE `users`.`id` = 1 AND `users`.`id` = 1 User.eager_load(:comments).where(id: 1).first # SELECT DISTINCT `users`.`id` AS alias_0, `users`.`id` FROM `users` LEFT OUTER JOIN `comments` ON `comments`.`user_id` = `users`.`id` WHERE `users`.`id` = 1 ORDER BY `users`.`id` ASC LIMIT 1 # SELECT `users`.`id` AS t0_r0, `users`.`name` AS t0_r1, `users`.`created_at` AS t0_r2, `users`.`updated_at` AS t0_r3, `comments`.`id` AS t1_r0, `comments`.`user_id` AS t1_r1, `comments`.`created_at` AS t1_r2, `comments`.`updated_at` AS t1_r3 FROM `users` LEFT OUTER JOIN `comments` ON `comments`.`user_id` = `users`.`id` WHERE `users`.`id` = 1 AND `users`.`id` = 1 ORDER BY `users`.`id` ASC
WHERE `users`.`id` = 1 AND `users`.`id` = 1
とかめっちゃ冗長そうなクエリがある。
2回目のクエリだけでほしい情報は取れるはず
なんとかして1回のクエリにしたい![]()
User.eager_load(:comments).where(id: 1)
の場合は、クエリが1回なので、
これの時点でクエリを無理やり発火させてから、firstを取ればいいのでは?User.eager_load(:comments).where(id: 1).load.first # SELECT `users`.`id` AS t0_r0, `users`.`name` AS t0_r1, `users`.`created_at` AS t0_r2, `users`.`updated_at` AS t0_r3, `comments`.`id` AS t1_r0, `comments`.`user_id` AS t1_r1, `comments`.`created_at` AS t1_r2, `comments`.`updated_at` AS t1_r3 FROM `users` LEFT OUTER JOIN `comments` ON `comments`.`user_id` = `users`.`id` WHERE `users`.`id` = 1キタ━━━━(゚∀゚)━━━━!!
おわり。
クエリ整形ver
User.eager_load(:comments).find(1) # SQL (4.0ms) SELECT DISTINCT `users`.`id` FROM `users` LEFT OUTER JOIN `comments` ON `comments`.`user_id` = `users`.`id` WHERE `users`.`id` = 1 LIMIT 1 # SQL (2.8ms) SELECT `users`.`id` AS t0_r0, `users`.`name` AS t0_r1, `users`.`created_at` AS t0_r2, `users`.`updated_at` AS t0_r3, `comments`.`id` AS t1_r0, `comments`.`user_id` AS t1_r1, `comments`.`created_at` AS t1_r2, `comments`.`updated_at` AS t1_r3 FROM `users` LEFT OUTER JOIN `comments` ON `comments`.`user_id` = `users`.`id` WHERE `users`.`id` = 1 AND `users`.`id` = 1 # -- pretty format. # SELECT # DISTINCT `users`.`id` # FROM # `users` # LEFT OUTER JOIN `comments` ON `comments`.`user_id` = `users`.`id` # WHERE # `users`.`id` = 1 # LIMIT # 1; # # SELECT # `users`.`id` AS t0_r0, # `users`.`name` AS t0_r1, # `users`.`created_at` AS t0_r2, # `users`.`updated_at` AS t0_r3, # `comments`.`id` AS t1_r0, # `comments`.`user_id` AS t1_r1, # `comments`.`created_at` AS t1_r2, # `comments`.`updated_at` AS t1_r3 # FROM # `users` # LEFT OUTER JOIN `comments` ON `comments`.`user_id` = `users`.`id` # WHERE # `users`.`id` = 1 # AND `users`.`id` = 1; #=> #<User id: 1, name: "hogehoge", created_at: "2019-11-15 04:54:42", updated_at: "2019-11-15 04:54:42"> User.eager_load(:comments).find_by(id: 1) # SQL (1.0ms) SELECT DISTINCT `users`.`id` FROM `users` LEFT OUTER JOIN `comments` ON `comments`.`user_id` = `users`.`id` WHERE `users`.`id` = 1 LIMIT 1 # SQL (2.7ms) SELECT `users`.`id` AS t0_r0, `users`.`name` AS t0_r1, `users`.`created_at` AS t0_r2, `users`.`updated_at` AS t0_r3, `comments`.`id` AS t1_r0, `comments`.`user_id` AS t1_r1, `comments`.`created_at` AS t1_r2, `comments`.`updated_at` AS t1_r3 FROM `users` LEFT OUTER JOIN `comments` ON `comments`.`user_id` = `users`.`id` WHERE `users`.`id` = 1 AND `users`.`id` = 1 # SELECT # DISTINCT `users`.`id` # FROM # `users` # LEFT OUTER JOIN `comments` ON `comments`.`user_id` = `users`.`id` # WHERE # `users`.`id` = 1 # LIMIT # 1; # # SELECT # `users`.`id` AS t0_r0, # `users`.`name` AS t0_r1, # `users`.`created_at` AS t0_r2, # `users`.`updated_at` AS t0_r3, # `comments`.`id` AS t1_r0, # `comments`.`user_id` AS t1_r1, # `comments`.`created_at` AS t1_r2, # `comments`.`updated_at` AS t1_r3 # FROM # `users` # LEFT OUTER JOIN `comments` ON `comments`.`user_id` = `users`.`id` # WHERE # `users`.`id` = 1 # AND `users`.`id` = 1; # #=> #<User id: 1, name: "hogehoge", created_at: "2019-11-15 04:54:42", updated_at: "2019-11-15 04:54:42"> User.eager_load(:comments).where(id: 1).first # SQL (0.6ms) SELECT DISTINCT `users`.`id` AS alias_0, `users`.`id` FROM `users` LEFT OUTER JOIN `comments` ON `comments`.`user_id` = `users`.`id` WHERE `users`.`id` = 1 ORDER BY `users`.`id` ASC LIMIT 1 # SQL (0.6ms) SELECT `users`.`id` AS t0_r0, `users`.`name` AS t0_r1, `users`.`created_at` AS t0_r2, `users`.`updated_at` AS t0_r3, `comments`.`id` AS t1_r0, `comments`.`user_id` AS t1_r1, `comments`.`created_at` AS t1_r2, `comments`.`updated_at` AS t1_r3 FROM `users` LEFT OUTER JOIN `comments` ON `comments`.`user_id` = `users`.`id` WHERE `users`.`id` = 1 AND `users`.`id` = 1 ORDER BY `users`.`id` ASC # # SELECT # DISTINCT `users`.`id` AS alias_0, # `users`.`id` # FROM # `users` # LEFT OUTER JOIN `comments` ON `comments`.`user_id` = `users`.`id` # WHERE # `users`.`id` = 1 # ORDER BY # `users`.`id` ASC # LIMIT # 1; # # SELECT # `users`.`id` AS t0_r0, # `users`.`name` AS t0_r1, # `users`.`created_at` AS t0_r2, # `users`.`updated_at` AS t0_r3, # `comments`.`id` AS t1_r0, # `comments`.`user_id` AS t1_r1, # `comments`.`created_at` AS t1_r2, # `comments`.`updated_at` AS t1_r3 # FROM # `users` # LEFT OUTER JOIN `comments` ON `comments`.`user_id` = `users`.`id` # WHERE # `users`.`id` = 1 # AND `users`.`id` = 1 # ORDER BY # `users`.`id` ASC; #=> #<User id: 1, name: "hogehoge", created_at: "2019-11-15 04:54:42", updated_at: "2019-11-15 04:54:42"> User.eager_load(:comments).where(id: 1).load.first # SQL (1.0ms) SELECT `users`.`id` AS t0_r0, `users`.`name` AS t0_r1, `users`.`created_at` AS t0_r2, `users`.`updated_at` AS t0_r3, `comments`.`id` AS t1_r0, `comments`.`user_id` AS t1_r1, `comments`.`created_at` AS t1_r2, `comments`.`updated_at` AS t1_r3 FROM `users` LEFT OUTER JOIN `comments` ON `comments`.`user_id` = `users`.`id` WHERE `users`.`id` = 1 # SELECT # `users`.`id` AS t0_r0, # `users`.`name` AS t0_r1, # `users`.`created_at` AS t0_r2, # `users`.`updated_at` AS t0_r3, # `comments`.`id` AS t1_r0, # `comments`.`user_id` AS t1_r1, # `comments`.`created_at` AS t1_r2, # `comments`.`updated_at` AS t1_r3 # FROM # `users` # LEFT OUTER JOIN `comments` ON `comments`.`user_id` = `users`.`id` # WHERE # `users`.`id` = 1; #=> #<User id: 1, name: "hogehoge", created_at: "2019-11-15 04:54:42", updated_at: "2019-11-15 04:54:42">
- 投稿日:2019-11-15T16:32:57+09:00
link_to をブロックで囲って each で回す方法(link_to do ~ end)
user/index.html.rb<% @users.each |user| do %> <%= link_to user_path(user) do %> <%= @user.name %> <% end %> <% end %>上記のように、link_to do〜end で囲えば良い。
- 投稿日:2019-11-15T15:09:33+09:00
たのしいRuby5版22章で躓いたこと【エンコード関係】
Rubyの知識を復習するため、『たのしいRuby第5版』という書籍を読んでいます。
今回は、その22章の「テキスト処理を行う」というところで詰まった箇所とその解決方法を書いていこうと思います。環境
OS:windows10
詰まったところ
まず、青空文庫から江戸川乱歩『二銭銅貨』をダウンロードします。
nisendouka.rb#ダウンロードするライブラリ require "open-uri" # 青空文庫のURL url = "https://www.aozora.gr.jp/cards/001779/files/56647_58167.html" # 書きこむファイル名 filename = "nisendouka.html" # バイナリモードでファイルを開きます File.open(filename, "wb") do |f| text = open(url).read #ここがよくない # nisendouka.htmlに書き込む f.write text endこのコードを実行すると、nisendouka.htmlという名前のHTMLファイルが作成され(すでに存在する場合は上書きされます)指定したURLのhtmlファイルが書き込まれます。
問題は次のコード
cut_nisen.rbhtmlfile = "nisendouka.html" textfile = "nisendouka.txt" html = File.read(htmlfile, encoding: "UTF-8") open(textfile, "w:UTF-8") do |f| in_header = true html.each_line do |line| if in_header && /<div class="main_text">/ !~ line next else in_header = false end break if /<div class="bibliographical_information">/ =~ line f.write line end end処理の内容としては、 <div class="main_text" と <div class="bibliographical_information" で囲まれた部分(本文)を取り出すという処理ですが、これを実行するとエラーが出ました。
# エラー内容 cut_nisen.rb:9:in `=~': invalid byte sequence in UTF-8 (ArgumentError)初めてみるエラーだったので調べてみました。
エラー原因
調べてみると、どうもUTF-8以外の文字コードで読み込んでいる(?)ことが原因で起こるエラーのようでした。(参考)
確かに元の青空文庫の文字コードはSHIFT-JISとなっていたので、それをUTF-8で読み込もうとしたことがよくなかったようです。
そこで青空文庫から読み込む際の文字コードをSHIFT-JIS、HTMLファイルに書き込む際の文字コードをUTF-8と明示することでエラーが解消されました。
nisendouka.rb# 変更後のコード #ダウンロードするライブラリ require "open-uri" url = "https://www.aozora.gr.jp/cards/001779/files/56647_58167.html" filename = "nisendouka.html" File.open(filename, "wb") do |f| text = open(url, "r:shift_jis").read #文字コードをshift-jisに指定 f.write text.encode("utf-8") # 文字コードをutf-8に変更 endまとめ
スクレイピングなどをするときは文字コードに気を付けることが重要だと思いました。
補足
Stringクラスのencodingメソッドを使うとその文字列の文字コードを確認することができます
sample.rbstr = "こんにちは" p str.encoding #=> #<Encoding:UTF-8>
また、Rubyのデフォルトの文字コードはUTF-8ですが、Encoding.default_internalを使うことでencodeメソッドによる文字コードを指定することができます。手元の環境で実行したのですが、UTF-8のままになってしまいました…。
原因の分かる方、教えていただけると幸いです。sample2.rbEncoding.default_internal = "Shift_JIS" s = "太郎" puts s.encodeくわしくはこちらのRubyリファレンスをご覧ください。
あとこちらのブログもかなり参考になりそうです(2012年のものなので情報の正確性に注意!)
- 投稿日:2019-11-15T14:12:57+09:00
CookieにSameSite属性がセットされていた時の標準エラーをとりあえず回避した話
GoogleDriveが数日前よりCookieにSameSite属性(None)をセットするようになった。
Rubyの標準ライブラリであるWebAgent::CookieManagerはSameSite属性に対応していない。
また、未知の属性がCookieにセットされていた場合は、標準エラーを吐く。
従って、Cronでスクリプトを起動していると、標準エラー発生を通知するメールが、毎時届くようになってしまった。と、多分、このようなことが起きていたのだと思う。
メールがウザすぎるので、parseメソッドだけオーバーライドしてみた。
標準エラーでなく例外を吐いてくれたら、こんなことせずに済むのだけれど、多分、深い理由があるのでしょうね。core_ext.rbclass WebAgent class Cookie def parse(str, url) @url = url cookie_elem = str.split(/;/) cookie_elem = join_quotedstr(cookie_elem, ';') cookie_elem -= [""] first_elem = cookie_elem.shift if first_elem !~ /([^=]*)(\=(.*))?/ return end @name = $1.strip @value = normalize_cookie_value($3) cookie_elem.each{|pair| key, value = pair.split(/=/, 2) key.strip! value = normalize_cookie_value(value) case key.downcase when 'domain' @domain = value when 'expires' @expires = nil begin @expires = Time.parse(value).gmtime if value rescue ArgumentError end when 'path' @path = value when 'secure' @secure = true when 'httponly' @http_only = true when 'samesite' # samesite属性 @samesite = true else warn("Unknown key: #{key} = #{value}") end } end end end
- 投稿日:2019-11-15T14:00:46+09:00
[メモ]2019/11/15 Ruby on Rails
Railsでページを表示するのには以下の3つのファイルが必要
ページの作成に必要なもの
・ビュー(View)
・コントローラ(Controller)
・ルーティング(routing)
ビュー(View)
ビュー(View)とはページの見た目を作るHTMLファイル。
ブラウザとのやり取りの中で,Railsからビューが返されページが表示される。コントローラ(Controller)
ページを表示するときに、Railsではコントローラを経由してビューをブラウザに返す。
「rails g controller home top」を実行したとき、「home_controller.rb」というコントローラファイルが生成され、ファイルの中に「topメソッド」が追加される。
コントローラーのメソッドを「アクション」と呼ぶ。メソッドとは、「オブジェクト(もの)の操作を定義したもの」
例)オブジェクト:マリオとすると
メソッド 操作
・ジャンプする。
・走る
・しゃがむetc...
3. ルーティング(routing)
ブラウザとコントローラを繋ぐ。ページが表示されるまでに、
ルーティング→コントローラ→ビューという順番で処理が行われる。
- 投稿日:2019-11-15T13:47:33+09:00
railsの復習
Railsでの処理のポイント
- ルーティング
- コントローラー
- モデル
- ビュー
モデル、ビュー、コントローラーをまとめて
MVCという。HTTPメソッド
- GET ページを表示する操作
- POST データを登録する操作
- PUT データを変更する操作
- DELETE データを削除する操作
コントローラ アクション
- index 一覧表示リクエストアクション
- new 新規投稿リクエストアクション
- create データ投稿リクエストアクション
- show 個別詳細表示リクエストアクション
- edit 投稿編集表示リクエストアクション
- update データ編集リクエストアクション
- destroy データ削除リクエストアクション
部分テンプレート
renderメソッドを使う。
renderメソッドのpartialオプションでファイルの指定を行う。
部分テンプレートのファイル名の先頭は「_(アンダーバー)」で始める。★以下、省略表示が鬼。順々に教えてもらいたいところ。部分テンプレートの理解が浅いうちから最終省略形見せられたら大混乱間違いなしだわ・・・。でも改めて見ると、やりたいことをササッとやってくれ、って考えると最終形の方が直感的に分かるか、って気がしないでもないが、経緯を理解した上で使ってかないと、部分テンプレートがどれなのか分からなくなりそう。
<% @users.each do |user| %> <%= render partial: "user", locals: { user: user } %> <% end %><%= render partial: "user", collections: "@users" %><%= render @users%>フォーム入力データの取得
form-tagに入力されたデータは、params.permit(フォームの名前定義)で取得。
form-withに入力されたデータは、params.require(モデル名).permit(フォームの名前定義)で取得。
form-withの場合、form-tagに比較して深い階層に保存される為、require()が必要になる。フォームの入力データは、プライベートメソッドにて取得することで、Classの外部から呼び出されないようにする。バリデーション 一定の制約をかける
空のデータを登録できないようにするような場合に使用。
validates フォームの名前定義, presence: truelink_to
link_to 表示するテキスト, prefixもしくは、URIパターン, HTTPメソッド
HTTPメソッドを省略した場合は、GETで実行されるので注意する。ridirect_to
コントローラのビューに関わらず、特定のページにとばす。
ridirect_to アクション名 (実行条件?)コードの記述(試験対策)
スコープの意識は・・・まだあんまりしなくていいのかな。
コードの書き順はよく確認すること。routes.rbのコードの書き順に注意。
いつもは頭に書いてるのが末尾にあったり、
先に定義していないといけないコードを、後ろに書いてあったりする。
railsのエラー修正問題ヤベェな。
どないしょ。
- 投稿日:2019-11-15T13:04:34+09:00
Rubyのcompact! に注意。
経緯
compact!メソッドによって仕事でバグを生み出してしまったので、備忘録としてアウトプット
compact、compact!の戻り値
compact
、compact!
は配列からnil
を弾くが、配列にnil
が含まれていない時、compact!
の戻り値が予想していないものだった。。。## arrayがnilを含んでいる時 array = [1,2,3,4,5,nil] # compact array.compact => [1, 2, 3, 4, 5] # compact! array.compact! => [1, 2, 3, 4, 5] ## arrayにnilがない時 array = [1,2,3,4,5] # compact array.compact => [1, 2, 3, 4, 5] # compact! array.compact! => nil #ふぁ!?!?結論
nil
を含んでいない配列にcompact!
を使うと、「nil含んでねーじゃねーか」的な感じでnil
が返ってくるので、注意!!!
- 投稿日:2019-11-15T12:42:48+09:00
ApplicationControllerでStandardErrorをrescue_fromするときに少しでも開発しやすく
背景
Ruby on RailsでAPIサーバーを実装するとき、予期しないバグでアプリケーションに例外が起きたときに、500エラーレスポンスで固定のメッセージを返したいときがあります。
ApplicationController
に、次のような関数呼び出しを書いて、アプリケーションで共通の例外処理を実装するでしょう。rescue_from StandardError, with -> do render json: { message: 'Something wrong' }, status: :internal_server_error end問題
問題1: 開発中も例外が表示されない
開発中に出た例外が起きたときも、固定メッセージしか情報が得られません。
デバッグのためにいちいち上記の実装をコメントアウトするのも手間です。解法: 環境変数を見て例外処理をスキップ
Ruby on Railsは環境変数で開発環境と本番環境を切り替えます。
このルールに乗り、次のように開発中の場合は例外処理をスキップします。rescue_from StandardError, with -> do # 開発中は例外をキャッチしません。 raise exception if Rails.env.development? render plain: 'Something wrong', status: :internal_server_error end問題2: テストではレスポンスコードやメッセージを本番と同じにしたい
RSpec
などのテストコードでは本番の振る舞いを確認したいです。
ですので開発中環境のように、例外処理そのものをスキップする実装は望ましくありません。解法: 例外の内容を標準出力に
例外処理の実装はそのままに、例外の内容を標準出力に書き出します。
本場環境では余計な情報が出力されないように、ここでも環境変数を見て出力するかどうかを切り替えます。rescue_from StandardError, with ->(exception) do puts exception.backtrace if Rails.env.test? render json: { message: 'Something wrong' }, status: :internal_server_error end問題3: 例外のバックトレースが長すぎて読みにくい
通常
RSpec
を実行する時は、複数のテストケースを実行します。
予期しない例外が起きる時は、複数のテストケースで起きることがあるでしょう。
そのときにバックトレースをすべて表示すると標準出力の表示が流れすぎて見るのが大変です。解法: ActiveSupport::BacktraceCleanerで出力するバックトレースの情報を減らす
Ruby on RailsにはActiveSupport::BacktraceCleanerというバックトレースの情報を減らすためのクラスが用意されています。
これを使って、gem
(依存ライブラリ)とRSpec
に関するバックトレースの表示をしません。rescue_from StandardError, with ->(exception) do if Rails.env.test? bc = ActiveSupport::BacktraceCleaner.new bc.add_silencer { |line| line =~ %r{gems|/rspec} } puts bc.clean exception.backtrace end render json: { message: 'Something wrong' }, status: :internal_server_error end問題3: 例外の色が黒字だと出力から例外情報をみつけられない
通常
RSpec
を実行する時は、複数のテストケースを実行します。
予期しない例外が起きる時は、複数のテストケースで起きることがあるでしょう。
たくさんの標準出力の内容から例外を目grepするのは大変です。解法: 例外表示の1行目を赤くする
多くのターミナルはANSI escape codeを使うと出力文字に色をつけられます。
これを使って例外名に色を付けます。
またRSPecのエラーと合わせて赤字にすると、情報が必要以上に増えず読みやすくなります。rescue_from StandardError, with ->(exception) do if Rails.env.test? puts "\e[31m", exception.class, "\t#{exception.message}\e[0m" bc = ActiveSupport::BacktraceCleaner.new bc.add_silencer { |line| line =~ %r{gems|/rspec} } puts bc.clean exception.backtrace end render json: { message: 'Something wrong' }, status: :internal_server_error end問題4: 独自例外が出力される
アプリケーション固有の例外処理を独自例外を使って共通化することがあります。
例外処理の対象をStandardError
にしていると、これらの独自例外も標準出力に表示します。
また、共通の例外処理が上手く実装できているか確認するためのテストコードを書くこともあります。
その場合は、テストを実行すると常に独自例外が標準出力に表示されます。
バックトレースまで表示するので、馬鹿になりません。
偽陽性に慣れると、本当の例外まで見逃すようになります。解法: 独自例外の時は標準出力に表示しない
例外の出力処理に、キャッチした例外が自作の独自例外か確認するガード条件を追加します。
rescue_from StandardError, with ->(exception) do if Rails.env.test? # MyErrorは、意図した例外なので出力しません。 puts_detail_of exception unless exception.is_a? MyError end render json: { message: 'Something wrong' }, status: :internal_server_error end def puts_detail_of(exception) bc = ActiveSupport::BacktraceCleaner.new bc.add_silencer { |line| line =~ %r{gems|/rspec} } puts "\e[31m", exception.class, "\t#{exception.message}\e[0m" puts bc.clean exception.backtrace endおわりに
良き開発を、例外とともに
参考文献リスト
- 投稿日:2019-11-15T12:37:41+09:00
独学でプログラミング学習を始めて1.5か月の振り返りと独学者へのアドバイス
独学でプログラミング学習を始めて1.5か月ほどがたちました。転職、ポートフォリオ等なにも成果がない状態ですが、私自身の振り返りとアウトプットの習慣付けのために学習内容や反省点、これから独学される方へのアドバイスなどを投稿します。技術的なことは書いていません。
対象読者
・独学でプログラミング学習を始める方
・独学で挫折しそうな方自己紹介
・24歳、新卒1年目、メーカー営業職で工場研修中(長い)
・過去2度プログラミングの独学挫折(HTML,CSS,PHP)
・2109年9月末から3度目の独学スタートなぜ独学か
・お金がないからです。
学習内容
・HTML(復習)
・CSS(復習)
・Ruby
・Ruby on Rails
・MySQL
・Bootstrap
・AWS
・Docker
チュートリアルやドットインストール、YouTubeなどの教材を利用し、手を動かしながら学習しています。現在はVM上のcentOS環境にDocker×Rails×MySQLでポートフォリオ製作中です。学習時間
・平日2時間
・休日4~10時間
平日は仕事前に0~1時間、帰宅後1~2時間という感じです。睡眠時間を削ることもありましたが翌日の体調が著しく悪化するため睡眠優先してます。その他、通勤・休憩などでPCを触れない自由時間が4時間以上あるので、その時間に技術書を読んだり、用語の理解に努めたりしています。
土曜日は朝から晩まで。日曜日は最低半日(約4時間)で後はリフレッシュに時間を使うようにしています。成長したこと
・独学で1.5か月継続している!!
過去2回は2週間ほどで辞めてます。「エラーが出ることと向き不向きは関係ない」と考えるようになったのが一番の要因だと思います。・コンピューター、プログラムとは何かということを学んだ
「コンピューターにはどんな機能があるのか」、「プログラムとは何か」を理解しないままプログラミングやってみたいと言っていた自分から卒業しました。・公式ドキュメントを参考にするようになった。
Qiitaファーストでコピペとかしてましたが、用語や概念を少しずつ理解していくうちに公式ドキュメントに抵抗がなくなりました。(幸い英語のリーディングには抵抗がありません。)反省
・プログラムを書く時間が少ない。
一番の反省点は、肝心のRuby, Rails, SQLへの理解が深まる前からDockerやAWSにも手を出してしまったことです。ただでさえ学習効率が悪いにも関わらず、DockerやAWSでの環境構築に時間がかかるうちに学習したことが頭から抜けていくという状態でした。・アウトプット不足
エラーを解決した経験、隙間時間に学んだ用語などをQiitaに投稿する習慣が大切だと分かっていながらなかなかできませんでした。今の悩み
・ポートフォリオがRailsチュートリアルのサンプルに寄せられていく。デザインに気を遣わないとチュートリアルと何が違うのかわからなくなります。
今後の方針
・基礎固め
ポートフォリオ製作を通して今まで学んだ技術への理解を深めていきます。新しい技術には手を出しません。・アウトプットの習慣化
独学の最大のデメリットはアウトプット→フィードバックの機会が少ないために学習効率が悪いことだと感じています。(自走というより自歩です。)
自らアウトプット+フィードバックを得られる場を確保しなければインプットの質も下がり、自己満の勉強になってしまいます。
MENTAなどのサービスでメンターお願いします。独学で学習を始めた方へアドバイス(自戒を込めて)
・エラーは解決できる
個人的に過去の挫折の原因は「エラーが出た→プログラミングに向いてない...」と考えてしまうことがでした。
「エラー発生→対応→解決しない」ということがよくあると思います。「対応して解決しなかった」ということは、プログラミングの向き不向きとは関係がありません(少なくとも始めて数か月は)。「うまくいかない方法を1つ見つけた」ということです。つまり解決に近づいているのです。初心者が出すエラーなど大したことはありません。検索して、ひたすら試せば解決できます。
「初心者がやるエラーはだいたい経験したかな」と思えるまでやってみてください。・とりあえずPC立ち上げましょう
高すぎる目標やハードルを定めることは挫折のもとです。初めはとりあえず毎日PC立ち上げればオッケーぐらいの気持ちでいいかもしれません。私はたまにサボりそうになりますが、そのときはPC立ち上げることを目標にします。結局触りだすと楽しくなります。時にはハードルを思いっきり下げることも継続の秘訣です。おわりに
スクールに通わないと決めた以上、自分がもつリソースを最大限活用して学習効率を上げていくのみです。少しずつ技術的なこと、サンプルアプリなどもアウトプットしていきます。
アドバイスいただけると幸いです。
- 投稿日:2019-11-15T11:37:25+09:00
Rubyの多重継承優先順位まとめ
はじめに
言語の基本的な動作を確認する作業って、ついサボってしまうことが多いのですが、今回は職場の先輩に時間を頂いて
あらためてRubyで多重継承に伴うメソッドの呼び出され方を検証しました。Rubyはクラスの多重継承ができない言語ですが、Moduleを用いて事実上の多重継承を可能にしています(Mixin方式)
ケース1:複数のModuleを継承
module Foo def do_task puts "I'm in Foo class!" end end module Bar def do_task puts "I'm in Bar class!" end end class Child include Foo include Bar def execute do_task end end instance = Child.new instance.execute # which task is called?結果:後勝ち
$ ruby case1.rb I'm in Bar class!ケース2:親クラス継承+Module取り込み
module Foo def do_task puts "I'm in Foo class!" end end module Bar def do_task puts "I'm in Bar class!" end end class Parent def do_task puts "I'm in the Parent class!" end end class Child < Parent include Foo include Bar def execute1 do_task end def do_task super end end instance = Child.new instance.execute1 # which task is called? instance.do_task # which task is called?結果:取り込むModuleの後勝ち
$ ruby case2.rb I'm in Bar class! I'm in Bar class!ケース3:菱形継承(ダイヤモンド継承)
module Parent def do_task puts "I'm in the Parent class!" end end module Foo include Parent end module Bar include Parent end class Child include Foo include Bar def execute do_task end end instance = Child.new instance.execute # which task is called?結果:問題なく実行可能
$ ruby case3.rb I'm in the Parent class!まとめ
というわけで、Rubyではダイヤモンド継承にまつわる問題も回避できていること、基本的には後に書かれたものがオーバーライドするという挙動を確認しました。
複雑なコードでは随所に継承やModuleの取り込みを行いますので、このような基本的コードで挙動を検証するのもたまにはいいかもしれませんね。
多重継承については、Wikipediaでも一定量の知識を習得できるので、CSバックグラウンドがない方やエンジニアになりたての方はあらためて確認してみても良いかもしれません
- 投稿日:2019-11-15T10:26:15+09:00
enumで、キーの文字列に数字を含める方法
*前提*
schema.rbt.integer "sex" t.integer "age"通常のenum(文字列のみ)の書き方は以下の通り。
user.rbenum sex: { 未選択: 0, 男性: 1, 女性: 2 }enumで数字を含めるには以下のように
user.rbenum age: { 未選択: 0, "10代": 1, "20代": 2, "30代": 3, "40代": 4, "50代": 5, "60代": 6, "70代": 7, "80代": 8, "90代": 9 }キーをダブルクオーテーション("")で囲えば良い。
むしろ本来こんなふうに書くべきなのかもしれない。
- 投稿日:2019-11-15T04:39:30+09:00
【初学者向け】Railsのform_forでプルダウン(セレクトボックス)を実装してみる
概要
今回はrailsアプリケーションにおいてフォームを送信する際に、以下のような簡単なプルダウンを実装できるようにします。
例
上記の例は以下のようなコードで実装できます。
hoge/view/new.html.erb<%= form_for @hoge do |f| %> # 以下の一行がプルダウンを実装するためのコード <%= f.select :faculty, [["理工学部", "理工学部"], ["経済学部", "経済学部"], ["法学部", "法学部"], ["医学部", "医学部"]], include_blank: "選択して下さい" %> <% end %>一般形
コードを一般形に直すと以下のようになります。
hoge/view/new.html.erb<%= form_for @hoge do |f| %> # 以下の一行がプルダウンを実装するためのコード <%= f.select :保存先のカラム名, [["選択肢1", "実際にDBに保存させる内容"], ["選択肢2", "実際にDBに保存させる内容"]], include_blank: "選択して下さい" %> <% end %>「保存先のカラム名」には、プルダウンで選択した値を送信する際に保存したいカラムの名前を記述します。
「選択肢」と「実際にDBに保存させる内容」は基本一緒にして大丈夫です。
(「実際にDBに保存させる内容」を変えれば、"経済学部"を選択して送信した際に保存される内容を例えばtrueや2などの整数にする、といったことができます。)参考記事
- 投稿日:2019-11-15T04:39:30+09:00
Railsのform_forでプルダウン(セレクトボックス)を実装してみる
概要
今回はrailsアプリケーションにおいてフォームを送信する際に、以下のような簡単なプルダウンを実装できるようにします。
例
上記の例は以下のようなコードで実装できます。
hoge/view/new.html.erb<%= form_for @hoge do |f| %> # 以下の一行がプルダウンを実装するためのコード <%= f.select :faculty, [["理工学部", "理工学部"], ["経済学部", "経済学部"], ["法学部", "法学部"], ["医学部", "医学部"]], include_blank: "選択して下さい" %> <% end %>一般形
コードを一般形に直すと以下のようになります。
hoge/view/new.html.erb<%= form_for @hoge do |f| %> # 以下の一行がプルダウンを実装するためのコード <%= f.select :保存先のカラム名, [["選択肢1", "実際にDBに保存させる内容"], ["選択肢2", "実際にDBに保存させる内容"]], include_blank: "選択して下さい" %> <% end %>「保存先のカラム名」には、プルダウンで選択した値を送信する際に保存したいカラムの名前を記述します。
「選択肢」と「実際にDBに保存させる内容」は基本一緒にして大丈夫です。
(「実際にDBに保存させる内容」を変えれば、"経済学部"を選択して送信した際に保存される内容を例えばtrueや2などの整数にする、といったことができます。)参考記事
- 投稿日:2019-11-15T04:39:30+09:00
【Rails】 form_for でプルダウン(セレクトボックス)を実装してみる
概要
今回はrailsアプリケーションにおいてフォームを送信する際に、以下のような簡単なプルダウンを実装できるようにします。
例
上記の例は以下のようなコードで実装できます。
hoge/view/new.html.erb<%= form_for @hoge do |f| %> # 以下の一行がプルダウンを実装するためのコード <%= f.select :faculty, [["理工学部", "理工学部"], ["経済学部", "経済学部"], ["法学部", "法学部"], ["医学部", "医学部"]], include_blank: "選択して下さい" %> <% end %>一般形
コードを一般形に直すと以下のようになります。
hoge/view/new.html.erb<%= form_for @hoge do |f| %> # 以下の一行がプルダウンを実装するためのコード <%= f.select :保存先のカラム名, [["選択肢1", "実際にDBに保存させる内容"], ["選択肢2", "実際にDBに保存させる内容"]], include_blank: "選択して下さい" %> <% end %>「保存先のカラム名」には、プルダウンで選択した値を送信する際に保存したいカラムの名前を記述します。
「選択肢」と「実際にDBに保存させる内容」は基本一緒にして大丈夫です。
(「実際にDBに保存させる内容」を変えれば、"経済学部"を選択して送信した際に保存される内容を例えばtrueや2などの整数にする、といったことができます。)参考記事
- 投稿日:2019-11-15T04:28:39+09:00
Ruby + alpine でほんの少しだけバッチバージョンを変えた Dockerfile を自作してみようとした例 ( #ruby #docker )
Rubyでは公式イメージのDockerfileが公開されている
Ruby
該当のruby version のSHA をコピっておく
https://www.ruby-lang.org/en/news/2019/04/17/ruby-2-6-3-released/
Dockerfile
VERSION と SHA だけ書き換えた Dockerfile を作る
ENV RUBY_MAJOR 2.6 - ENV RUBY_VERSION 2.6.4 - ENV RUBY_DOWNLOAD_SHA256 df593cd4c017de19adf5d0154b8391bb057cef1b72ecdd4a8ee30d3235c65f09 + ENV RUBY_VERSION 2.6.3 + ENV RUBY_DOWNLOAD_SHA256 11a83f85c03d3f0fc9b8a9b6cad1b2674f26c5aaa43ba858d4b0fcc2b54171e1build
docker build . -t yumainaura/ruby2.6.4-alipine3.10
うまく行けば動くはず。
うまくいけば。
Original by Github issue
- 投稿日:2019-11-15T03:49:42+09:00
mastodonのアバターを更新する方法
インスタンス運営側なら以下のコマンドでいけるんだけど、今回は、ユーザーの場合。
mastodon:media:redownload_avatars検索ボックスで、
https://mstdn.syui.cf/@syui
みたいに検索すると、更新される。ユーザーを検索すると、avatarのurlが変更されてるかチェック、変更されてたらインスタンスのメディアサーバーにダウンロード、avatarを更新する処理が
sidekiq
で走るぽい?
- 投稿日:2019-11-15T02:23:54+09:00
【初学者向け】RailsのルーティングにおいてHTTPリクエストので気をつける点
Railsを使ったアプリケーション開発で、routesファイルのルーティング設定において注意したい点を忘れないように記事を書いてみました。初投稿および初学者のため、表現や解釈に間違いがあるかもしれませんがご容赦ください。
遭遇したエラー
ActiveRecord::RecordNotFound in UsersController
Railsのconfig/routes.rbファイルでルーティングの設定をして、指定したURLにアクセスできないエラーが発生してしまいました。
この時routesファイルとコントローラとは次のように設定していました。config/routes.rb# usersコントローラ get "users/:id" => "users#show", as: "user" get "users/close" => "users#complete"users_controller.rbclass UsersController < ApplicationController def show @user = EndUser.find(params[:id]) end def complete end end今回はEndUserというモデルを用意しています。
この状態でブラウザからurlに
http://localhost:3000/users/close
と入力すると、下のようなエラーに遭遇しました。
routesファイルに記述したHTTPリクエストが上の行から順に実行されることで、users/closeのURL上でusers/:idのリクエストが先に実行され、usersコントローラのshowアクションが呼び出されています。:id部分にURLで入力したusers/closeのcloseという文字列が代入されています。これによりshowアクションで設定したインスタンス変数に、EndUserテーブルの中からidに"close"という文字列を持つ存在しないレコードを探して渡そうとしていたことが原因でした。config/routes.rb# usersコントローラ get "users/:id" => "users#show", as: "user" #こっちから実行されるため get "users/close" => "users#complete" #これが実行されていない解決策
そこで、以下のようにルーティングの順番を設定し直しました。
config/routes.rb# usersコントローラ get "users/close" => "users#complete" get "users/:id" => "users#show", as: "user"completeアクションに対応するviewファイルは次のように設定しています。
users/complete.html.erb<h1>退会が完了しました</h1>もう一度ローカルサーバー上でusers/closeと入力すると、上のget "users/close"のリクエストが先に実行され、次のように正しくURLにアクセスすることができました。
まとめ
routesファイル中でurlを設定するときは、
users/close
users/:id
のようにURLの階層が同じかつ上位の階層名が同じ場合は階層名や記述する順番に気をつけることでエラーの予防や解消に繋がりそうです。
- 投稿日:2019-11-15T02:23:54+09:00
【初学者向け】RailsのルーティングにおいてHTTPリクエストの設定で気をつける点
Railsを使ったアプリケーション開発で、routesファイルのルーティング設定において注意したい点を忘れないように記事を書いてみました。初投稿および初学者のため、表現や解釈に間違いがあるかもしれませんがご容赦ください。
遭遇したエラー
ActiveRecord::RecordNotFound in UsersController
Railsのconfig/routes.rbファイルでルーティングの設定をして、指定したURLにアクセスできないエラーが発生してしまいました。
この時routesファイルとコントローラとは次のように設定していました。config/routes.rb# usersコントローラ get "users/:id" => "users#show", as: "user" get "users/close" => "users#complete"users_controller.rbclass UsersController < ApplicationController def show @user = EndUser.find(params[:id]) end def complete end end今回はEndUserというモデルを用意しています。
この状態でブラウザからurlに
http://localhost:3000/users/close
と入力すると、下のようなエラーに遭遇しました。
routesファイルに記述したHTTPリクエストが上の行から当てはめられることで、users/closeのURL上でusers/:idのリクエストが先に当てはめられてusersコントローラのshowアクションが呼び出されています。:id部分にURLで入力したusers/closeのcloseという文字列が代入されています。これによりshowアクションで設定したインスタンス変数に、EndUserテーブルの中からidに"close"という文字列を持つ存在しないレコードを探して渡そうとしていたことが原因でした。config/routes.rb# usersコントローラ get "users/:id" => "users#show", as: "user" #こっちから実行されるため get "users/close" => "users#complete" #これが実行されていない解決策
そこで、以下のようにルーティングの順番を設定し直しました。
config/routes.rb# usersコントローラ get "users/close" => "users#complete" get "users/:id" => "users#show", as: "user"completeアクションに対応するviewファイルは次のように設定しています。
users/complete.html.erb<h1>退会が完了しました</h1>もう一度ローカルサーバー上でusers/closeと入力すると、上のget "users/close"のリクエストが先に実行され、次のように正しくURLにアクセスすることができました。
まとめ
routesファイル中でurlを設定するときは、
users/close
users/:id
のようにURLの階層が同じかつ上位の階層名が同じ場合は階層名や記述する順番に気をつけることでエラーの予防や解消に繋がりそうです。
- 投稿日:2019-11-15T02:13:08+09:00
フォロー機能 User.rbとRelationship.rb
Relationship.rbclass Relationship < ApplicationRecord belongs_to :follower, class_name: "User" belongs_to :followed, class_name: "User" endRelationshipモデル(中間モデル)には、
follower (フォローする人)
followed(フォローされる人)
が存在している。user.rbhas_many :follower, class_name: "Relationship", foreign_key: "follower_id", dependent: :destroy # フォロー取得。Relationshipモデルのfollower_idにuser_idを格納 has_many :followed, class_name: "Relationship", foreign_key: "followed_id", dependent: :destroy # フォロワー取得。followed_idにuser_idを格納上2行は、user主体で得る情報。
特定のアクションが起きた際、user_idが格納される場所をforeign_keyで指定することで、follower_id および followed_id の適切な方にuser_idを格納。また、フォロー数等のときは、ユーザ情報の中身を必要としないので
<% current_user.follower.each do |user| %>
<%= user.count %>
<% end %>のように、followerをとってよい(フォロー数取得)。
(フォロワー数を取得する場合は、followed)
user.rbhas_many :following_user, through: :follower, source: :followed #following_userを命名。自分がフォローしているユーザ情報を取得。 has_many :follower_user, through: :followed, source: :follower #自分をフォローしているユーザ情報を取得。ユーザ情報を詳しく必要とする場合は、こちら(emailなど)。
<% current_user.following_user.each do |user| %>
<%= user.email %>
<% end %>following_userで、followerテーブルを通じて、followedテーブルを参照。
- 投稿日:2019-11-15T01:42:01+09:00
ユーザ一覧で"フォローする"ボタンを押したときの挙動 備忘録
user/index/html.erb<% if user != current_user %> <% if current_user.following?(user) %> <%= link_to 'フォロー解除', unfollows_path(user.id), method: :POST %> <% else %> <%= link_to 'フォローする', follows_path(user.id), method: :POST %> <% end %> <% end %>上の 'フォローする' ボタンを押すと、follows_path が読み込まれる
follows_pathは
routes.rbpost 'follows/:id' => 'relationships#follows', as: "follows" # フォローする。follows_pathの作成。上の as "follows"で名前付きパスをつけたもの。
follows_path が呼び出されると、
relationships#follows(relationshipsコントローラのfollowsアクション)
が呼び出されるrelationships.controller.rb# ユーザーをフォローする def follows(user_id) follower.create(followed_id: user_id) endフォロー完了。
- 投稿日:2019-11-15T01:07:07+09:00
【Ruby】HTMLタグを表す正規表現と`gsub`メソッド内での活用例
はじめに
先日、【Ruby】HTTPClientとLINE Notifyを使ってターミナルから文字列を送るプログラムという記事を書きました。
その中で使用している
HTTPClient
の学習中に<div>
や</div>
など、HTMLタグを表す正規表現を見て「便利!」と感じたためまとめました!どなたかの参考になれば幸いです。
この記事が役に立つ方
- HTMLタグが邪魔で除去したい
- 正規表現初心者
この記事のメリット
- HTMLタグを表す正規表現を理解し、活用が出来る
- ついでにRubyの
gsub
メソッドの使い方が分かる環境
- シェル:zsh
- Ruby 2.6.3
参考にしたサイト
HTTPClient
チュートリアルサイトにて。
get_content
メソッドの使用例の中で見かけました。以下リンクの「The get_content function」の中に書いてあったものです。
Ruby HTTPClient tutorial(英語)HTMLタグを表す正規表現
こちらがHTMLタグを表す正規表現です。
%r{</?[^>]+?>}このコードを順に読み解いていきます。
%r{正規表現を記載}
{}
内は/
などをエスケープして記述する必要がない。HTMLタグは閉じタグに
/
が必ず使われているので、通常の/正規表現/
というでいちいちエスケープする必要がなくなる%r{}
のほうが便利です。
<
そのまま
<
を表す。
/?
/
が1つあるか、ないか。
[^>]
^
を付けた文字は除外される。
つまり、>
以外の1文字を表します。
[^>]+?
+?
は直前に書いた文字が「1個以上」という意味です。
>
以外の文字が1個以上ということですね。
(もう一度)
%r{</?[^>]+?>}
とは「
<
から始まって/
があってもなくてもよくて、最後の>
までの間に>
以外の文字がいくつかある」
ということになりますね。つまり、
<任意の文字列>
か</任意の文字列>
ということ!
まさにHTMLタグのことです。【活用例】
gsub
メソッドでHTMLタグを除去する(何らかのHTML).gsub(%r{</?[^>]+?>},’’)Rubyで
gsub
メソッドは、文字列の置換をするメソッドです。上記コードでは
<任意の文字列>
か</任意の文字列>
を''
に置換(削除)
しています。例えば、HTMLドキュメントからHTMLタグを除去して可読性を向上する際に使えます。(以下参照)
【Before】
<html><head><title>Something.</title></head> <body>Something.</body> </html>【After】
Something. # <title>タグ内のもの Something. # <body>タグ内のものおわりに
正規表現はパッと見てもわからない、でも読み解くと必ず分かるのがいいですね。
よく使いそうな正規表現をリスト化しておき、必要に応じて参照出来るようにしておくと作業効率上がりそう。
参考にさせて頂いたサイト(いつもありがとうございます)