Okinawa.rb Meetup 4/24

ISUCONのはじめかた話を聞いてきた。題材はprivate-isu。雑にメモ

github.com

ISUCON流れ

全体流れ

  • sshの設定で公開鍵はgithubにあげとく作戦
  • config設定(セッションが切れないようにするなど)
  • アプリケーションのコードをgithubにアップする
  • githubにあげたコードをローカルに持ってくる
  • ローカルでサーバーと同じような状態にする
  • ローカルでコードを修正できるようにする
  • ベンチマーク実行する
  • ブラウザから操作してみる
  • ボトルネックを探す
  • ローカルでコード改修する
  • 環境にデプロイ
  • ベンチマーク実行する

ローカルでアプリを動かせるようにする状態にするのがなんとなく難しそうだった。

MySQLメモ

  • ダンプ
$ mysqldump -uroot isuconp > db.sql
  • リストア
mysql> create database isuconp;
mysql> quit
$ mysql -uroot -proot -D isuconp < db.sql

ローカルの3306にアクセスしたらサーバーのMySQLにアクセスするようにしたかったけど、なんか3306を指定したらエラーになってうまくいかなかったので、いったん13306を指定してる

shimabukuromegumi-no-MacBook-Pro:~ root# ssh -f -N -C -L 13306:localhost:3306 isucon@13.231.226.73 -p 22
shimabukuromegumi-no-MacBook-Pro:~ root# mysql -uroot -P 13306 -h 127.0.0.1
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 648
Server version: 5.5.49-0+deb8u1 (Debian)

Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql>

st-hakky.hatenablog.com

$ mysql.server start
$ mysql.server stop
$ mysql.server status

qiita.com

memcached メモ

isuconアプリをローカルで立ち上げる時にmemcachedがないっていうエラーになったので、導入した。(なぜ必要なのかはいまいちわかっていない)

  • インストール
brew install memcached
  • 起動
memcached

joppot.info

sinatraメモ

ローカルにisuconアプリを持ってきたあと、bundle install、bundle exec ruby app.rbで起動すると思ったけど、うまく起動しなかった。bundle exec rackup -p 8080とするとちゃんと起動した。(なんで起動しなかったのかよくわかっていないってのと、rackupってコマンドがなんなのかよくわかっていない)

cd isucon-pixiv/ruby
bundle install --path=vendor/bundle
bundle exec rackup -p 8080

qiita.com

ngx-mrubyメモ

isucon7の予選のsleep問題にrubyでも対策できるようにngx-mrubyを使えるのでは???という話。isucon7のsleep問題もいまいちよくわかっていなくて、なるほどわからん状態だけど、ngx-mruby気になる。

blog.takanabe.tokyo

tech.pepabo.com

github.com

isucon7メモ

ISUCON 7 予選で ISUCON 初参加した結果を振り返る · GitHub

一見無駄に見える参照実装の sleep の意味に気づくとブレイクスルーすることができたと思います。

github.com

isucon.net

Goつかってたということもあって、sleepはとくに障害になりませんでした。

これがPerlRubyといったprefork型のやつだと、/fetchの並列度によってはworkerが/fetchに専有されるみたいなのもありえたのですが、GoとNode.jsはその辺考えなくて済むのでそこは楽でしたね。 beatsync.net

AFTER CODEBSE School 5week

今週からRails tutorialに取り組み始めた。(今週の学習時間23時間)

f:id:shimabukuromeg:20180423005627p:plain

AFTER CODEBASE School 5week

この1週間でやったこと

  • rails tutorial

    • 1章 ゼロからデプロイまで
    • 2章 toyアプリケーション
    • 3章 ほぼ静的なページの作成
    • 4章 Rails風味のRuby
  • progate rails

    • 復習(気になったところ)
  • たのしいruby
    • 8章「クラスとモジュール」まではざっと読んだ。それ以降は気になったところを少しづつ読んだ
  • sinatra自作アプリ制作
    • slimで書いてたアプリをsinatraで書き直してみてる

振り返り

  • railsチュートリアル楽しい
    • いままでprogateでrails学習してたけど、ローカルで作業ほうが実際の物作ってる感あって良い。
    • 先にprogateでrailsやっていたおかげで、railsチュートリアル入りやすかったのよかった。
  • たのしいruby難しい
    • rubyの基礎が網羅されてて、初心者必読何だろうなあと思うけど、色々むずい。(特異クラスとかブロックとかProcクラスとか)
    • このあたり動機なく一つづつ勉強していっても、どうせすぐ忘れそうなので、チュートリアルとか実際に何かを作っているときに出てきたら随時勉強していこうと思った。ただ、1日1章ぐらい(理解できなくても良いから、そんなに時間かけずに)目を通して置いて、いざ勉強しようとなった時に入りやすくしておく感じにしておく。
  • sinatra自作アプリ制作たのしい
    • codebaseでの講座でslimで作ったアプリをsinatraで描き直すっていうのをだいぶ前からやろうとしてるんだけど、なかなかモチベーション上がらなくて、やってなかった(同じものを作り直すっていうのが、何となく時間ロスな感じがしてモチベーション上がってなかった。)
    • ただ、最近ようやくprogate rails終わらせて、railsチュートリアルやり始めれてたっていうのがあって、チュートリアルばっかじゃなくて、オリジナルのもの作りたいとか思い始めてて、sinatraアプリ制作を再開した。
    • とりあえずチュートリアルの集中力切れたら、sinatraアプリ自作やるって感じで進めてる。

反省

  • 反省点としては、1週間でどこまでできるかを想定して作業してなかったこと。基本的には、railsチュートリアルを中心にその周辺知識を学んでいくっていうtodoはあるけど、今のところそのtodoに対して一週間でどれだけ時間取れるかっていうのが目標になってる感じ。
  • 一応どこまでやるとかは考えるようにするけど、寄り道し始めたりするとまあまあ時間奪われて、想定通りに行かなくなって、テンション下がりそうなので、今のところ目標は時間ベースにする(〜1000時間)
  • HTMLとかCSSとかJSとかの基礎レベルで寄り道発生しやすい

来週todo

  • railsチュートリアルをどこまで進めれるか計算してみた。
  • railsチュートリアル2週180時間=1週90時間で終わると想定する。
  • 今のところ1-4章で15時間かかってるから、残り10章を75時間でやる。
  • 7.5時間/章
  • 1週間に取れる学習時間がだいたい20-25時間なので、だいたい3章分は完了させないと、想定通りの時間でチュートリアル1週できない。
  • 来週1週間で5-7章(3章分)を終わらすことを目標にする
    • 第5章レイアウトを作成する
    • 第6章ユーザーのモデルを作成する
    • 第7章ユーザー登録

まとめ

  • 複数の似たような教材をで学習したことを、それぞれ紐付けながら復習すると理解深まりやすい、と今週一週間を通して思った。(railsチュートリアル/Proage rails/たのしいruby本)
  • 特にrailsチュートリアルの4章とたのしいruby本を一緒にやると良い感じがした。
  • 目標設定大事

AFTER CODEBSE School 4week

今週はProgate Railsを22時間やって、やっとProgate Rails 1周完了した(Progate Railsにかけた時間合計で37時間ぐらい)

f:id:shimabukuromeg:20180416202814p:plain

まとめ

  • とりあえず1週したけどすぐ忘れそうなのでいい感じに復習するようにする
  • CODE BASEのプログラミング講座終わって1ヶ月がたった。
  • ざっくりだけど週20時間ペースの学習時間
  • この1ヶ月でやったこと

    • ドットインストール(slim、ActiveRecord
    • Progate (rubyrails
    • ruby環境構築
    • たのしいrubyを読む(8章の途中までクラスとモジュールのやつ)
    • 卒業制作のslimアプリ制作の続き(一旦ストップ)
  • 次の1ヶ月で終わっていたいこと

以下、progate rails 作業メモ

Progate Rails まとめメモ

Progate Rails 1

アプリケーションの作成

    $ rails new アプリケーション名

サーバー立ち上げ

    $ rails server

トップページ作成

    $ rails generate controller home top

※home・・・・コントローラー名

※top・・・・・アクション名

※新しくページを作る時に、一度作ったコントローラー名と同じコントローラー名で、このコマンドを利用して、ページを作ることはできない(コントローラーのファイルがすでに作られてるから)

ページ作成に必要な3要素

  • ビュー
    • ビューとは、ページの「見た目」を作るためのHTMLファイルです。
    • app/views/home/top.html.erb が作られる
    • 「erb」とは「Embedded Ruby(埋め込みRuby)」の略
    • 埋め込むRubyコードをブラウザに表示したい場合は、<% %>ではなく、<%= %>を用います。
    • CSSファイルの場所は、app/assets/stylesheet/home.scss 例のコマンド(rails generate controller home)叩いた時に作られる。
    • 画像とかは、publicフォルダへ保管する
  • コントローラー
    • ページを表示するとき、Railsの中ではコントローラを経由してビューをブラウザに返しています。
    • app/controller/home_controller.rb
    • コントローラー内のメソッドをアクションという
    • コントローラ内のアクションは、ブラウザに返すビューをviewsフォルダの中から見つけ出す役割を担っている
    • アクションは、コントローラと同じ名前のビューフォルダから、アクションと同じ名前のHTMLファイルを探してブラウザに返します。
    • コントローラーは目的に合わせて作成する
      • トップページに関するコントローラ
      • 投稿に関するコントローラー(投稿一覧/新規投稿など)
  • ルーティング
    • Rails内ではコントローラを経由してビューを返していますが、ブラウザとコントローラを繋ぐ役割を担うのがルーティング
    • ページが表示されるまでの流れは、ルーティング→コントローラ→ビュー
    • ルーティングは、送信されたURLに対して「どのコントローラの、どのアクション」で処理するかを決める対応表のことです。
    • ブラウザでURLを入力すると、ルーティングがURLを見て、適切なコントローラのアクションを呼び出します。
    • get "URL" => "コントローラー名#アクション名"

Routesって何ですか?

  • URLとHTTPメソッドに応じて適切なコントローラにリクエストを振り分けるのがroutes

コントローラーって何ですか?

  • 受け付けたリクエストを処理するのがコントローラー

ビューって何ですか?

  • コントローラでの処理の結果を受け取って、HTMLを返すのがビュー

Progate Rails 1 まとめ

  • まず、Routes,コントローラー,ビューの概念を理解するための説明など。
  • そのあと、routesを変えたり、cssを適用したり、手を動かしながら上記概念を理解する
  • 全体的にtopページ、aboutページを作ってみるのに必要な知識把握できる

Progate Rails 2

  • Progate Rails 1 は、homeコントローラーの話だったけど、Progate Rails 2 は、Postコントローラーの話っぽい。Post コントローラー を使って、投稿に関するリクエストを処理する

Post(投稿)ページ作成

 $ rails generate controller posts index

※posts・・・・コントローラー名 ※index・・・・・アクション名。(一覧ページを作成する時は、indexというアクション名を使用することが一般的)

  • 投稿一覧ページのhtml(ビュー)作った
  • 投稿一覧ページの内容を変数を使って表示できるようにした
  • 投稿が徐々に増えていくことを想定して、投稿内容は配列にまとめるようにした
  • ここまで、上記内容は、全部ビューに書いてたけど、ほんとはアクション(コントローラー)に記述するのが一般的なので、アクションに記述し直した。アクションで定義した変数をビューで使いたい場合は、アクションで定義する変数に@(アットマーク)をつける。そうすれば使える。
  • データベース(テーブル)を用意して、データベースに格納しているデータを取ってきて、投稿一覧ページを表示できるようするために

    マイグレーションとは?

  • データベースに変更を加えること。
  • データベースに変更を加えるためのファイルを生成するコマンド
    $ rails g model Post content:text

※ Post・・・テーブル名の単数形 ※ content・・・カラム名 ※ text・・・データ型

  • 上記コマンドを実行すると、db/migrateフォルダの下にマイグレーションファイルが作られる
  • 上記コマンド実行で作成されたマイグレーションファイルをデータベースに反映させるコマンド $ rails db:migrate

マイグレーションファイルが存在していてい、そのマイグレーションファイルがDBに反映されていない状態だと、Railsではエラーになる。なので、マイグレーションファイルを作成した後は、必ず、rails db:migrateを実行しましょう

モデルとは、テーブルを操作するためのクラス

  • rails g model Post content:text を実行した際に、以下2つのファイルが作られる
    • app/modelsフォルダにモデルが定義されたファイル
    • db/migrateフォルダにマイグレーションファイル
  • ApplicationRecordを継承したクラスをモデルという
    • ApplicationRecordクラスとは????
  • モデル(クラス)から、インスタンスを作成(Post.new)して、そのインスタンスを使って、テーブルのデータを操作する
  • インスタンスを作成した後、データを保存するには、saveメソッドを使う。
  • 登録したデータを取得するときは、Post.firstを使う
  • 登録したデータを全て取得するときは、Post.allを使う。配列で取得できる

    rails consoleとは?

  • Ruby のコードを手軽に実行できるやつ

共通のレイアウトをまとめる方法

  • Railsでは、views/layouts/application.html.erb にヘッダーなど共通部分を書いてあげれる
  • 共通レイアウト views/layouts/application.html.erb に記述されてる <%= yield %> の部分に、他のビューファイルが代入される感じになる

Progate Rails 2 まとめ

  • モデル、マイグレーションの概念理解。Railsマイグレーションファイル作成、反映するの場合のコマンド説明など
  • rails consoleで、モデルのインスタンスで、テーブルへの操作するのを理解できるよう手を動かす感じ。
  • 最終的には、テーブルからデータを取得し、投稿ページが表示できるようになった。
  • この時点では、テーブルのデータを取ってきて、表示させてるだけで、テーブルにデータを登録するなどはできていないので、Progate Rails 3でデータの更新などはやっていく流れっぽい。

Progate Rails 3

  • 前回(Progate Rails 2)が投稿ページを表示するというような内容で、Progate Rails 3は実際に投稿 = データ保存する方法をお勉強するっぽい。
  • テーブルから特定のidの投稿(レコード)を取得するためには、find_byメソッドを利用する。例えば、Post.find_by(id:2) で、idが2のレコードを取れる。前回までは、Post.firstとかだったやつのid指定パターン
  • 投稿したコメントの詳細ページを表示できるようにした。詳細ページは、投稿コメントのidをURLに含めるようにして、そのidの値をparams[:id]で取得するような感じ。
  • 【復習】routesは、リクエスト(メソッドとパス)とコントローラー(アクション)を結ぶ定義が書いていあるファイル
  • 【復習】コントローラーのファイルは、受け付けたリクエストを処理する、コントローラーと同じ名前のビューのディレクトリからアクションと同じ名前のビューファイルを探して、ブラウザに返す
  • 投稿一覧ページの各投稿をlink_to(表示内容,”リンク先パス”)メソッドを使って、詳細ページへリンクするようにした
  • 新規投稿ページを作成した。ざっくり、新しくページを追加したい場合の流れは以下。
    • routesに新しいページへのリクエストとコントローラー(アクション)を追加する
    • コントローラに、いったんアクションだけ記述する。def アクション end って感じ。
    • 表示させるビューファイルを作成する。
    • これで、新しく追加予定のリクエストにアクセスしてみて、ビューファイルの内容が表示されることを確認すればOK。
  • 入力ファオームを作成した。新しく作ったページにform定義を書いた。入力フォームには、form_tagメソッドを使った。 form_tag(送信先のURL) do end
  • フォームからPostした際の処理(routes,コントローラー)を追加した。
  • 更に、createアクションの中身を追加した。
  • アクションでの処理は、基本的にアクションと同じ名前のビューファイルを探して、表示させるような動きになるはずだけど、アクションでの処理の中身にリダイレクトの処理などが書いてたら、必ずしもそうなるわけじゃない。redirect_to()メソッド
  • フォームのinput要素のname属性を設定すると、name属性の値をキー、入力された値をバリューとしたハッシュが、Rails側へ送信できる。(Rails側のコントローラーのアクションで受け取れる。params[:キー] って感じ)
  • 投稿一覧のメッセージをorderメソッドを使って並び替えした。Post.all.order(created_at: :desc)

Paramsとは

  • paramsとは、Railsで送られてきた値を受け取るためのメソッドです。
  • 送られてくる情報(リクエストパラメータ)は主に、以下2通り。(侍エンジニア塾説明)
    • getのクエリパラメータ
    • Postでformを使って送信されるデータの2つです。
  • paramsは以下の2通りの使い方があるので、整理して覚えておきましょう。(Progate説明)
    • 「:○○」を使ったルーティングのURLから値を取得する
      • get “/post/:○○ => “”
    • 「name="○○"」が付いたフォームの入力内容を受け取る

Progate Rails 3 まとめ

  • フォームからデータを登録する方法を勉強した
  • 特定のidのデータを取得する方法 find_by()

Progate Rails 4

  • proate rails 3 でフォームに入力したデータをDBに保存するやり方を学んで、今回はそのデータを更新/削除する処理のお勉強をする感じっぽい
  • rails consoleを使って、削除と編集(update)の処理を手を動かして理解する。find_by()メソッドを使う。
  • rails console でpost.destroyとかpost.content = “変更値” post.saveとかで、データの操作を理解したら、今度はそれをビューのフォームとかからできるようにする
  • 【復習】新しいページを作るとき

    • routesにルート追加
    • コントローラーにアクション追加
    • ブラウザに返すビューを追加
  • link_toメソッドを使って、post用のリンクを作る(削除したい場合) link_to("表示内容","パス",{method: "post"})

  • 基本的にprogate 1-3の内容をわかってたら、そんなに難しくないっぽい。

Progate Rails 5

バリデーションとは

  • 不正なデータがデータベースに保存されないように、データをチェックする仕組みのことをバリデーションという
  • バリデーションに引っかかった場合(不正なデータの場合)にはデータベースに保存されない
  • バリデーションの設定はモデルで設定する
  • 空文字制限する場合は以下
    class Post < ApplicationRecord
      # contentカラムに対して、空の投稿を制限するバリデーションを作成してください
      validates :content , { presence: true }
    end
  • 文字数制限する場合は以下
    class Post < ApplicationRecord
      # contentカラムに対して、文字数を制限するためのバリデーションを追加してください
      validates :content, {presence: true,length: {maximum: 140}}
    end
  • データを保存するときの post.save メソッドはデータ保存に成功した時に戻り値true、失敗した時に戻り値falseを返す仕組みになってる
  • コントローラーで、編集リクエストがきた場合にpost.saveの結果が成功したか、失敗したかでリダイレクト先を変更するようにした
  • validatesでチェックに引っかかって、post.save がfalseになった場合、直前まで入力したデータをそのままに再度入力できるようにする。そのためには、renderメソッドを使う
  • そもそも、直前まで入力してたデータが消えてしまうのは、editアクションにリダイレクトされるのが原因。
  • なので、直前まで入力していたデータをそのまま使うために、renderメソッドを使う。renderメソッドを使うと他のアクションを経由せずに、ビューを表示することができる(編集フォームを表示することができる)
  • renderメソッドを使う場合のパスの指定に注意する。render("フォルダ名/ファイル名")って書く。
  • saveメソッドで保存に失敗(バリデーションに失敗した場合)、Rails側で自動的にエラーメッセージが生成されるようになってる。
  • @post.errors.full_messagesの中に、エラー内容が配列で格納されてる
  • エラーメッセージをビューに出力させる例が以下。(このエラーメッセージを記述するHTMLの場所に戸惑った。form_tagのなかに書くっぽい。
    <% @post.errors.full_messages.each do |message| %>
       <div class="form-error">
         <%= message %>
       </div>
    <% end %>
  • 投稿に成功したら、成功したってメッセージを表示させる。そのため、Railsの特殊な変数flashを利用する。falsh[:notice] = “表示させたい文字列”
    <% if flash[:notice] %>
      <div class="flash">
        <%= flash[:notice] %>
      </div>
    <% end %>
  • 「renderメソッドを使うときは、別のアクションを経由しないで、renderメソッドで指定したビューを使える。そうすることで、renderメソッドを使っているアクションで定義してる変数を使える。」なんだけど、renderメソッドに指定されてるビューのアクションに、renderメソッドを使っているアクションで、定義している変数がなかったら、その別のアクション経由でアクセスした場合にエラーになるから、例えば、@post = Post.newって書いとけば大丈夫

Progate Rails 6

  • ユーザーの新規登録/編集ができるようにした
  • これまで投稿の機能でやってたことをユーザーに置き換えてやる
  • postsテーブルを作って投稿内容を保存してたことと同じことを、usersテーブル作ってユーザーを保存するようにする
  • 【復習】usersテーブルのマイグレーションファイルとモデルクラスを作成するコマンド
    $ rails g model User name:string email:string
  • 【復習】usersテーブルを作成する、DBに変更を反映するコマンド
    $ rails db:migrate
  • バリデーションで重複した入力を登録できないようにする場合は、uniqueness: trueをつかう
    class User < ApplicationRecord
      # nameカラムに関するバリデーションを作成してください
      validates :name,{presence: true}
      # emailカラムに関するバリデーションを作成してください
      validates :email,{presence: true ,uniqueness: true}
    end

Progate Rails 7

  • ユーザーのプロフィール画像を登録できるようにした
  • 画像ファイルは、ファイル名をテーブルに保存して、ファイル自体はpublicフォルダに保存するのが一般的
  • プロフィール画像のファイル名をusersテーブルに保存できるように、テーブルの構造を変更した。テーブルの構造を変更するまでの全体的な流れは、
  • これまで、テーブルを作成する際には、例えば rails model User image_names:string というコマンドを実行して作成していたが、マイグレーションファイルのみを作成する場合は、rails g migratione ファイル名 コマンドを使う
    $ rails g migration add_image_name_to_users
  • 作成したマイグレーションファイルに、テーブルを変更したい内容を、changeメソッドの中に記述する
  • マイグレーションファイルをDBに反映するために、使っていたコマンド「rails db:migrate」は、このchangeメソッドに記述された内容を実行するためのコマンドで、これまでに、テーブルを作ってた時も(rails model .. を実行した時も)changeメソッドが実行されていた。(rails model .. を実行した時は、changeメソッドの中身は自動で記述されてた)
  • カラムを追加するため、上記で作成したマイグレーションファイル編集するために、どんな感じでchageメソッドの中身を記述するかというと、例えば、usersテーブルに、image_nameカラム(string型)を追加したい場合は以下のように記述する。(add_column :テーブル名, :カラム名, :データ型)
    class AddImageNameToUsers < ActiveRecord::Migration[5.0]
      def change
        add_column :users,:image_name,:string
      end
    end
  • changeメソッドを記述したら、以下コマンドを実行して、テーブルに反映させる
    $ rails db:migrate
  • プロフィール画像を送信する方法(注意点は以下2点、この時点では画像フォームを作成しただけで、受け付けた画像ファイルを保存する処理は書いていないので、保存できない)

    • フォームで、画像をアップロードしたい場合、inputタグに「type="file"」を追加することで、画像ファイルを選択するボタンを表示することができる
    • 画像の送信は特殊なので、form_tagに{multipart: true}を追加する必要がある。
  • publicフォルダにふぁいるを保存する方法を知るために、まずrubyでファイルを作成する方法についてお勉強する。

    • Rubyのコードでファイルを扱うには、Rubyに元から用意されてあるFileクラスを使う
    • ファイルを作成するためには、Fileクラスのwriteメソッドを使う
    • 使い方は「File.write(ファイルの場所, ファイルの中身)」 [1] pry(main)> File.write("public/sample.txt","Hello World") => 11
  • 画像保存時の処理の流れ

    • フォームに画像ファイルが入力されたか確認する(if params[:image] 〜)
    • テーブルにファイル名を保存するようにする(@user.image_name = "#{@user.id}.jpeg”)
    • フォームに入力されたイメージを受け取る(image = params[:image])
      • 変数imageに対し、readメソッドを用いることでその画像データを取得できる
    • Fileクラスを使って、publicしたに画像ファイルを保存する
     if params[:image]
       @user.image_name = "#{@user.id}.jpeg"
       image = params[:image]
       File.binwrite("public/user_images/#{@user.id}.jpeg",image.read)
    end

Progate Rails 8

  • Progate Rails 8 は、ログイン/ログアウト機能を作るセクション
  • ログインフォームを作成したし、ログイン状態に応じてアクセス制御したり、パスワード入力するようにしたり、など。
  • パスワードを保管するカラムを作成した
    rails g migration ファイル名
  • 反映
    rails db:migrate
  • パスワードを入力する用のフォームを作った inputタグのtype属性をpasswordとすると、入力したパスワードが伏字となるパスワード用のフォームになります。

  • ログインした後に、ログイン情報を保持するためには、sessionという変数を使う ページを移動してもユーザー情報を保持し続けるために、sessionという特殊な変数を用います。sessionに代入された値は、ブラウザ(InternetExplorer, GoogleChrome等)に保存されます。sessionに値を代入すると、ブラウザはそれ以降のアクセスでsessionの値をRailsに送信します。

  • sessionの使い方

    session[:キー名] = 値
  • sessionに値を代入するときには、user_idをキーとし、値を代入します。@userが存在する場合に変数sessionに@user.idを代入することで、特定したログインユーザーの情報が保持され続けます。Progateの例は以下。
    def login
      @user = User.find_by(email: params[:email], password: params[:password])
      if @user
        session[:user_id] = @user.id
        flash[:notice] = "ログインしました"
        redirect_to("/posts/index")
      else
        @error_message = "メールアドレスまたはパスワードが間違っています"
        @email = params[:email]
        @password = params[:password]
        render("users/login_form")
      end
    end
  • ログインの処理ってどうやるんだっけてよく忘れちゃうけど、progateの上記コードだと、emailとpasswordの入力値を引数に、find_byして、その結果値を受け取れたら、sessionにユーザーidを格納して、ログイン完了って感じで、find_byした結果、その結果がfalseだったら、ログイン失敗って感じか。

  • ログイアウトする場合は、sessionの値を空にする ログアウトする、つまり「ログイン状態でなくする」にはsession[:user_id]の値を空にします。session[:user_id]にnilを代入することで、session[:user_id]の値を空することができます。

  • sessionの値を空にする方法

    session[:user_id] = nil
  • ログインしているユーザー名を表示させるようにする
  • ログインしているかどうかでアクセスを制御する。
  • 各コントローラの全アクションで共通する処理がある場合には、before_actionを使う。before_actionを用いることで、アクションが呼び出される際に必ずbefore_actionの処理が実行されます。これにより、全アクションで共通する処理を1箇所にまとめることができます。(applicationコントローラーの一番最初の行に以下のような感じで書く before_action 全アクションで共通する処理

  • ここで使う全てのコントローラーの全てのアクションで共通する処理は、applicationコントローラにて記述する 全てのコントローラで共通する処理はapplicaitonコントローラにまとめることができる。例えば、ログイン中のユーザーを取得するset_current_userメソッドを定義し、before_actionに指定する。これで、全コントローラの全アクションで@current_userを定義することができます。

  • application_controller.rbの記述例

    class ApplicationController < ActionController::Base
      before_action :set_current_user
      def set_current_user
        current_user = User.find_by(id: session[:user_id])
      end
    end
  • アクセス制御、ログインしていない場合、コンテンツにアクセスできないようにした。ログインしていない場合のアクセス制御は方法は、まず、application_controller.rbに、@current_userがいるかどうか条件分岐にて確認して、いない場合(nilの場合)は、flashでログインが必要ですと表示させた後、ログインページにリダイレクトされるように設定
  • そのために必要な関数を以下のような感じで定義する。(application_controller.rbに記述)
    def authenticate_user
      if @current_user == nil
        flash[:notice]="ログインが必要です"
        redirect_to("/login")
      end
    end
  • これも共通化するために、application_controller.rbに記述する。ただ、この処理は、全てのアクションに設定するわけではないので、before_action は、(applicationコントローラの先頭ではなくて)該当のコントローラーの先頭に書いて、適用したいアクションにのみ適用する。適用したいアクションはonlyで指定する before_action :authenticate_user,{only: [:index,:show,:edit,:update]}

  • ここまでは、ログインしていないユーザーのアクセス制御だったけど、つぎはログインしているユーザーのアクセス制御を行う。例えば、ログインしているユーザーに新規登録などのページは不要。application_controller.rbに以下の関数を定義して、ログインしてるユーザーに参照されたくないviewへのアクションに適用するようにする。上記authenticate_userの逆。

  • すでにログインしているユーザーのチェックは、@current_userが存在するかどうかのif文で確認
    def forbid_login_user
      if @current_user
        flash[:notice] = "すでにログインしています"
        redirect_to("/posts/index")
      end
    end

Progate Rails 8 まとめ

  • このセクションとこのセクションを含む道場コースは明日もう一度復習する。(すぐ忘れそうな要素多い)

Progate Rails 9

  • このセクションでは、投稿とユーザーを紐付ける実装のお勉強。
  • postsテーブルに誰が投稿したかがわかるようにuser_idカラムを追加した
  • (復習)マイグレーションファイルを作成
    $ rails g migration マイグレーションファイル名
  • (復習)変更内容追記
    def change
      add_column :users,:user_id,:integer
    end
  • (復習)変更内容反映
    $ rails db:migrate
  • 投稿するタイミングで、@current_user.idをpostsテーブルのuser_idカラムに保存する。
  • (復習)@current_userってどのタイミングでどうやって作成されるだっけ?
    • application_controller.rbで定義してて、session[:user_id]の中に入っているユーザーidを条件にusersテーブルからひっぱてきたユーザーを@current_userに格納している。
    • session[:user_id]に入っているユーザーidはどっから持ってきたのかというと、ログイン時もしくは、新規登録時のアクションの処理でsession[:user_id]=@user.idで値を保管してる。
    • この際の@userインスタンスは、ログイン処理や新規登録処理では、フォームにパスワードやemailの情報を入力するので、その値を使ってusersテーブルから該当ユーザーの情報を取得している
  • Railsでは、モデルクラスにインスタンスメソッドを定義することができる。
  • (復習)インスタンスメソッドとはなんだっけ???
    • クラスに定義されているメソッドで、インスタンスに対して呼び出すことのできるメソッドのこと

どのユーザーが投稿したメッセージかわかるようにするには???(投稿メッセージに投稿したユーザーを表示するようにするには???)

  • メッセージを投稿する際、投稿したユーザーの情報をpostsテーブルに保存するようにする(user_idカラム追加)
  • 投稿メッセージ(=@postインスタンス)のuser_idの情報を使って、@userインスタンスをリターンするインスタンスメソッドをpostモデルに定義する。このインスタンスメソッドを使って、投稿メッセージのビューを表示するアクションの中に@userインスタンスを定義してあげると、投稿メッセージを表示するビューで、@userインスタンスを利用することができる
    def user
      return User.find_by(id: self.user_id)
    end
  • @userを使って投稿メッセージに投稿したユーザーの情報を表示するようにする

  • 特定のユーザーが投稿した投稿インスタンスの情報を、1件のみpostsテーブルから取得する方法は、Post.find_by(user_id: @user.id) とかで取得できる。

  • 特定のユーザーが投稿した投稿インスタンスの情報を、複数postsテーブルから取得する場合はwhereメソッドを使って取得する。
  • 以下のように書くとuser_idが1の投稿が全て取得され、配列に格納される
    Post.where(user_id: 1)

ユーザー詳細ページに、そのユーザーが投稿したメッセージ一覧を出す方法???

  • (予想)ユーザーの詳細ページは、/users/:idのURLで、users#showアクションで、表示する。投稿一覧の情報を取得するには、Post.where(user_id :該当ユーザーのid)で取得する。showアクションの中に投稿一覧の配列の情報を取得するようにする????(結果としては違っててた)
    def show
      posts = Post.where(user_id: params[:id])
    end
  • Userモデルにpostsメソッド(インスタンスメソッド)を作成する。このメソッドを使うとユーザーインスタンスが投稿した投稿メッセージをpostsテーブルか取得できる。
    def posts
      return Post.where(user_id: self.id)
    end

Progate Rails 10

  • このセクションでは投稿メッセージに「いいね」をつける実装のお勉強。(取り消しもできる)
  • likeテーブルを作る
  • テーブル作成のためのマイグレーションファイルを作成
    $ rails g model テーブル名 カラム名:データ型 カラム名:データ型
    $ rails g model Like user_id:integer post_id:integer
    $ rails db:migrate
    $ rails g migrateion マイグレーションファイル名
  • likeテーブルには、user_idとpost_idが保存されている。

likeテーブルのデータを使って、どのユーザーがどの投稿にいいねしたか表示する方法???

  • likeデータを作成するためには、コントローラーが必要なので作成する。これまで、「rails g controller」コマンドで作っていたけど、今回はビューファイルが必要ないので、(rails g controllerコマンド実行したら、ビューファイルも一緒に作成される。ビューファイルが必要ないということはpostリクエストを処理するアクションのみ必要になるってこと)手動で作成する。ファイル名:likes_controller.rb
  • likesコントローラーを作ったので、/likes/:post_id/create のpostリクエストが受けたら、likes_controller.rbに定義したcreateアクションで処理される

    「いいね!」するために、createアクションの処理に何が書いてあるでしょうか???

  • (予想)ログインユーザーの情報と投稿詳細の情報( /posts/:id ページの情報)をLikeテーブルに保存する処理だな
    like = Like.new(user_id: @current_user.id,post_id: @post.id)
    if like.save
    flash[:notice] = "いいねしました"
    end
  • (結果)リダイレクトの処理抜けてた。likeじゃなくて@like。先頭に@がついている変数は、インスタンス変数。同じインスタンスのメソッドで共通利用できる

「いいね!」を取り消すために必要な処理は???

  • (予想)Likeテーブルに保存されているデータを削除する
  • 削除のpostリクエストをroutesに追加する
  • 削除のpostリクエストを受け付けた時のアクションを追加する
  • (復習)これまで出てきた削除処理って、どんな感じで書いてたっけ???
  • (復習)これまでの削除処理を振り返ってみると、データ削除する際に、postリクエストで受け付けて削除処理してた。データ削除する用のメソッドってなかったけ???
  • (復習)データベースに登録してるデータの削除方法忘れた
  • (正解)routes.rb ※抜粋 post "likes/:post_id/destroy" => "likes#destroy"

  • (正解)likes_controllerrb ※抜粋 def destroy @like = Like.find_by(user_id: @current_user.id,post_id: params[:post_id]) @like.destroy redirect_to("/posts/#{params[:post_id]}") end

  • (正解)show.html.erb ※抜粋 <%= link_to("いいね!済み","/likes/#{@post.id}/destroy",{method: "post"}) %>

「いいね!」の見た目を作るには???(ハートのアイコンにする)

  • Font Awesomeを使う。「Font Awesome」とは、様々なアイコンをフォントとして利用できるようにしたもの

  • これをlink_to()メソッドに指定してあげる。ただ、上記spanタグのようなHTMLをlink_to()メソッドに指定する場合、これまで書いてきたlink_to()メソッドとは、異なる書き方が必要

  • これまで書いてきたlink_to()メソッド
    <%= link_to("表示したい文字列","URL") %>
  • リンクとして表示した文字列をHTMLで表現したい場合は以下のように書く。
    <% link_to("URL") do %>
     <- ここにHTMLを記述 ->
    <% end %>

「いいね!」の数を取得するには???

  • (予想)投稿メッセージのidをlikeテーブルで検索をかける
    @posts = Like.where(post_id: params[:post_id])
    @posts.size
  • (正解)countメソッドを使う
    @likes_count = Like.where(post_id: 2).count

特定のユーザーが「いいね!」した投稿の一覧を表示するには???

  • (予想)いいね一覧を表示させるアクションを作成して、その処理の中にユーザーidを条件にlikeテーブルで検索した結果を表示させるようにする
    @favarit = Like.where(user_id: prams[:id])
  • (正解)routes.rb 抜粋
    get "users/:id/likes" => "users#likes"
  • (正解)users_controller.rb 抜粋
    def likes
      @user = User.find_by(id: params[:id])
      @like = Like.where(user_id: @user.id)
    end
  • (正解)likes.html.erb 抜粋
        <% @likes.each do | like | %>
          <% post = Post.find_by(id: like.post_id)  %>  
          <div class="posts-index-item">
            <div class="post-left">
              <img src="<%= "/user_images/#{post.user.image_name}" %>">
            </div>
            <div class="post-right">
              <div class="post-user-name">
                <%= link_to(post.user.name, "/users/#{post.user.id}") %>
              </div>
              <%= link_to(post.content, "/posts/#{post.id}") %>
            </div>
          </div>
        <% end %>

Progate Rails 11

  • このセクションではパスワードの安全な取り扱い方法についてお勉強する
  • パスワードを暗号化するためのgem bcryptを使って暗号化する

    どうやって暗号化するのか???

  • bcryptをインストールすると、has_secure_passwordというメソッドが使えるようになるので、これを使って暗号化する。
  • このメソッドをUserモデルクラスで使えるように設定しておくと、新規ユーザー登録の際にパスワードが勝手に暗号化されてる。Userモデルクラスに設定するのは、以下の記述にあるように記載してあげればOK
    class User < ApplicationRecord
      has_secure_password
      validates :name, {presence: true}
      validates :email, {presence: true, uniqueness: true}
      def posts
        return Post.where(user_id: self.id)
      end
    end
  • has_secure_passwordメソッドは暗号化したパスワードをpassword_digestというカラムに保存すると決まっている。
  • そのため、password_digestカラムを新しくUserテーブルに追加して、passwordカラムを削除する必要がある
  • (復習)テーブルに変更を加える時には、まずマイグレーションファイルを作成する
    $ rails g migration マイグレーションファイル名
    $ rails g migration change_users_columns
  • (復習)マイグレーションファイルを作成したら、変更内容を記述する(changeメソッドに変更内容を記述数流)
    def change
      add_column :users,:password_digest,:string
      remove_column :users,:password,:string
    end
  • (復習)変更内容を反映する
    $ rails db:migrate

password_digestカラムを新しく追加して、passwordカラムを削除したので、これまでpasswordカラムに保存してた処理をpassword_digestカラムに保存するように修正しないといけない、と思っていたけどその必要はないらしい。なぜ???

  • has_secue_passwordメソッドがpasswordに保存された値をpassword_digestに暗号化して保存するようにしてくれるらしく、問題ないとのこと。

暗号化したパスワードでログインする方法は???

  • 元々のログイン方法は、フォーム入力されたemailとpasswordがusersテーブルに存在するかチェックして、存在してたらログインできるという方法だった。パスワードが暗号化されたらどうなるか???
  • フォーム入力されたパスワードを暗号化して、保存されている暗号化されたパスワードと一致するか確認する必要がある。その処理の方法は???
  • (正解)authenticateメソッドを使う。authenticateメソッドは渡された引数を暗号化し、password_digestの値と一致するかどうかを判定できる。
  • このauthenticateメソッドはhas_secure_passwordメソッドを有効にしている場合に利用できるメソッド

AFTER CODEBASE School 3week

今週は、Progate Rails 15時間分やった。ほんとはslimアプリをsinatraで書き直す作業を完成させようと思ってたけど、あまり気分が乗らなかったので、Progate Railsを中心に進めた。

f:id:shimabukuromeg:20180409191823p:plain

AFTER CODEBASE School 3week

まとめ

  • Progate Railsやってて思ったのは、基本的にいろんなことすぐ忘れちゃうので、一度やったことと同じことを何回もやるの重要。復習大事。
  • ほんとは、Progate Railsは今週で終わらしたかったけど、時間足りず、終われなかった。。。。土日に確保できる時間に頼ってるとこあるから、土日に時間取れなかったら、予定通りいかないので計画大事。

AFTER CODEBASE School 2week

今週はCODEBASE Schoolで書いてたSlimのアプリでできたことを、Sinatraでもできるようになることを目標に作業してた。けど、目標達成できず。。。。(今週の学習時間22時間)

f:id:shimabukuromeg:20180402001028p:plain

AFTER CODEBASE School 2week

  • Progate
  • ドットインストール
  • Slimで書いてたのと同じことをSinatraで書く(先週ドットインストール Sinatra入門終わった後に足りてなそうな知識を中心に学習)
    • DBの操作
    • セッション(ログイン)->未完了
    • 画像アップロード ->未完了
    • パスワードをハッシュ化 ->未完了

振り返り

  • 今週は本筋じゃないとこで色々ハマって時間取られた。そして結局原因はわからず。。。。。なんかよくわかんないけど動いた(改善した)、にしては時間取られすぎたと思うので、ちゃんと理解できるようにしておきたい。だけど、寄り道しすぎたら目的見失う問題もあるので、バランスよくする
    • zshだとrbenv install がうまくいかない。bashだとうまくいった(xcodeとかhomebrewとか再インストールとかして無駄に時間かかった)
    • mysql2がない(gem is not loaded)っぽいエラーになってて、activerecord のバージョン上げて 5.1.6にすると直った
    • sinatraでbootstrapのテンプレートのCSSをちゃんと読み込んでくれなかったんだけど、何回か同じ作業をやり直してみるとうまくいった(うまくいったってことは同じ作業をしてないっぽい)
  • いろんなことを忘れてるなーと思った。例えば、Slimと同じことをsinatraでやろうと思った時に、Slimでログインする機能作ったときどうやったんだっけ?とか、jQueryとかめっちゃ忘れてた
- ユーザーテーブルを作成
- 新規ユーザー登録フォーム作成
  - slimのやつをコピペした
- ユーザー登録処理作成
  - 既存に存在しないチェック
  - ハッシュ化
  - セッションに追加
- ログインフォーム作成
  - slimのやつをコピペした
- ログイン処理
  - セッションチェック
  - パスワードチェック
  - セッションに追加

その他 備忘録メモ

  • phpでvar_dumpして変数の中身を調べてたことと同じようなことをしたいと思って調べてたけど、まだちゃんとわかってない

qiita.com

qiita.com

  • ドットインストール Active Record入門はsqlite3を使ってるけど、mysqlを使いたかった。以下記事が参考になった

qiita.com

qiita.com

  • HTMLはしょっちゅう忘れるので随時いろいろ振り返る

developer.mozilla.org

Active Record入門 ドットインストール をやってみた備忘録メモ

Active Recordとは

  • OR Mapper (OR Mapper の O は Object、R は Relational database のこと)
  • マッピングすることによって、SQL を意識せずに Ruby のオブジェクトのようにデータベースを簡単に扱うことができる
  • 例えば、users テーブルのレコードをUserクラスのインスタンスとして扱えるようになる。
class User < ActiveRecord::Base
end

バリデーション

  • 入力値チェック。レコードを挿入したり更新したりする時に、ルールを付けることができる仕組み
  • 以下の例は、空白を防ぐ。nameは3文字以上
class User < ActiveRecord::Base
   validate :name,:age,presence: true
   validate :name, length: {minimum: 3}
end

コールバック

  • callbackの種類は他にもいくつかあり、何らかの処理の前後で特定の処理をしたい場合に使う。
  • ActiveRecordで定義されてる
    • before_destoryってのと
    • after_destroyってのが
  • 以下の例は、レコードが削除される前後で標準出力する
require 'active_record'
require 'pp'
require 'logger'

Time.zone_default = Time.find_zone! 'Tokyo'
ActiveRecord::Base.default_timezone = :local

ActiveRecord::Base.establish_connection(
    "adapter" => "sqlite3",
    "database" => "./myapp.db"
)

class User < ActiveRecord::Base
  before_destroy :print_before_msg
  after_destroy :print_after_msg

  protected
    def print_before_msg
      puts "#{self.name} will be deleted"
    end
    def print_after_msg
      puts "#{self.name} deleted"
    end
end

User.delete_all
User.create(name: "taka",age: 33)
User.create(name: "masa",age: 44)
User.create(name: "mari",age: 53)
User.create(name: "taro",age: 23)
User.create(name: "maki",age: 13)
User.create(name: "mika",age: 29)

User.where("age >= 20").destroy_all
  • 実行結果
$ bundle exec ruby main.rb
taka will be deleted
taka deleted
masa will be deleted
masa deleted
mari will be deleted
mari deleted
taro will be deleted
taro deleted
mika will be deleted
mika deleted

アソシエーション

  • 複数のテーブル(オブジェクト)を関連づける仕組み
  • 例えば、
    • ユーザーが複数のコメントをもつという関係
      • has_many :comments
    • コメントからすると、一つのユーザーに属する関係
      • belongs_to :user
require 'active_record'
require 'pp'

Time.zone_default = Time.find_zone! 'Tokyo'
ActiveRecord::Base.default_timezone = :local

ActiveRecord::Base.establish_connection(
    "adapter" => "sqlite3",
    "database" => "./myapp.db"
)

class User < ActiveRecord::Base
  has_many :comments
end

class Comment < ActiveRecord::Base
  belongs_to :user
end

User.delete_all
User.create(name: "taka",age: 33)
User.create(name: "masa",age: 44)
User.create(name: "mari",age: 53)
User.create(name: "taro",age: 23)
User.create(name: "maki",age: 13)
User.create(name: "mika",age: 29)

Comment.delete_all
Comment.create(user_id: 1,body: "hello01")
Comment.create(user_id: 1,body: "hello02")
Comment.create(user_id: 2,body: "hello03")

comments = Comment.all
comments.each do |c|
  puts "#{c.body} by #{c.user.name}"
end
  • 実行結果
$ bundle exec ruby main.rb
hello01 by taka
hello02 by taka
hello03 by masa
  • アソシエーション(削除)

    • 例えば、ユーザーを削除して、関連するコメントを一緒に削除したい場合
class User < ActiveRecord::Base
  has_many :comments, dependent: :destroy
end

感想

Active Record の関連付け (アソシエーション) あたりが難しかったので要復習

railsguides.jp

rbenv install で ruby をインストールできない問題(途中で止まってるように見える)

ここ一週間ぐらいローカルのMacに rbenv install で ruby をインストールできなくて悩んでたけど、色々やって最終的に、ログインシェルをzshにしてたのをbashにするとrbenv install できるようになった。なぜだかわからん。。。。もうちょっと調べようと思うけど、何したか忘れないように備忘メモ。

rbenv install 実行結果(zsh

何回やっても、checking whether make sets $(MAKE)...の部分で止まる。。。。なぜだ。。。

$ rbenv install 2.3.0 -v
ruby-build: use openssl from homebrew
/var/folders/90/vd_x3drd53bcvdw__08nrtmh0000gn/T/ruby-build.20180329003818.50000 ~
Downloading ruby-2.3.0.tar.bz2...
-> https://cache.ruby-lang.org/pub/ruby/2.3/ruby-2.3.0.tar.bz2
/var/folders/90/vd_x3drd53bcvdw__08nrtmh0000gn/T/ruby-build.20180329003818.50000/ruby-2.3.0 /var/folders/90/vd_x3drd53bcvdw__08nrtmh0000gn/T/ruby-build.20180329003818.50000 ~
Installing ruby-2.3.0...
ruby-build: use readline from homebrew
checking for ruby... /Users/shimabukuromegumi/.rbenv/shims/ruby
config.guess already exists
config.sub already exists
checking build system type... x86_64-apple-darwin17.4.0
checking host system type... x86_64-apple-darwin17.4.0
checking target system type... x86_64-apple-darwin17.4.0
checking whether the C compiler works... yes
checking for C compiler default output file name... a.out
checking for suffix of executables...
checking whether we are cross compiling... no
checking for suffix of object files... o
checking whether we are using the GNU C compiler... yes
checking whether clang accepts -g... yes
checking for clang option to accept ISO C89... none needed
checking whether we are using the GNU C++ compiler... yes
checking whether clang++ accepts -g... yes
checking how to run the C preprocessor... clang -E
checking for grep that handles long lines and -e... /usr/bin/grep
checking for egrep... /usr/bin/grep -E
checking whether clang needs -traditional... no
checking for ld... ld
checking whether the linker is GNU ld... no
checking whether clang -E accepts -o... yes
checking for real target cpu... x86_64
checking for ranlib... ranlib
checking for ar... ar
checking for as... as
checking for objdump... objdump
checking for objcopy... no
checking for gobjcopy... no
checking for nm... nm
checking whether ln -s works... yes
checking whether make sets $(MAKE)...

ログインシェル変更(zshからbashへ)

$ cat /etc/shells
# List of acceptable shells for chpass(1).
# Ftpd will not allow users to connect who are not using
# one of these shells.

/bin/bash
/bin/csh
/bin/ksh
/bin/sh
/bin/tcsh
/bin/zsh
/usr/local/bin/zsh

$ chsh -s /bin/bash

qiita.com

rbenv install リトライ

ログインシェルを bashにして以下コマンドを実行すると問題なくインストールできた

$ rbenv install 2.3.0 -v
$ rbenv versions
  system
  2.3.0
* 2.4.0 (set by /Users/shimabukuromegumi/.rbenv/version)
  2.5.0
$ rbenv global 2.3.0
$ rbenv versions
  system
* 2.3.0 (set by /Users/shimabukuromegumi/.rbenv/version)
  2.4.0
  2.5.0
$ ruby -v
ruby 2.3.0p0 (2015-12-25 revision 53290) [x86_64-darwin17]

ログインシェルがbashであるのとzshであるので、環境がどう変わるのか???

  • 謎だ。。。
  • もう少し調べよう

github.com

github.com