20191115のRubyに関する記事は28件です。

とりあえずこれだけ。Rails ToDoアプリ作成【CRUD】勉強会 #1

初心者用Railsアプリ練習帳

勉強会を開き、Rails初学者に日頃教えています。初心者向け勉強回資料用もかねて作成。
ある程度基礎を学習していると、読みながら真似はできるけど、いまいち全体の知識がつながっていないので自分で作ることができない…

そんな状態になりがちでした。

対象者

  • Railsでなんでもいいからの教材をやった人
  • Rails基礎はなんとなくわかってきた人
  • Railsで自分でアプリ作るのはまだ無理そうという人
  • Rails以外のフレームワークならわかる人

※ 正確さより概念とアプリ作成の流れを重視します。

コンセプト

他にも言っている方がいますが、勉強会で教えている経験から初学者が同じ教材を何周もするのは上達が遅いように感じています。

知識はあるけど自分で一から作るのは無理そう。
エラーが出て対処出来ない

ということになりがちでした。

こういう場合、実際に手を動かし、自分の頭で考えて簡単なアプリを作る必要があるけど、

  • いきなりだと何を作ればいいのかわからない…
  • 一連のアプリを作っていく順番がわからない…
  • エラーでても対処できない…
  • 簡単なのから作ってみればいいと言うが何が簡単なものなのか分からない…
  • 何個もアプリ自分で作るといいらしいが何を作れば練習やスキル向上なるか分からない…

そんな人が未経験からプログラミングをやる人には何人もいました。
現状の私の考えですが、世の中にある教材は見た目とか完成度ないと売れないし、強い人達からツッコまれるので分かりやすさより精確さが必要とされる教材になってしまうようにおもいます。

なので、後で身につければ良いこともあれもこれも付け足しており、本当にコレだけ!
という事項が初心者には見えにくくなっている気がします。

それをうけて今回、ムダなものとにかく剥ぎまくって、コアな部分だけを数多く練習すればみんな出来るようになるのではないかと考え、練習問題を作りました。
今後も感想とかもらって継続的にブラッシュアップしていければと思っています。

手順

  1. とりあえずToDoアプリをなぞって作ってみる(少しだけでも勉強した人が前提) ←今回はココ
  2. 超シンプルアプリを量産。手を動かして覚える!概念つかむ!
  3. データベースから欲しいデータを取得する!千本ノックのようにやりまくります!

2,3は個人的に開く勉強会で解説します。

とりあえずToDoアプリをなぞって作ってみる

環境構築はローカルでもいいですけど、WindowsやMacで違うので初心者はcloud9で作っていいと思います。
構築方法は今回省略

以下のコマンドでアプリを作成。プロジェクト名になります。なんでもいいです。
今回、私はrails-todo1にしました

$ rails new rails-todo1

沢山のファイルが作られる。終わったらプロジェクトのディレクトリに移動

$ cd rails-todo1

サーバーを起動させて表示させてみる

$ rails s

welcome_rails.png

こんな画面が出たらOK

これから中身を作っていくので一応流れを確認

今回新規作成で一から作っていきますが、だいたいこんな感じで作ると考えていただければよいかと思います。

新しいアプリ(ページ)を作る手順

全体の流れで考えること

  1. 作るページはどんなページで作る?
  2. データベースに保存するデータは何か?
  3. モデルは作ったか?
  4. マイグレーションファイルを生成&データベースに反映
    • generate modelコマンドするとmigrationファイルも作られる。
    • カラム(データベースの列)を追加したり、データベースの中身を変えたいときにもマイグレーションファイル使う
    • rails db:migrateでマイグレーションファイルの内容をデータベースに反映
  5. ページを表示するのか?データを登録・更新をするのか?

    • 表示するならGET
    • データの登録ならPOST
    • データの更新ならPATCH (PUT)
    • データの削除ならDELETE
  6. ルーティングを決める

    • URLにパラメータをいれる必要があるのか?(例えば各ユーザーの情報ページを表示するならparams[:id]のように書く。ならばURLに〇〇〇/:idのようにidを含める必要がある)
    • URLにパラメータいらない(新規登録や全データ表示など)
  7. 処理を行うコントローラーは作ったか

  8. コントローラーのアクションは作ったか

    • GET?POST?PATCH?DELETE?かを確認してつくる
  9. ビューファイルはいるか?

さっそく簡単なアプリ作成していく

1.どんなもの作るか

↓ なんの飾りもない超シンプルアプリ
image.png

テキストを入力して登録する
todo2.gif

2.データベースに保存するデータは何か

  • テーブル名:Todo
  • カラム名:title
  • デフォルトで作られるカラム(ID, 作成日, 更新日)

3. モデルは作ったか

今回は、まだ何もつくってないので当然必要。
今回作るToDoアプリでは、Todoテーブルのデータ取得・新規登録・データ更新・データ削除を行うのでModelを1つ作成します

というわけで、モデル作成。

$ rails generate model Todo title:string

2.で書いたように必要なテーブルがTodo
必要なカラムがtitleなので、このように書いています。

rails generate model Todoでモデルファイルが作られる
title:string部分は追加したいカラム名と型。
この部分は書かなくても手動で記述すれば作れるのですが、コマンドでこのように打ったほうが楽です

db/migrate/~~~~~~~~~_create_todos.rb
class 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

今回は最初のページが
image.png

Todoを追加するための機能を持つページを「表示」します。
ということでGETメソッドです

6. ルーティングを決める

ルーティングは
どのURLにアクセスすると、どのコントローラーとアクションを使うのか定義するものです。

Railsにはresourcesという便利機能があってルーティング書くのを便利にできる機能があるんですが、今回は原理を理解するために今回は一つ一つルーティングを記述していきましょう。

config/routes.rb
get '/todos', to: 'todos#index', as: 'todos'
post '/todos', to: 'todos#create'

ルーティングを確認する

コンソールに↓を入力するとルーティングを確認できる。

$ rails routes
Prefix  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みたいな感じ
他タブで開いておくといちいちコマンド入れなくても確認できるので便利!

image.png

現状では/todosにGETを設定してtodoコントローラーindexアクションの処理を使えと指定している

補足:_pathについて

$ rails routesだとPrefixtodosという名前になっていてURLに名前を付けて便利に使えるようになっている。これに_pathを追加すると使えます。
todos + _pathtodos_pathです。

todos_pathと書くと/todosと書いたのと同じことになります。
結構使いますので覚えておくといいです。

7. 処理を行うコントローラーは作ったか

コントローラーは大雑把にルーティングモデルビューをつなぐ役割、データ返す役割をしているものです。

mvc_pattern.png

コントローラーは何をするか雑に説明

細かく言うとわかりにくくなるので大雑把に。

  • ルーティングで該当のURLにマッチしたら、コントローラーのアクションに処理が飛んでくる。そして処理を行う。
    • その際にparamsのデータもらうこともあり
  • Todo.new使ってビューファイルで記述するform_with(form_forも含む)で使うフォームでデータを送信するために必要なモデルの部品みたいなものが入った箱作ったりする。
    • ビューで使うならインスタンス変数に格納する
  • Todo.find(params[:id])みたいに自分で作ったModel使ってデータベースの値を取得する。
    • Todo.find(params[:id])Todo.newで作った部品に、取得したデータも含んだものだと思っておけばいいです。※下図参照
    • ビューで使うならインスタンス変数に格納する
  • インスタンス変数はビューで使用可能なのでページを操作によってデータ表示やフォームの送り先を変えられる動的なページを作れる

image.png

初心者のうちはデータ取得したり、データを加工する、ビューで表示するためのデータを@todoみたいなインスタンス変数を作る!
みたいな感じでいいと思います。

コントローラー作成コマンド

以下コマンドで、Controllerを作成します。

とりあえずコントローラーを生成する

$ rails g controller Todos

このコマンドならtodosというコントローラー作成されるので、todos_controller.rbというファイルができます。

8. コントローラーのアクションは作ったか

app/controllers/todos_controller.rb
class 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>

作成した画面

まだデータの登録がないので全件取得しても表示するものがないので、このような表示になっています。
image.png

こんな1~9までのような順序でアプリの作成(もしくは追加)をしていくことになります。

では、次にTodoの全件表示しかできない状態なので
次は新規ToDo追加できる機能を追加します

新規追加機能の作り方

次に新規登録できる機能を作ります。
今度は1~9まの順序で機能を追加していきましょう。

1.どんなもの作るか

ToDoテーブルにデータを新規追加する機能
テキストフィールドに入力した文字をボタン押すとデータベースに登録

image.png

2.データベースに保存するデータは何か

テーブル名:Todo
カラム名:title
デフォルトで作られるカラム(ID, 作成日, 更新日)

titleだけデータベースに保存すればいいことになります。

3. モデルは作ったか

モデルはTodoつかうので新しく作成の必要なし

4. マイグレーションファイルを生成&データベースに反映

モデルはTodo使う。カラムの追加も必要ないのでマイグレーションファイル作る必要も、データベースに反映する必要もない。

5. ページを表示するのか?データを登録・更新をするのか?

カラム名でtitleをデータベースに登録する処理を追加することになります。

ページの表示をしているわけでも、情報の更新をしているわけでもなく、情報の削除でもありません。

こういうデータの登録処理はPOSTで行いましょう。

6. ルーティングを決める

  • メソッド:POST
  • ルーティング:/tasks
  • コントローラー&アクションtasks#create

ルーティングを確認

実はすでに追加してしまっていたのですが、今回使うルーティングは↓の二つ目です。

routes.rb
Rails.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.rb
class 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
としているのは、ページを表示しているわけでないのでリダイレクトしないと処理が終わっても処理が固まった感じになってしまうからです。

処理としては正常に終了してるけど、↓のように何も起こってない…みたいな感じになる。
正常に登録はできているのでリロードするとちゃんと表示されることになる。

todo1.gif

get '/todos', to: 'todos#index', as: 'todos'
as: 'todos'と書くと/todosというURLにtodosという名前を付けて使えるようにしています。

この名前に、ルーティングの名前(todosなど) + _pathtodos_path
のようにするといちいちURLを書く必要がなくなります。

注意点として、Formで入力した情報を新規登録や更新する際はセキュリティ対策としてStrong Parametersを使わないとエラーでますので追加します。

Strong Parameters

セキュリティ対策で、入れないとエラーになります。
難しいこと省いて、フォームから受け取ったデータを渡すと、許可したデータだけ返却されるくらい覚えておけばとりあえずは良いと思います。

そして追加したのがtodo_paramsメソッド。また、privateを使うと、todo_paramsメソッドが外部から使えないようにできるのでセキュリティを高まります。
難しいことはいいので、privateの下にStrong Parametersいれるんだなくらいで今はいいです。

app/controllers/todos_controller.rb
class 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
end

9. ビューファイルはいるか?

データベースでのデータの追加処理では使いません。
ただし、送信するためのテキスト入力フォーム送信ボタン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_controllerindexにフォームを作るための部品を用意するため@todo = Todo.newを追加

app/controllers/todos_controller.rb
class TodosController < ApplicationController
  def index
    @todos = Todo.all
    @todo = Todo.new # フォーム作るために必要
  end
end

image.png

登録機能完成

データベースに登録する機能が完成しました!

todo2.gif

今度は更新と削除

次は更新機能を実装します。

必要機能

  • 情報更新ページ(今回はeditアクションとedit.html.erbにて)
    • ルーティングを追加する
    • editアクションで編集する情報を表示する todos_controller.rb
    • ビューで見た目を作る edit.html.erb
  • 情報更新処理
    • updateアクションで情報の更新処理 todos_controller.rb

更新情報編集ページ用ルーティング追加

routes.rb
Rails.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.rbeditアクションを使用します。ルーティングの名前はedit_todoとでもしておきます。

更新情報編集ページ用コントローラー

editアクションは、情報編集画面(/todos/:id/edit)にアクセスすると処理されるアクションで、ビューで見た目を作るときに必要なデータの取得します。

editアクションを情報を追加します。

todos_controller.rb
  def 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])id1だったら取得するデータを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.rb
Rails.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' # 更新処理用ルーティング追加
end
app/controllers/todos_controller.rb
  def update
    @todo = Todo.find(params[:id])
    @todo.update(todo_params)
    redirect_to todos_path
  end

resouses使えるならこんな感じでシンプルに書ける

<-- 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 %>

image.png

<td><%= link_to '編集', edit_todo_path(todo.id) %></td>

ついでに、ちゃんと省略して書かないとこういう書き方になる。todotodo.id
Railsがいい感じで判断してくれるのでこのようになりますが、原理は覚えておくほうがいいと思います。
htmlだと↓のようなものが作られる
<a href="/todos/1/edit">編集</a>

削除機能実装

削除機能はわかってしまえば他と大して変わらないので楽です。
1. ルーティング追加
2. コントローラーにdestroyアクション追加
3. ビューに削除ボタンのリンク追加

routes.rb
Rails.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.rb
  def 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 %>

削除機能が完成しました。

todo3.gif

見た目は最悪ですが、ToDo登録・更新・削除機能が完成しました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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を選択してこれもインストールする

rubydevenv.png

その後、VSCode左下の歯車をクリック

検索欄に、「terminal.integrated.shell.windows」と入力して
表示される「Edit in setting.json」をクリックする

rubydevenv2.png

    "terminal.integrated.shell.windows": "C:\\Program Files\\Git\\bin\\bash.exe"

と入力する

すると、VSCode下部のターミナルがGit Bashに変更される

ターミナルから、rails コマンドが実行できるので統合開発環境として活用可能に
rubydevenv4.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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合宿公式サイトでお願いします.

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

クソコードあるあるを詰め込んだ「ひどいFizzBuzz」を書きました。

タイトルの通りです。
コードレビューの練習などにお使いください。

hidoi_fizzbuzz.rb
print "数字を入力してください:"
# 入力された数字を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年程度のクソエンジニアです。こんなクソコードを実務で書いてしまわないように日々勉強しております・・・)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails Tutorialの知識から【ポートフォリオ】を作って勉強する話 #14 ユーザ投稿表示, ページネーション編

こんな人におすすめ

  • プログラミング初心者でポートフォリオの作り方が分からない
  • Rails Tutorialをやってみたが理解することが難しい

前回:#13 パスワード再設定編
次回:準備中

今回の流れ

  1. 投稿用のモデルをつくる
  2. サンプル投稿を表示する
  3. テストをつくる

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.rb
class 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
end

user_idは投稿したユーザ、create_atは投稿時間を管理しています。
これらを複合キーとすることで、望み通り取り出すことが可能です。

Userモデルを編集する

MicropostモデルはUserモデルに所属しました。
この実装はbelongs_to :userにより、user_idとして形になりました。
今度はUserモデルにMicropostモデルを所有してもらいましょう。

app/models/user.rb
class User < ApplicationRecord
  has_many :microposts
  # 中略
end

has_manyによりUserはMicropostと1対多の関係になりました。
こうすることでmicropostsを指定する時、こんな書き方が可能です。

user = User.new
user.microposts

慣習的にも正しいので、以上の作業は忘れずに行いましょう。

バリデーションを追加する

ここでは以下のバリデーションを追加します。

  • user_idが空の場合、Micropostを生成しない
  • いずれの値も空の場合のみ(user_idを除く)Micropostを生成しない
app/models/micropost.rb
class 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.rb
class Micropost < ApplicationRecord
  belongs_to :user
  default_scope { order(created_at: :desc) }
  # 中略
end
app/models/user.rb
class User < ApplicationRecord
  has_many :microposts, dependent: :destroy
  # 中略
end

エラー時の日本語化を行う

あとはエラー時の言語を日本語にしましょう。
日本語化にはgemと設定が必要です。#6Railsのバリデーションエラーのメッセージの日本語化を参考に設定を行なってください。

お済みの方は以下のようにファイルを編集してください。

config/locales/models/ja.yml
ja:
  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.rb
User.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.rb
class 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 bootstrap4
config/locales/models/ja.yml
ja:
  # 中略
  views:
    pagination:
      first: "&laquo; 最初"
      last: "最後 &raquo;"
      previous: "&lsaquo; 前"
      next: " &rsaquo;"
      truncate: "..."

これらを終えるとビューはこんな感じになります。
PC版↓
lantern_logs_pc.png
スマホ版↓
lantern_logs_iphoneX.png

参考になります↓
【Ruby on Rails】gem(Kaminari)でページネーション機能を追加してBootstrapを適用する。
【Rails初心者】ページネーションを実装して自分好みにデザインを変える

テストをつくる

最後にテストを完成させます。
ここでの手順は以下の通りです。

  • FactoryBotを整える
  • テストを書く

FactoryBotを整える

まずはテストを行う前の準備をしましょう。
今回新たにMicropostモデルが生成されました。
それに伴うテストを行いたいので、FactoryBotで導入しましょう。

ここでの手順は以下の通りです。

  • テスト用のMicropostモデルを生成する
  • テスト用のUserモデルを編集する
spec/factories/microposts.rb
FactoryBot.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
end
spec/factories/users.rb
FactoryBot.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
end

microposts.rbではtraitを使って、Micropostモデル内を区切りました。
users.rbではsequenceを使って、メールアドレスの一意性を保つよう番号をつけました。
以上で準備は完了です。

※ 変更により他のテストが失敗する可能性があるので、適宜変更を加えてください。

Micropostモデルのテスト

それではいよいよテストに入りましょう。
このテストでは以下を確認します。

  • モデルが正しく生成されているか
  • いずれの値も空の場合(user_idを除く)、Micropostは存在しないか
  • カラムが最新のものから順に並んでいるか
  • user_idが存在しないMicropostは存在しないか
  • memoが255文字を超えないか
spec/models/micropost_spec.rb
require '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
end

Userモデルのテスト

このテストでは以下を確認します。

  • ユーザが削除されたら投稿も削除されるか
spec/models/user_spec.rb
require '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 パスワード再設定編
次回:準備中

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

メモ: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.rb
require 'rails/test_help'
require "minitest/reporters" #1行目
Minitest::Reporters.use!     #2行目

gem 'minitest'にどういう役割があるのか、調べてもイマイチよくわからなかったが...
どうやらRailsのテストの方法はいくつかあるようで、minitestというのはそのうちの一つらしい。
今のところはminitest-reportersの前提modならぬ前提gem的な存在だと思っておくことにする(適当)。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【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は、インスタンス変数と呼ばれるものです。
詳しくは次回の復習時に記載しようと思うのですが、概要としては以下です。

・それぞれのインスタンスが固有に持つデータ(名前なり年齢なり)のために存在する(別の言い方をすれば、「インスタンス間で共有しないデータ」のため、ということです)
・インスタンスに属するメソッドであれば、どのメソッドからでも参照できる

おつかれさまでした!

ここまでお付き合いいただき、ありがとうございました!
次回はクラス変数、インスタンス変数、クラスメソッド、インスタンスメソッドあたりの復習をしようと思います!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

eager_load + find_by でクエリが2回発行される件の対策

結論

よくある1:NのRelationがある時

class User < ApplicationRecord
  has_many :comments
end

class Comment < ApplicationRecord
  belongs_to :user
end
User.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回のクエリにしたい :thinking:

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">

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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 で囲えば良い。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

たのしい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.rb
htmlfile = "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.rb
str = "こんにちは" 
p str.encoding #=> #<Encoding:UTF-8>

また、Rubyのデフォルトの文字コードはUTF-8ですが、Encoding.default_internalを使うことでencodeメソッドによる文字コードを指定することができます。

手元の環境で実行したのですが、UTF-8のままになってしまいました…。
原因の分かる方、教えていただけると幸いです。

sample2.rb
Encoding.default_internal = "Shift_JIS"
s = "太郎"
puts s.encode

くわしくはこちらのRubyリファレンスをご覧ください。
あとこちらのブログもかなり参考になりそうです(2012年のものなので情報の正確性に注意!)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

CookieにSameSite属性がセットされていた時の標準エラーをとりあえず回避した話

GoogleDriveが数日前よりCookieにSameSite属性(None)をセットするようになった。
Rubyの標準ライブラリであるWebAgent::CookieManagerはSameSite属性に対応していない。
また、未知の属性がCookieにセットされていた場合は、標準エラーを吐く。
従って、Cronでスクリプトを起動していると、標準エラー発生を通知するメールが、毎時届くようになってしまった。

と、多分、このようなことが起きていたのだと思う。

メールがウザすぎるので、parseメソッドだけオーバーライドしてみた。
標準エラーでなく例外を吐いてくれたら、こんなことせずに済むのだけれど、多分、深い理由があるのでしょうね。

core_ext.rb
class 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
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[メモ]2019/11/15 Ruby on Rails

Railsでページを表示するのには以下の3つのファイルが必要

ページの作成に必要なもの
・ビュー(View)
・コントローラ(Controller)
・ルーティング(routing)

  1. ビュー(View)
     ビュー(View)とはページの見た目を作るHTMLファイル。
     ブラウザとのやり取りの中で,Railsからビューが返されページが表示される。

  2. コントローラ(Controller)
    ページを表示するときに、Railsではコントローラを経由してビューをブラウザに返す。
    「rails g controller home top」を実行したとき、「home_controller.rb」というコントローラファイルが生成され、ファイルの中に「topメソッド」が追加される。
    コントローラーのメソッドを「アクション」と呼ぶ。

メソッドとは、「オブジェクト(もの)の操作を定義したもの」
例)オブジェクト:マリオとすると
メソッド 操作
・ジャンプする。
・走る
・しゃがむetc...
3. ルーティング(routing)
ブラウザとコントローラを繋ぐ。

ページが表示されるまでに、
ルーティング→コントローラ→ビューという順番で処理が行われる。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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: true

link_to

link_to 表示するテキスト, prefixもしくは、URIパターン, HTTPメソッド
HTTPメソッドを省略した場合は、GETで実行されるので注意する。

ridirect_to

コントローラのビューに関わらず、特定のページにとばす。
ridirect_to アクション名 (実行条件?)

コードの記述(試験対策)

スコープの意識は・・・まだあんまりしなくていいのかな。
コードの書き順はよく確認すること。routes.rbのコードの書き順に注意。
いつもは頭に書いてるのが末尾にあったり、
先に定義していないといけないコードを、後ろに書いてあったりする。


railsのエラー修正問題ヤベェな。
どないしょ。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rubyのcompact! に注意。

経緯

compact!メソッドによって仕事でバグを生み出してしまったので、備忘録としてアウトプット

compact、compact!の戻り値

compactcompact!は配列から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が返ってくるので、注意!!!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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

おわりに

良き開発を、例外とともに

参考文献リスト

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

独学でプログラミング学習を始めて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立ち上げることを目標にします。結局触りだすと楽しくなります。時にはハードルを思いっきり下げることも継続の秘訣です。

おわりに

スクールに通わないと決めた以上、自分がもつリソースを最大限活用して学習効率を上げていくのみです。少しずつ技術的なこと、サンプルアプリなどもアウトプットしていきます。
アドバイスいただけると幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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バックグラウンドがない方やエンジニアになりたての方はあらためて確認してみても良いかもしれません

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

enumで、キーの文字列に数字を含める方法

*前提*

schema.rb
t.integer "sex"
t.integer "age"

通常のenum(文字列のみ)の書き方は以下の通り。

user.rb
enum sex: { 未選択: 0, 男性: 1, 女性: 2 }

enumで数字を含めるには以下のように

user.rb
enum age: { 未選択: 0, "10代": 1, "20代": 2, "30代": 3, "40代": 4, "50代": 5, "60代": 6, "70代": 7, "80代": 8, "90代": 9 }

キーをダブルクオーテーション("")で囲えば良い。
むしろ本来こんなふうに書くべきなのかもしれない。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【初学者向け】Railsのform_forでプルダウン(セレクトボックス)を実装してみる

概要

今回はrailsアプリケーションにおいてフォームを送信する際に、以下のような簡単なプルダウンを実装できるようにします。

スクリーンショット 2019-11-15 3.34.17.png

スクリーンショット 2019-11-15 3.35.25.png

上記の例は以下のようなコードで実装できます。

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などの整数にする、といったことができます。)

参考記事

【開発メモ】Ruby on Railsのform_forでドロップダウンリストの選択ボックスを設置する方法

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsのform_forでプルダウン(セレクトボックス)を実装してみる

概要

今回はrailsアプリケーションにおいてフォームを送信する際に、以下のような簡単なプルダウンを実装できるようにします。

スクリーンショット 2019-11-15 3.34.17.png

スクリーンショット 2019-11-15 3.35.25.png

上記の例は以下のようなコードで実装できます。

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などの整数にする、といったことができます。)

参考記事

【開発メモ】Ruby on Railsのform_forでドロップダウンリストの選択ボックスを設置する方法

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】 form_for でプルダウン(セレクトボックス)を実装してみる

概要

今回はrailsアプリケーションにおいてフォームを送信する際に、以下のような簡単なプルダウンを実装できるようにします。

スクリーンショット 2019-11-15 3.34.17.png

スクリーンショット 2019-11-15 3.35.25.png

上記の例は以下のようなコードで実装できます。

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などの整数にする、といったことができます。)

参考記事

【開発メモ】Ruby on Railsのform_forでドロップダウンリストの選択ボックスを設置する方法

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ruby + alpine でほんの少しだけバッチバージョンを変えた Dockerfile を自作してみようとした例 ( #ruby #docker )

Rubyでは公式イメージのDockerfileが公開されている

https://github.com/docker-library/ruby/blob/bf0e16e7511c97fdf351fdfc2e7e17478a4eaf16/2.6/alpine3.10/Dockerfile

image

Ruby

該当のruby version のSHA をコピっておく

https://www.ruby-lang.org/en/news/2019/04/17/ruby-2-6-3-released/

image

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 11a83f85c03d3f0fc9b8a9b6cad1b2674f26c5aaa43ba858d4b0fcc2b54171e1

build

docker build . -t yumainaura/ruby2.6.4-alipine3.10

うまく行けば動くはず。

うまくいけば。

Original by Github issue

https://github.com/YumaInaura/YumaInaura/issues/2716

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

mastodonのアバターを更新する方法

インスタンス運営側なら以下のコマンドでいけるんだけど、今回は、ユーザーの場合。

mastodon:media:redownload_avatars

検索ボックスで、https://mstdn.syui.cf/@syuiみたいに検索すると、更新される。

ユーザーを検索すると、avatarのurlが変更されてるかチェック、変更されてたらインスタンスのメディアサーバーにダウンロード、avatarを更新する処理がsidekiqで走るぽい?

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【初学者向け】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.rb
class UsersController < ApplicationController

  def show
    @user = EndUser.find(params[:id])
  end

  def complete
  end
end

今回はEndUserというモデルを用意しています。
この状態でブラウザからurlに
http://localhost:3000/users/close
と入力すると、下のようなエラーに遭遇しました。
スクリーンショット 2019-11-15 0.27.10.png
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にアクセスすることができました。
スクリーンショット 2019-11-15 2.21.30.png

まとめ

routesファイル中でurlを設定するときは、
users/close
users/:id
のようにURLの階層が同じかつ上位の階層名が同じ場合は階層名や記述する順番に気をつけることでエラーの予防や解消に繋がりそうです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【初学者向け】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.rb
class UsersController < ApplicationController

  def show
    @user = EndUser.find(params[:id])
  end

  def complete
  end
end

今回はEndUserというモデルを用意しています。
この状態でブラウザからurlに
http://localhost:3000/users/close
と入力すると、下のようなエラーに遭遇しました。
スクリーンショット 2019-11-15 0.27.10.png
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にアクセスすることができました。
スクリーンショット 2019-11-15 2.21.30.png

まとめ

routesファイル中でurlを設定するときは、
users/close
users/:id
のようにURLの階層が同じかつ上位の階層名が同じ場合は階層名や記述する順番に気をつけることでエラーの予防や解消に繋がりそうです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

フォロー機能 User.rbとRelationship.rb

Relationship.rb
class Relationship < ApplicationRecord
    belongs_to :follower, class_name: "User"
    belongs_to :followed, class_name: "User"
end

Relationshipモデル(中間モデル)には、
:boy:follower (フォローする人)
:two_women_holding_hands:followed(フォローされる人)
が存在している。

user.rb
  has_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.rb
  has_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テーブルを参照。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ユーザ一覧で"フォローする"ボタンを押したときの挙動 備忘録

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.rb
post '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

フォロー完了。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【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タグのことです。:thumbsup:

【活用例】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>タグ内のもの

おわりに

正規表現はパッと見てもわからない、でも読み解くと必ず分かるのがいいですね。:relaxed:

よく使いそうな正規表現をリスト化しておき、必要に応じて参照出来るようにしておくと作業効率上がりそう。:thinking:

参考にさせて頂いたサイト(いつもありがとうございます)

Ruby HTTPClient tutorial

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む