- 投稿日:2020-07-04T23:39:07+09:00
【Rails】form_withの属性や基本構文
はじめに
チーム開発にて Ruby on Rails を用いてWebアプリを作成中です。
まだまだ未熟なため、記述やコードがわかりにくいかもしれません。
記載しきれなかったものや変更点は、随時更新・ブラッシュアップしていきます。私と同じ初学者の方は検索に検索を重ねると「いま自分が何を調べたいのか」が分からなくなってしまいがちですので、専門用語やカタカナ用語は都度 解説します。
form_with
:
フォーム実装のためのヘルパーメソッド。
Rails5.1よりform_withが導入され、form_forやform_tagが非推奨となり今後 削除される予定となっている。一つの構文でフォームが書けるようになった。
HTTPメソッドを指定する必要が無い。form_withではinputタグは用いない。html.haml.form-wrapper = form_with url: credit_cards_path, method: :post, model: @card, local: true, html:{name: "inputForm"}, class: 'form-wrapper', id: 'charge-form' do |form|解説します。
= form_with 〜 do |form| -# 例えるなら、お菓子作りでいうケーキカップ(= form_with)と袋と紐(do |form|)url: credit_cards_path -# パスは関連モデルがある場合、省略可。method: :post -# formのデフォルトHTTPメソッドはPOST、省略可。model: @card -# ActiveRecordを継承するモデルのインスタンスが利用可。local: true -# デフォルトで非同期通信リクエストで送信されるようになる。remote: trueの指定をする必要は無い。
非同期通信
:
コンピュータ間で送信者のデータ送信タイミングと受信者のデータ受信タイミングを合わせずに通信を行う通信方式。つまり、送信者と受信者の両方がオンラインである必要がなく、片方が接続しているだけで通信が成立する。html:{name: "inputForm"} -# name: "inputForm"(html:{}が無い)だとエラーになる。class: 'form-wrapper', id: 'charge-form' -# class属性やid属性にはHTML属性は不要。form_with ヘルパー表
オプション 説明 デフォルト値 :url フォームに入力されたデータを送信するURL。名前付きルートを直接使用可能 nil :method HTTPメソッドの指定。 POST :format 送信するデータ形式 JSON形式やXML形式など:urlオプションが指定された場合は無視される。 text/html :scope ルーティングで名前空間が指定されている場合に利用するPrefix指定。 nil :model モデルオブジェクト。オブジェクトが新規レコードの場合は作成フォームが生成され、既存レコードの場合は更新フォームが生成される。 nil :authenticity_token 認証トークンの指定。カスタム認証トークンでオーバーライドするかfalse、認証トークンフィールドをスキップする。 :local リモートフォームを使わない設定。 false :skip_enforcing_utf8 IE5以前の文字化け対策(UTF-8の矯正送信)のスキップ設定。 false :builder フォームオブジェクトのオーバーライド(オリジナルフォームコントロールの作成) nil :id オプションのHTML id属性 nil :class オプションのHTML class属性 nil :data オプションのHTML data属性 nil :html id、class、data以外のオプションHTML属性 nil おわりに
form_withのまとめは以上となります。
例がお役に立てば幸いです。参考記事
- 投稿日:2020-07-04T22:57:33+09:00
モデル作成、削除等のあれこれ
毎回忘れるので、自分の忘備録用に
モデルの作成
外部キー制約(※1)をつけるならreferrence型で作成(インデックスは自動で付貼ってくれる)
カラムはuser_idではない点に注意rails g model Article title:string text:text user:references(*1)存在しない値の外部キーは登録できない(整合性がとれる)、親テーブルに外部キーが登録されている子テーブルのリソースは削除できない
外部キー制約をつけないならintger型で作成(インデックスを貼るならマイグレーションファイルで記述が必要)
カラムはuser_idにするrails g model Article title:string text:text user_id:intger
単純にマイグレーションファイルだけの作成
rails g migration Artcleモデルの型
- string : 文字列(255字まで)
- text : 文字列(string以上の文字数の場合)
- references: 外部キー
- integer : 整数
- float : 浮動小数点
- datetime : 日時
- time : 時間
- date : 日付
- boolean : trueまたはfalse
削除
マイグレーションを実行する前か後かで操作が変わるので注意(rails db:migrateをしたかしてないか)
rails db:migrate前ならdestroyで削除
rails destroy model Articlerails db:migrate後ならロールバックで戻す
rails db:rollback #1つ前 rails db:rollback STEP=2 #2つ前 rails db rollback VERSION=202007012345 #バージョン指定で戻すそのあとに rails destroy model Article で完了。
カラムの追加と削除
カラムの追加
rails g migration AddSubtitleToArticles subtitle:string
カラムの削除rails g migration RemoveSubtitleFromArticles subtitle:stringテーブル名Artcles(複数形)になる点に注意
マイグレーションファイルではchangeメソッドの中に記述される。
- 投稿日:2020-07-04T22:49:07+09:00
Ruby on Rails チュートリアル(第4版) 第6章
6.1.1 演習
1.Railsはdb/ディレクトリの中にあるschema.rbというファイルを使っています。これはデータベースの構造 (スキーマ (schema) と呼びます) を追跡するために使われます。さて、あなたの環境にあるdb/schema.rbの内容を調べ、その内容とマイグレーションファイル (リスト 6.2) の内容を比べてみてください。
省略2.ほぼすべてのマイグレーションは、元に戻すことが可能です (少なくとも本チュートリアルにおいてはすべてのマイグレーションを元に戻すことができます)。元に戻すことを「ロールバック (rollback)と呼び、Railsではdb:rollbackというコマンドで実現できます。
省略3.もう一度rails db:migrateコマンドを実行し、db/schema.rbの内容が元に戻ったことを確認してください。
省略6.1.2 演習
1.Railsコンソールを開き、User.newでUserクラスのオブジェクトが生成されること、そしてそのオブジェクトがApplicationRecordを継承していることを確認してみてください (ヒント: 4.4.4で紹介したテクニックを使ってみてください)。
>> user = User.new => #<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil> >> user.class => User(id: integer, name: string, email: string, created_at: datetime, updated_at: datetime) >> user.class.superclass => ApplicationRecord(abstract)2.同様にして、ApplicationRecordがActiveRecord::Baseを継承していることについて確認してみてください。
>> user.class.superclass.superclass => ActiveRecord::Base6.1.3 演習
1.user.nameとuser.emailが、どちらもStringクラスのインスタンスであることを確認してみてください。
>> user.name.class => String >> user.email.class => String2.created_atとupdated_atは、どのクラスのインスタンスでしょうか?
>> user.created_at.class => ActiveSupport::TimeWithZone >> user.updated_at.class => ActiveSupport::TimeWithZone6.1.4 演習
1.nameを使ってユーザーオブジェクトを検索してみてください。また、 find_by_nameメソッドが使えることも確認してみてください (古いRailsアプリケーションでは、古いタイプのfind_byをよく見かけることでしょう)。
>> User.find_by(name: "Michael Hartl") User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."name" = ? LIMIT ? [["name", "Michael Hartl"], ["LIMIT", 1]] => #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com", created_at: "2020-07-04 11:43:14", updated_at: "2020-07-04 11:43:14"> >> User.find_by_name("Michael Hartl") User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."name" = ? LIMIT ? [["name", "Michael Hartl"], ["LIMIT", 1]] => #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com", created_at: "2020-07-04 11:43:14", updated_at: "2020-07-04 11:43:14">2.実用的な目的のため、User.allはまるで配列のように扱うことができますが、実際には配列ではありません。User.allで生成されるオブジェクトを調べ、ArrayクラスではなくUser::ActiveRecord_Relationクラスであることを確認してみてください。
>> User.all.class => User::ActiveRecord_Relation3.User.allに対してlengthメソッドを呼び出すと、その長さを求められることを確認してみてください (4.2.3)。Rubyの性質として、そのクラスを詳しく知らなくてもなんとなくオブジェクトをどう扱えば良いかわかる、という性質があります。これをダックタイピング (duck typing) と呼び、よく次のような格言で言い表されています「もしアヒルのような容姿で、アヒルのように鳴くのであれば、それはもうアヒルだろう」。(訳注: そういえばRubyKaigi 2016の基調講演で、Ruby作者のMatzがダックタイピングについて説明していました。2〜3分の短くて分かりやすい説明なので、ぜひ視聴してみてください!)
>> User.all.length User Load (0.2ms) SELECT "users".* FROM "users" => 26.1.5 演習
1.userオブジェクトへの代入を使ってname属性を使って更新し、saveで保存してみてください。
>> user.name = "Muramako" => "Muramako" >> user.save (0.1ms) SAVEPOINT active_record_1 (0.1ms) RELEASE SAVEPOINT active_record_1 => true2.今度はupdate_attributesを使って、email属性を更新および保存してみてください。
特定の属性のみを更新したい場合は、次のようにupdate_attributeを使います。の文があるので、こちらを使います。>> user.update_attribute(:email, "Muramako@example.com") (0.1ms) SAVEPOINT active_record_1 SQL (0.1ms) UPDATE "users" SET "email" = ?, "updated_at" = ? WHERE "users"."id" = ? [["email", "Muramako@example.com"], ["updated_at", "2020-07-04 11:59:33.683197"], ["id", 1]] (0.1ms) RELEASE SAVEPOINT active_record_1 => true3.同様にして、マジックカラムであるcreated_atも直接更新できることを確認してみてください。ヒント: 更新するときは「1.year.ago」を使うと便利です。これはRails流の時間指定の1つで、現在の時刻から1年前の時間を算出してくれます。
>> user.update_attribute(:created_at, 1.year.ago) (0.1ms) SAVEPOINT active_record_1 SQL (0.1ms) UPDATE "users" SET "created_at" = ?, "updated_at" = ? WHERE "users"."id" = ? [["created_at", "2019-07-04 12:00:26.106554"], ["updated_at", "2020-07-04 12:00:26.107328"], ["id", 1]] (0.1ms) RELEASE SAVEPOINT active_record_1 => true6.2.1 演習
1.コンソールから、新しく生成したuserオブジェクトが有効 (valid) であることを確認してみましょう。
>> User.new.valid? => true2.6.1.3で生成したuserオブジェクトも有効であるかどうか、確認してみましょう。
>> user.valid? Traceback (most recent call last): 1: from (irb):2 NameError (undefined local variable or method `user' for main:Object) Did you mean? super6.2.2 演習
1.新しいユーザーuを作成し、作成した時点では有効ではない (invalid) ことを確認してください。なぜ有効ではないのでしょうか? エラーメッセージを確認してみましょう。
>> u = User.new => #<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil> >> u.invalid? => true >> u.errors.full_messages => ["Name can't be blank", "Email can't be blank"]2.u.errors.messagesを実行すると、ハッシュ形式でエラーが取得できることを確認してください。emailに関するエラー情報だけを取得したい場合、どうやって取得すれば良いでしょうか?
自力で分からず。無念。
u.email.errors.messages
u.errors.messages(:email)
等試したけどダメ。
ググった結果正解はu.errors.messages[:email]
惜しい!
find_byの時なんかは、()だと思うんだけど、エラー調べるときは[]なのね(何でだろう)。6.2.3 演習
1.長すぎるnameとemail属性を持ったuserオブジェクトを生成し、有効でないことを確認してみましょう。
>> user = User.new(name: "Muramakooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo", email: "Muramakooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo@example.com") => #<User id: nil, name: "Muramakooooooooooooooooooooooooooooooooooooooooooo...", email: "Muramakooooooooooooooooooooooooooooooooooooooooooo...", created_at: nil, updated_at: nil> >> user.invalid? => true2.長さに関するバリデーションが失敗した時、どんなエラーメッセージが生成されるでしょうか? 確認してみてください。
>> user.errors.messages=> {:name=>["is too long (maximum is 50 characters)"], :email=>["is too long (maximum is 255 characters)"]}6.2.4 演習
1.リスト 6.18にある有効なメールアドレスのリストと、リスト 6.19にある無効なメールアドレスのリストをRubularのYour test string:に転記してみてください。その後、リスト 6.21の正規表現をYour regular expression:に転記して、有効なメールアドレスのみがすべてマッチし、無効なメールアドレスはすべてマッチしないことを確認してみましょう。
省略
2.先ほど触れたように、リスト 6.21のメールアドレスチェックする正規表現は、foo@bar..comのようにドットが連続した無効なメールアドレスを許容してしまいます。まずは、このメールアドレスをリスト 6.19の無効なメールアドレスリストに追加し、これによってテストが失敗することを確認してください。次に、リスト 6.23で示した、少し複雑な正規表現を使ってこのテストがパスすることを確認してください。test "email validation should reject invalid addresses" do invalid_addresses = %w[foo@bar..com user@example,com user_at_foo.org user.name@example. foo@bar_baz.com foo@bar+baz.com] invalid_addresses.each do |invalid_address| @user.email = invalid_address assert_not @user.valid?, "#{invalid_address.inspect} should be invalid" end endREDになった。リスト6.23を反映したらGREENになった。
3.foo@bar..comをRubularのメールアドレスのリストに追加し、リスト 6.23の正規表現をRubularで使ってみてください。有効なメールアドレスのみがすべてマッチし、無効なメールアドレスはすべてマッチしないことを確認してみましょう。
省略6.2.5 演習
1.リスト 6.33のように、メールアドレスを小文字にするテストをリスト 6.26に追加してみましょう。ちなみに追加するテストコードでは、データベースの値に合わせて更新するreloadメソッドと、値が一致しているかどうか確認するassert_equalメソッドを使っています。リスト 6.33のテストがうまく動いているか確認するためにも、before_saveの行をコメントアウトして redになることを、また、コメントアウトを解除すると greenになることを確認してみましょう。
省略2.テストスイートの実行結果を確認しながら、before_saveコールバックをemail.downcase!に書き換えてみましょう。ヒント: メソッドの末尾に!を付け足すことにより、email属性を直接変更できるようになります (リスト 6.34)。
省略6.3.2 演習
1.この時点では、userオブジェクトに有効な名前とメールアドレスを与えても、valid?で失敗してしまうことを確認してみてください。
>> user = User.new(name: "Muramako", email: "Muramako@example.com") => #<User id: nil, name: "Muramako", email: "Muramako@example.com", created_at: nil, updated_at: nil, password_digest: nil> >> user.valid? User Exists (0.2ms) SELECT 1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER(?) LIMIT ? [["email", "Muramako@example.com"], ["LIMIT", 1]] => false2.なぜ失敗してしまうのでしょうか? エラーメッセージを確認してみてください。
>> user.errors.messages => {:password=>["can't be blank"]}6.3.3 演習
1.有効な名前とメールアドレスでも、パスワードが短すぎるとuserオブジェクトが有効にならないことを確認してみましょう。
>> user = User.new(name: "Muramako", email: "Muramako@example.com", password: "mmm") => #<User id: nil, name: "Muramako", email: "Muramako@example.com", created_at: nil, updated_at: nil, password_digest: "$2a$10$/pcBPDq8e44IQ/8vLxMR3e3xVyMOOk.G35K.yCFFEWO..."> >> user.invalid? User Exists (0.2ms) SELECT 1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER(?) LIMIT ? [["email", "Muramako@example.com"], ["LIMIT", 1]] => true2.上で失敗した時、どんなエラーメッセージになるでしょうか? 確認してみましょう。
>> user.errors.messages => {:password=>["is too short (minimum is 6 characters)"]}6.3.4 演習
1.コンソールを一度再起動して (userオブジェクトを消去して)、このセクションで作ったuserオブジェクトを検索してみてください。
user = User.find_by(id:1)2.オブジェクトが検索できたら、名前を新しい文字列に置き換え、saveメソッドで更新してみてください。うまくいきませんね...、なぜうまくいかなかったのでしょうか?
>> user.email = "mhartl@example.jp" => "mhartl@example.jp" >> user.save (0.1ms) begin transaction User Exists (0.3ms) SELECT 1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER(?) AND ("users"."id" != ?) LIMIT ? [["email", "mhartl@example.jp"], ["id", 1], ["LIMIT", 1]] (0.0ms) rollback transaction => false >> user.errors.messages => {:password=>["can't be blank", "is too short (minimum is 6 characters)"]}3.今度は6.1.5で紹介したテクニックを使って、userの名前を更新してみてください。
>> user.update_attribute(:email, "mhartl@example.jp") (0.1ms) begin transaction SQL (2.0ms) UPDATE "users" SET "email" = ?, "updated_at" = ? WHERE "users"."id" = ? [["email", "mhartl@example.jp"], ["updated_at", "2020-07-04 13:29:40.208038"], ["id", 1]] (6.7ms) commit transaction => true謎のエラー
$ rails test (GREEN) $ git add -A $ git commit -m "Make a basic User model (including secure passwords)" $ git checkout master $ git merge modeling-users $ git push$ rails testここでRED。さっきはGREENだったのに、なぜ?
You don't have bcrypt installed in your application. Please add it to your Gemfile and run bundle instal
Gemfileに、gem 'bcrypt', '3.1.12'
はあるのに。
bundle install
→rails t
でもやっぱりRED。
ググってみたら、一度Gemfileのgem 'bcrypt', '3.1.12'
をコメントアウト→bundle install
→コメント解除→bundle install
これでGREENに。なぜなのかは分からないけれど。
無事GREENになったのでherokuへpush!ここでまたエラー!
$ heroku run rails db:migrate bash: heroku: command not foundググった。
heroku command not found の対処に載っているコードを入力。
うまくいきました。ありがたや。メモ
- コントローラ名には複数形を使い、モデル名には単数形を用いるという慣習がある。(例)コントローラはUsersでモデルはUser。
dup
は、同じ属性を持つデータを複製するためのメソッド。case_sensitive: false
は大文字小文字を区別するかどうかを指定するオプション。- マイグレーション名に
to_users
をつけると、usersテーブルにカラムを追加するマイグレーションがRailsによって自動的に作成される。has_secure_password
を追加すると、そのオブジェクト内でauthenticate
メソッドが使えるようになる。このメソッドは、引数に渡された文字列 (パスワード) をハッシュ化した値と、データベース内にあるpassword_digestカラムの値を比較する。
- 投稿日:2020-07-04T21:23:46+09:00
[Rails]部分テンプレートの使い方
部分テンプレートとは
複数のビューファイルの中で使われている部分を一つのビューファイルとして管理する時に使います。
部分テンプレート用ビューファイルの作り方
部分テンプレートとして呼び出す場合は、ファイル名の頭に_(アンダーバー)を作成します。
new.html.erbのファイルを呼び出したい場合は、_new.html.erbになるということです。部分テンプレートの呼び出し方
部分テンプレートの呼び出しには以下のようにrenderメソッドを使用します。
render 'ファイル名'ただし、同じディレクトリでない場合の呼び出しは以下のようにフォルダ名の記述も必要です。
render 'フォルダ名/ファイル名'まとめ
今回が**部分テンプレートの使い方を簡潔にまとめました。
個人開発アプリも日々進んでいるので近いうちに実際に使用した機能のアウトプットも行います。よかったら参考にして下さい!
- 投稿日:2020-07-04T21:23:32+09:00
【Docker】初心者が環境構築しました!
背景
未経験から転職した僕が初めてDockerの環境構築をしたので、アウトプットします。併せて、同じ初心者の方の参考になれば嬉しいです。
使用環境
- Macを使用しています
- 開発フレームワークはRuby on Railsです
Dockerとは
仮想環境を提供するオープンソースソフトウェアです。
仮想環境というのは、自分のPCやサーバーとは別の環境のことをいいます。
このメリットは何のか?といいますと例を2つくらい挙げたいと思います。
MacPCを使用しいてWindows専用のソフトを使いたい!という時に仮想環境を作ってWindowsの環境を作り出せばそこでWindowsのソフトを作れちゃうって感じになります!
自分のPCのプログラミング言語のバージョンが5だとして、仕事の現場のバージョンが3を使っていたとしたらわざわざ自分のPCのバージョンを落さなくてもこの仮想環境情で開発できちゃうって訳です!!
これは便利ですね?(初心者ながら思っています、、笑)
なぜDockerを使うのか
理由は主に2点あります。
1. 環境構築が簡単
既に開発が行われている現場な難しい環境構築をしなくても簡単なコマンド(docker compose)を打てばすぐに開発に移れます。
2. 移動のしやすさ
イメージで言うとdockerと言う船にコンテナと言うパーツ(プログラミング言語Webサーバー、データベース等)が乗っている感じです。これはコマンドで簡単に移動できるみたいです。(まだ体験していないですが、、)環境構築手順
※以下はまだDockerとDocker Composeを自分のPCにインストールしていない方向けです。(初めてDockerを使う方)
1. まずは下記のURLでDockerのインストールから始めましょう!
https://www.docker.com/
2. インストールが終わったらターミナルを開いて下記の作業をしてください。
そして、% docker --versionと% docker-compose --version(%は除いてください) と打ってください。
下記の様に出ていればインストール成功です。
まとめ
今回は概念の理解と簡単な環境構築手順をまとめてみました。
勉強し始めで間違っているところあるかもしれませんが、Dockerってすごく便利だ
なって思いました。今後も勉強してまた記事を更新していきたいと思います!!
- 投稿日:2020-07-04T20:18:21+09:00
has_secure_passwordとpresenceとallow_nilと私。
Railsチュートリアル(第6版)の10.1.4 -TDDで編集を成功させる でつまり、
午前中いっぱいかけて解消させた疑問の備忘録です。
私以外の人はこんなところでつまらないかもしれないですが・・つまったところと疑問点
つまったところ:パスワードが空のままでも更新できるようにする
「ユーザー情報を編集する際、いちいちパスワードを入力するのは不便なので
パスワードを変えないときは、入力しなくてもユーザー情報を更新できるようにする。」
という内容。具体的には、現状
user.rbclass User < ApplicationRecord has_secure_password validates :password, presence: true, length: { minimum: 6 }となっていて、nilや空文字、6文字未満のパスワードは受け入れられないところを
user.rbclass User < ApplicationRecord has_secure_password validates :password, presence: true, length: { minimum: 6 }, allow_nil: trueと、
allow_nil
というオプションで、パスワードが空だった時の例外処理を与える(バリデーションをスキップする)よ。ということでした。
何が疑問なの?という方、どうかこの記事をそっと温かくスキップしてください
疑問点:そもそも、presence: true書く必要あったの??
なぜにそんな疑問が生まれてしまったのかというと、チュートリアル中の、
「allow_nilによって新規登録時にパスワードがなくても登録できちゃう!?と心配になるけど、
has_secure_password
がオブジェクト生成時に存在性を検証するようになっているから、
その心配もいらないよ!」
という文。
(簡略化してます。語弊があったら申し訳ないです。。)以下、私の疑問と疑問が生まれる原因となった勘違いです。
- 新規登録時はパスワード必須にしたい →
has_secure_password
で検証できる- 編集時はパスワード入れなくても更新できるようにしたい →
has_secure_password
はオブジェクト生成時にのみ、存在性を検証するので、編集時はパスワード入れなくてもOK! ⇒ あれ、has_secure_password
だけで完結する?presence: true
いらなくない・・?大事なことを見落としていたわけですね。
結論:編集時、空の場合はスキップしたいが、空文字の場合はバリデーションかけねば。
改めて、下記3点を明確にしておきたいと思います。
has_secure_password
:DBにレコードが生成された時だけ存在性のvalidationをおこなうpresence: true
: 値がnil
や空文字
でないことを確認allow_nil
: 対象の値がnil
の場合にvalidationをスキップ = 値が空文字
の場合はvalidationにひっかかるつまり、
presence: true
は書いておかないと、
「編集時、パスワード欄にスペースを打ちこまれちゃった場合にも更新ができてしまう」
のでしたね・・どうやって疑問を解消したのか
「
presence: true
いらなくない・・?」と思っていたので
presence: true
を消して、下記の状態でテストを回してみました。user.rbclass User < ApplicationRecord has_secure_password validates :password, length: { minimum: 6 }, allow_nil:trueすると下記統合テストはGREENに。
test/integration/users_edit_test.rbtest "successful edit" do get edit_user_path(@user) assert_template 'users/edit' name = "Foo Bar" email = "foo@bar.com" patch user_path(@user), params: { user: {name: name, email: email, password: "", password_confirmation: "" }} assert_not flash.empty? assert_redirected_to @user @user.reload assert_equal name, @user.name assert_equal email, @user.email endやはり
presence: true
いらないのか・・?と思ったそのとき、
別のテストが私に思い出させてくれました。user_test.rbtest "password should be present (nonblank)" do @user.password = @user.password_confirmation = " " * 6 assert_not @user.valid? endこちらがしっかりREDになっていた!
まずこのテストはユーザを新規登録せずにパスワードの値を空文字に設定しているため
has_secure_password
の検証にはひっかかりません。そして
presence: true
を消してしまったがために、空文字入力がOKになり、
ユーザーが有効になってしまったのでテストがREDになっていたのでした。ここでしっかり、
" " * 6
と空文字がテストされていたおかげで
自分の勘違いに気づくことができました。。同じようにうっかりここで詰まってしまった方のご参考になれば幸いです。
参考
- 投稿日:2020-07-04T20:02:02+09:00
Ruby on Rails チュートリアル(第4版) 第5章
5.1.1 演習
1.Webページと言ったらネコ画像、というぐらいにはWebにはネコ画像が溢れていますよね。リスト 5.4のコマンドを使って、図 5.3のネコ画像をダウンロードしてきましょう8 。
$ curl -OL cdn.learnenough.com/kitten.jpg2.mvコマンドを使って、ダウンロードしたkitten.jpgファイルを適切なアセットディレクトリに移動してください (参考: 5.2.1)。
$ mv kitten.jpg app/assets/images/3.image_tagを使って、kitten.jpg画像を表示してみてください (図 5.4)。
/app/views/static_pages/home.html.erb<%= image_tag("kitten.jpg", alt: "kitten") %>5.1.2 演習
1.リスト 5.10を参考にして、5.1.1.1で使ったネコ画像をコメントアウトしてみてください。また、ブラウザのHTMLインスペクタ機能を使って、コメントアウトするとHTMLのソースからも消えていることを確認してみてください。
/app/views/static_pages/home.html.erb<%#= image_tag("kitten.jpg", alt: "kitten") %>2.リスト 5.11のコードをcustom.scssに追加し、すべての画像を非表示にしてみてください。うまくいけば、Railsのロゴ画像がHomeページから消えるはずです。先ほどと同様にインスペクタ機能を使って、今度はHTMLのソースコードは残ったままで、画像だけが表示されなくなっていることを確認してみてください。
省略5.1.3 演習
1.Railsがデフォルトで生成するheadタグの部分を、リスト 5.18のようにrenderに置き換えてみてください。ヒント: 単純に削除してしまうと後でパーシャルを1から書き直す必要が出てくるので、削除する前にどこかに退避しておきましょう。
省略
2.リスト 5.18のようなパーシャルはまだ作っていないので、現時点ではテストは redになっているはずです。実際にテストを実行して確認してみましょう。
RED
3.layoutsディレクトリにheadタグ用のパーシャルを作成し、先ほど退避しておいたコードを書き込み、最後にテストが green に戻ることを確認しましょう。パーシャルを作成
$ touch app/views/layouts/_rails_default.html.erb/sample_app/app/views/layouts/_rails_default.html.erb<%= csrf_meta_tags %> <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>GREENに・・・ならず。
/home/ec2-user/environment/sample_app/db/schema.rb doesn't exist yet. Runrails db:migrate
to create it, then try again. If you do not intend to use a database, you should instead alter /home/ec2-user/environment/sample_app/config/application.rb to limit the frameworks that will be loaded.
とのことなので、
rails db:migrate
を実行
まだエラーが出たものの、コードに間違いは無さそうなのでとりあえずサーバーを再起動Ctrl+C
→rails s
GREENになった。良かった!5.2.2 演習
1.5.2.2で提案したように、footerのCSSを手作業で変換してみましょう。具体的には、リスト 5.17の内容を1つずつ変換していき、リスト 5.20のようにしてみてください。
省略5.3.2 演習
1.実は名前付きルートは、as:オプションを使って変更することができます。有名なFar Sideの漫画に倣って、Helpページの名前付きルートをhelfに変更してみてください (リスト 5.29)。
省略2.先ほどの変更により、テストが redになっていることを確認してください。リスト 5.28を参考にルーティングを更新して、テストを greenにして見てください。
/test/controllers/static_pages_controller_test.rbtest "should get help" do get helf_path assert_response :success assert_select "title", "Help | #{@base_title}" end3.エディタのUndo機能を使って、今回の演習で行った変更を元に戻して見てください。
Ctrl+Z
のことらしい。元に戻して保存。5.3.3 演習
1.リスト 5.29のようにhelfルーティングを作成し、レイアウトのリンクを更新してみてください。
/config/routes.rbget '/help', to: 'static_pages#help', as: 'helf'/app/views/layouts/_header.html.erb<li><%= link_to "Help", helf_path %></li>2.前回の演習と同様に、エディタのUndo機能を使ってこの演習で行った変更を元に戻してみてください。
省略5.3.4 演習
1.footerパーシャルのabout_pathをcontact_pathに変更してみて、テストが正しくエラーを捕まえてくれるかどうか確認してみてください。
/app/views/layouts/_footer.html.erb<li><%= link_to "About", contact_path %></li>REDになる。Expected at least 1 element matching "a[href="/about"]", found 0..
2.リスト 5.35で示すように、Applicationヘルパーで使っているfull_titleヘルパーを、test環境でも使えるようにすると便利です。こうしておくと、リスト 5.36のようなコードを使って、正しいタイトルをテストすることができます。ただし、これは完璧なテストではありません。例えばベースタイトルに「Ruby on Rails Tutoial」といった誤字があったとしても、このテストでは発見することができないでしょう。この問題を解決するためには、full_titleヘルパーに対するテストを書く必要があります。そこで、Applicationヘルパーをテストするファイルを作成し、リスト 5.37のFILL_INの部分を適切なコードに置き換えてみてください。ヒント: リスト 5.37ではassert_equal <期待される値>, <実際の値>といった形で使っていましたが、内部では==演算子で期待される値と実際の値を比較し、正しいかどうかのテストをしています。
/test/helpers/application_helper_test.rbrequire 'test_helper' class ApplicationHelperTest < ActionView::TestCase test "full title helper" do assert_equal full_title, "Ruby on Rails Tutorial Sample App" assert_equal full_title("Help"), "Help | Ruby on Rails Tutorial Sample App" end end5.4.1 演習
1.表 5.1を参考にしながらリスト 5.41を変更し、users_new_urlではなくsignup_pathを使えるようにしてみてください。
/test/controllers/users_controller_test.rbrequire 'test_helper' class UsersControllerTest < ActionDispatch::IntegrationTest test "should get new" do get signup_path assert_response :success end end2.先ほどの変更を加えたことにより、テストが redになったことを確認してください。なお、この演習はテスト駆動開発 (コラム 3.3) で説明した red/green のリズムを作ることを目的としています。このテストは次の5.4.2で greenになるよう修正します。
REDになる。NameError: undefined local variable or method `signup_path' for #UsersControllerTest:0x00000000044804f05.4.2 演習
1.もしまだ5.4.1.1の演習に取り掛かっていなければ、まずはリスト 5.41のように変更し、名前付きルートsignup_pathを使えるようにしてください。また、リスト 5.43で名前付きルートが使えるようになったので、現時点でテストが greenになっていることを確認してください。
GREENになった。2.先ほどのテストが正しく動いていることを確認するため、signupルートの部分をコメントアウトし、テスト redになることを確認してください。確認できたら、コメントアウトを解除して greenの状態に戻してください。
/config/routes.rb# get '/signup', to: 'users#new'
REDになった。NameError: undefined local variable or method `signup_path' for #UsersControllerTest:0x0000000002acb078
元に戻したらGREENになった。3.リスト 5.32の統合テストにsignupページにアクセスするコードを追加してください (getメソッドを使います)。コードを追加したら実際にテストを実行し、結果が正しいことを確認してください。ヒント: リスト 5.36で紹介したfull_titleヘルパーを使ってみてください。
/test/integration/site_layout_test.rbget signup_path assert_select "title", full_title("Sign up")メモ
assert_equal <期待される値>, <実際の値>
は、内部では==演算子で期待される値と実際の値を比較し、正しいかどうかのテストをしている。
- 投稿日:2020-07-04T19:25:52+09:00
create時だけ値を生成してupdate時は変更できないようにしたい
numberというカラムを作成。numberはとある条件を元に数字を計算し、それが値として入る。(今回の記事に関係ないので具体的なコードや条件は割愛)
新規登録時にはbefore_saveを経由して上記条件に基づきカラムの値を生成するが、更新時にもその値が変わらないようにしたい場合。before_saveに書いたメソッドは登録更新どちらのsave時にも経由してしまうため、updateのときだけ通らないようにしたらいいのでは?と考えて以下の実装。
attr_accessorを使用。
参考にさせていただいた記事
https://kei178.me/programming/1416/Hoge.rbattr_accessor :number_required before_save :apply_number, if: :number_required? def number_required? number_required end def apply_number self.number = number_for_new_post true end def number_for_new_post # 適切な数字を生成するための処理 endcontrollerdef create @hoge = Hoge.new(create_hoge_params) @hoge.number_required = true # この一行追加 if @hoge.save redirect_to root_path else render :new end end ... def update begin @hoge = Hoge.find(params[:id]) @hoge.number_required = false # ここの一行追加 @hoge.update!(update_hoge_params) redirect_to hoge_path rescue ActiveRecord::RecordNotFound => e flash[:alert] = "更新失敗しました" redirect_to hoge_path rescue ActiveRecord::RecordInvalid => e render :edit end end ... private def create_hoge_params # 新規作成時に必要なカラム end def update_hoge_params # 更新時に必要なカラム endこれだと確かにupdateのときはbefore_saveを通らない。
しかし今回のように新規登録時にはnumberが必ず必要となる場合、number_required?だとわかりづらい。。そこで使うのがnilガード
そもそもbefore_saveを経由しても値が変わらないようにすれば問題がなかったため、以下のように修正するだけでよかった。
参考にさせていただいた記事
https://note.com/vixer93/n/n20c409919665Hoge.rbattr_accessor :number_required before_save :apply_number def apply_number self.number ||= number_for_new_post true endnumberに値がなければnumber_for_new_postメソッドの返り値がnumberに。
もしnumberがもう入ってればそのまま。
- 投稿日:2020-07-04T18:30:39+09:00
Rails5でECサイトを作る② ~Bootstrap4設定、Controller・アクション定義~
はじめに
架空のベーカリーで買い物できるECサイトを作るシリーズ、Rails5でECサイトを作る①の続きです。
必要なControllerとViewを揃え、アクションも定義だけ書いて、最低限のページ遷移ができるようにします。ソースコード
https://github.com/Sn16799/bakeryFUMIZUKI
Controller
コントローラを一気に作ります。Viewも伴うアクションは、ここでまとめて作ってしまいます。
Customerサイト
$ rails g controller addresses edit index $ rails g controller cart_items index $ rails g controller customers edit show withdraw $ rails g controller genres index show $ rails g controller homes about top $ rails g controller order_items index $ rails g controller orders confirm index new show thanks $ rails g controller products index showAdminサイト
$ rails g controller admin::customers edit index show $ rails g controller admin::genres edit index $ rails g controller admin::homes top $ rails g controller admin::order_items $ rails g controller admin::orders index show $ rails g controller admin::products edit index new show $ rails g controller admin::searches searchアクション
ViewのないアクションをControllerファイルに記述していきます。
詳しい内容は後で書くので、今は一通りアクションを定義して、ページ遷移できることを優先します。Customerサイト
app/controllers/addresses_controller.rbdef create end def update end def destroy endapp/controllers/cart_items_controller.rbdef create end def update end def destroy end def destroy_all endapp/controllers/customers_controller.rbdef udpate end def withdraw_done endapp/controllers/order_items_controller.rbdef new endapp/controllers/orders_controller.rbdef create endAdminサイト
app/controllers/admin/customers_controller.rbdef update endapp/controllers/admin/genres_controller.rbdef create end def update endapp/controllers/admin/order_items_controller.rbdef update endapp/controllers/admin/orders_controller.rbdef update endapp/controllers/admin/products_controller.rbdef create end def update endBootstrap4 読み込み
gem(bootstrap, jquery-rails)のインストールについては前回行ったので割愛します。
app/assets/stylesheets/application.cssをapplication.scssにリネームしておきます。
そのファイル内にBootstrapの読み込みを記述します。appseets/stylesheets/application.scss@import 'bootstrap';javascriptファイルにも同様に記述を行います。
app/assets/javascripts/application.js//= require rails-ujs //= require activestorage -------------------------------- //= require jquery3 //= require popper #ここを追記 //= require bootstrap-sprockets -------------------------------- //= require_tree .これでBootstrapを利用できるようになりました。試しに適当なところで使ってみましょう。
app/views/homes/top.html.erb<h1>Homes#top</h1> <p>Find me in app/views/homes/top.html.erb</p> <%= link_to 'ABOUT', homes_about_path, class: 'btn btn-info' %>正しくページが表示され、"ABOUT"と書かれたリンクがボタンになりました! きちんとBootstrapが適用されていますね。
containerなどの枠組みをまだ何も設定していないので、全体的におそろしく端に寄ってしまいました。後ほど直しますが、今はとりあえず気にしないことにします。後記
ひとまずRoutingとControllerを作ったので、必要なページは全て表示できるようになりました。次の記事から、具体的な機能の実装に入れそうです。
ところで、ページのデザインをどんな感じにするか、まだ迷っています。基本的にはBootstrapを下地としたレスポンシブ対応の設計にする予定ですが、おしゃれなCSSに挑戦するのも悪くないなぁと思ったり……。
今更ながら「パン屋の温かみあるイメージ」と「スタイリッシュな今風のサイト」が全然結び付かず、テーマ選びを間違えた感じがしているのですが、写真素材がすでに手元にあるのでこのまま進めます。
果たして、webサイトを違和感なく仕上げられるのか? 次回へ続く!
- 投稿日:2020-07-04T18:25:04+09:00
【Rails】初めてのDB設計
SQL(Structured Query Language)とは
- リレーショナルDB(RDB)に命令を行う言語。
- ISO(国際標準化機構)で規格化されており、どんなリレーショナルDBにも、同じ文法で操作する。
- リレーショナルDB(RDB): Webアプリで一般的に使用される、表形式でデータ管理するDBのこと。(例:MySQL、PostgreSQL、SQliteなど)
- 命令は2つに分類。
・ 定義:DDL
(Data Definition Language)
・ 操作:DML
(Data Manipulation Language)【DDLの命令】
命令 機能 CREATE DB、テーブル作成 ALTER DB、テーブル更新 DROP DB、テーブル削除 【DMLの命令】
命令 機能 INSERT データ登録 UPDATE データ更新 DELETE データ削除 SELECT データ検索 ターミナルでのSQL文実行
- SQLの実行には、DBへの接続が必要なので、以下のコマンドで
mysql
にrootユーザーで接続すると、MySQLが起動し、SQL文でMySQLが操作できる。ターミナルでのSQL操作-- mysqlに接続 % mysql -u root -- 今まで作ったDBの一覧表示 mysql> SHOW DATABASES; -- DB作成 mysql> CREATE DATABASE DB名; -- DBを選択 mysql> USE DB名; -- DB内のテーブル一覧表示 mysql> SHOW TABLES; -- テーブル作成 mysql> CREATE TABLE テーブル名 (カラム名 カラム名の型, ……); mysql> CREATE TABLE items (id INT, name VARCHAR(255)); -- ※ rails g model モデル名 でテーブルに紐づくモデルとマイグレーションファイルを作成/実行(rake db:migrate)するのと同義。 -- ※ rake db:migrate で、 CREATE TABLE というSQL文が動いてる -- テーブル構造(カラム名、カラム型、制約など)の確認 mysql> SHOW columns FROM テーブル名; mysql> SHOW columns FROM items; -- テーブル構造の変更(カラムの追加・修正・削除など) mysql> ALTER TABLE テーブル名 操作 -- カラム追加 mysql> ALTER TABLE テーブル名 ADD カラム名 カラム型; -- 1つ追加 mysql> ALTER TABLE テーブル名 ADD (カラム名 カラム型, ……); -- 複数追加 mysql> ALTER TABLE items ADD (price int, zaiko int); -- カラムの修正 (カラム型は変更がなくても、書く必要がある!) mysql> ALTER TABLE テーブル名 CHANGE 古いカラム名 新しいカラム名 新しいカラム型; mysql> ALTER TABLE items CHANGE zaiko stock int; -- カラムの削除 mysql> ALTER TABLE テーブル名 DROP カラム名; mysql> ALTER TABLE items DROP stock; -- データの登録 -- 全カラムに値を入れる場合 mysql> INSERT INTO テーブル名 VALUES(値1, ...); -- 特定カラムのみに値を入れる場合 (指定しないカラムはデフォルト値(NULL)が入る) mysql> INSERT INTO テーブル名(カラム名1, ...) VALUES(値1, ...); mysql> INSERT INTO items VALUES(1, "ペン", 120); mysql> INSERT INTO items(id, name) VALUES(2, "消しゴム"); -- テーブルの全レコード表示 mysql> SELECT * FROM items; -- データ更新 mysql> UPDATE テーブル名 SET 変更内容 WHERE 条件; mysql> UPDATE items SET price = 100 WHERE id = 2; -- データ削除 mysql> DELETE FROM テーブル名 WHERE 条件; mysql> DELETE FROM items WHERE id = 2; -- mysqlを終了 mysql> exit
SQL文 機能 SHOW DATABASES 文 DB一覧表示 CREATE DATABASE 文 DB作成 CREATE TABLE 文 テーブル作成 USE 文 DBを選択 USHOW TABLES文 DB内のテーブル一覧表示 ALTER TABLE 文 カラムの追加・修正・削除 INSERT 文 テーブルにデータ登録 UPDATE 文 テーブルのデータ更新 DELETE 文 テーブルのデータ削除
- SQL文の終わりは
;
を付ける。
(忘れたら、SQLがまだ続くとみなされ、->
が表示されるので、;
を入力すればOK)。- カラム型の(例)
INT
:数字、VARCHAR(●)
:最大●文字の文字列クエリでのSQL文実行
検索では、クエリから実行する。理由は、
- 検索では、SQL文が長くなるので、タイプミス時の修正が容易
- 検索結果の出力も長くなっても、SQL文が流れず、見やすい
- SQL文の文末の
;
が不要。クエリ(例えば、SequelPro)でのSQL文の実行方法は、上画面にSQL文を書き、
現在を実行
をクリックすると実行される。
SELECT句
(テーブルの指定) +FROM句
(取得するカラムの指定)が、検索の基本構造。
WHERE句
: 取得するレコードを制限。条件式は、
比較演算子(<、>、..)、
AND演算子(and、or)、
BETWEEN演算子(上限と下限の指定)、
IN演算子(1つのカラムにリストを指定し、値がリストに含まれる時 true)、
NOT演算子など。
ワイルドカード
: 文字の代わりに使える記号のこと。*
は全部の意味。クエリでの検索-- SQL文のコメントアウト -- 検索の基本構造 SELECT カラム名 -- 取得カラムの指定 FROM テーブル名 -- 検索するテーブルの指定 WHERE 条件 -- 取得レコードの制限 -- (例)全カラムを取得、レコードの検索条件を指定 SELECT * FROM users WHERE age <= 22 AND prefecture = "神奈川県" -- (例) SELECT * FROM users WHERE age <= 20 OR prefecture = "東京都" -- (例) SELECT * FROM users WHERE age BETWEEN 21 AND 24 -- (例) SELECT * FROM users WHERE NOT prefecture = "東京都" -- (例) SELECT * FROM users WHERE prefecture IN ("東京都", "神奈川県") -- 検索データの結合 CONCAT(文字列1, 文字列2, ..) -- 文字列の連結 -- (例)フルネームを出力する場合(通常、カラム名はSQL文になる) SELECT CONCAT(family_name, first_name) FROM users -- (例)フルネームを出力する場合(カラムに別名を付ける場合) SELECT CONCAT(family_name, first_name) AS "名前" -- AS は省略可 FROM users -- 重複する行の除外 SELECT DISTINCT カラム名 -- (例)shiftsテーブルから、date = "2015-07-01"のカラム取得(重複するuser_idを除く) SELECT DISTINCT user_id FROM shifts WHERE date = "2015-07-01" -- レコードのグループ化 GROUP BY カラム名 -- (例)shiftsテーブルからdate="2015-07-01"に誰がシフトに入ったかを取得("user_id"が同じものをグループ化し、"user_id"表示) SELECT user_id FROM shifts WHERE date = "2015-07-01" GROUP BY user_id -- レコード数を数える SELECT COUNT(カラム名) SELECT COUNT(*) -- 全部の値がNULLのレコードも含めた行数を取得 -- (例)shiftsテーブルからdate="2015-07-01"に、誰が、何コマずつシフトに入ったかを取得(カラムにコマ数という別名を付ける) SELECT user_id, COUNT(*) "コマ数" FROM shifts WHERE date = "2015-07-01" GROUP BY user_id -- テーブルの結合(テーブル1にテーブル2を結合(条件:テーブル1のカラム1がテーブル2のカラム2と一致するもの)) FROM テーブル名1 JOIN テーブル名2 ON テーブル名1.カラム名1 = テーブル名2.カラム名2 -- (例)shiftsテーブルにusersテーブルを結合。(shiftsテーブルの"user_id"がusersテーブルの"id"と一致する行を結合) FROM shifts JOIN users ON shifts.user_id = users.id -- ↑は普通、簡略して書く!(テーブルに別名で扱う。テーブル名の頭文字が一般的。) FROM shifts s JOIN users u ON s.user_id = u.id -- まとめて書くと、 SELECT CONCAT(family_name, first_name) "名前", COUNT(*) "コマ数" FROM shifts s JOIN users u ON s.user_id = u.id WHERE date = "2015-07-01" GROUP BY user_id -- サブクエリ:検索結果を用いた検索(例えば、↑の検索に該当しないデータの取得とか) SELECT * FROM users WHERE id NOT IN ( SELECT DISTINCT user_id FROM shifts WHERE date = "2015-07-01" )IN演算子(カラム名に値1、もしくは、値2、...を含む場合true)-- (値1, 値2, ……)部分がリスト WHERE カラム名 IN (値1, 値2, ……) -- ↑と同義 WHERE カラム名 = "値1" OR カラム名 = "値2" OR ...
CONCAT
関数: 複数の文字列を連結する関数(※文字列にNULLがあれば、結果はNULL)。AS
句:CONCAT
関数での検索結果のカラム名は、SQL文のままで見にくいので、検索結果のカラム名の変更する時に使う。DISTINCT
キーワード: 指定カラムの値が重複する行を除外して取得。GROUP BY
句: 指定カラムが同じ値のデータをグループにまとめる(表示されないデータもそのグループに保持されてる)。
DISTINCT
と似てるが、GROUP BY
は、グループ単位で集計した結果を取得できることが利点。COUNT
関数: グループ化したデータに使える集計関数。カラムを指定して使用することで、値がNULLでない行数を取得できる。
GROUP BY
句を併用すると、各グループが持つレコードの数を取得できる (※ グループ化データに使える集計関数は、他に、平均AVG
、最大値MAX
、最小値MIN
など)。JOIN
句: 指定テーブルの、カラムが同じデータを結合。FROM句の後に書き、結合する対象テーブルを指定。JOIN
にはINNER JOIN
、LEFT JOIN
やRIGHT JOIN
など、結合法則の違うものがある。サブクエリ
: 検索結果を使って、別のSQL文を実行する仕組みのこと。WHERE
句で使うことが多いが、SELECT
句やFROM
句でも使える。DBとは
DB構成要素:3つ(サービスで扱う概念(エンティティ)、エンティティの属性、エンティティ間の関係性(リレーション))。
・エンティティ
: サービスで管理する必要のある概念(情報、データ)のこと。テーブルにあたる。(例: ユーザー、投稿内容、コメント)
・エンティティの属性
: エンティティが持つ個別情報のこと。カラムにあたる。(例: タイトル、説明、公開日、監督)
・リレーション
: エンティティ間の関係性のこと。(例:映画-を監督)属性の中には
キー
(テーブル内のレコード同士を識別するための被らないデータ)が存在する。 主キー 、 外部キー の2種類ある。
・主キー
: テーブル内のレコードを判別のための識別子カラム(多くの場合、id)。
・外部キー
: 関連する他のテーブルの主キー(カラム)と関係がある時に必要なカラム。他のテーブルのレコードとのリレーションを表すために使う。DB設計
- サービスに必要な情報の管理方法を決める作業。
- 【DB設計の手順】
- 必要な機能を洗い出す。(ページの繋がりを把握から考えてみる)
- 洗い出した機能に、管理したいエンティティ(必要なデータ)を抽出する。
- データの持つ属性を決める。テーブルの分け方も考える。
※ データ変更・削除しないテーブルは、active_hashかenumを使えないか?も考えてみる。- テーブルの繋がり(リレーション)を決める(ER図)。
ER図とは? : Entity-Relationship Diagramの略、テーブル間の関係を視覚的に表した図(IE表記法 で書く)。テーブル作成
モデル + マイグレーションファイルを作成
テーブル作成$ rails g model モデル名テーブル変更
マイグレーションファイルのみ作成。※ 名前は何でもok。
テーブル変更$ rails g migration マイグレーション名 $ rails g migration DropUser # カラム追加 $ rails g migration Addカラム名Toテーブル名 カラム名:型 $ rails g migration AddNameToUsers name:string # カラム削除 $ rails g migration Removeカラム名Fromテーブル名 カラム名:型 # カラム名変更 $ rails g migration RenameFrom変更前のカラム名To変更後のカラム名Onテーブル名 # カラムの Null制約変更 $ rails g migration ChangeColumnToNotNull # カラム変更 $ rails g migration ChangeColumnToUser # 外部キー削除 $ rails g migration AddArticleToUsers article:references # テーブル作成時に外部キーをreferences型カラムで保存 $ rails g model モデル名(大文字単数) カラム名:references $ rails g model UserAccount user:references # 外部キー作成 $ rails g model User uuid:string:unique name:stringマイグレーションの実行・確認・取下げ・編集・削除
マイグレーション実行$ rails db:migrate # rails4までは、rakeコマンドマイグレーションの確認$ rails db:migrate:status → up:実行済み、down:実行前の状態※ schema_migrationsテーブルにもマイグレーションファイルのバージョンが保存されている(version:××××××)。
※ スキーマファイル(schema.rb):最新のテーブル一覧が記録されている。実行済みのマイグレーション取下げ$ rails db:rollback up → downの状態になる。 # 新しいものから2つのファイルを同時にロールバック $ rails db:rollback STEP=2※ upの状態でマイグレーションファイル削除すると、エラーに繋がるので、down状態を確認してから削除!
マイグレーションをリセットして再実行(変更済みのマイグレーションを変更・削除するときとか。。)# DBを削除後に、db/migrate/**.rb を古い順から実行 % rails db:migrate:reset # マイグレーションファイルの順番に問題がある場合、NGになることがある。 # そんな時は、分けて実行すると行ける場合がある。 % rails db:reset # db/schema.rbを元にDB作成 % rails db:migrateマイグレーションファイルの編集(テーブル作成)def change create_table :hoges do |t| t.カラムの型 :カラム名 t.string :name t.text :text t.references :user, foreign_key: true t.timestamps end endマイグレーションファイルの編集(カラム追加)def change # カラム追加 add_column :テーブル名, :カラム名, :型 # カラム削除 remove_column :テーブル名, :カラム名, :型 remove_columns :テーブル名, :カラム名, :カラム名, :カラム名 # カラム名変更 rename_column :テーブル名, :変更前のカラム名, :変更後のカラム名 # カラムの制約、オプションを変更 change_column :テーブル名, :カラム名, :型 change_column :users, :name, :string, null: false change_column :users, :name, :string, limit: 10 change_column :users, :uuid, :string, null: false, default: 0 change_column :テーブル名, :カラム名, :型, null: true # NULL制約 change_column :テーブル名, :カラム名, :型, null: false # NOT NULL制約 change_column :テーブル名, :カラム名, :型, index: true # インデックス change_column :テーブル名, :カラム名, :型, default: "piyo" # デフォルト change_column :テーブル名, :カラム名, :string, limit: 12 # 長さ varchar(12) change_column :テーブル名, :カラム名, comment: "コメントです" # コメント # テーブル作成 create_table :テーブル名 drop_table :テーブル名 # テーブル名変更 rename_table :現在のテーブル名, :新しいテーブル名 # インデックスをつける add_index :テーブル名, :インデックスを付けるカラム名 [, オプション]) add_index :company, :company_name add_index :users, [:name, :name2] add_index :user_accounts, [:provider, :uid], :name => 'unique_provider_uid', :unique => true # インデックスを削除 remove_index :テーブル名, :カラム名 remove_index :company, name: :company_name_fk # 外部キーを追加 add_reference :テーブル名, :リファレンス名 [, オプション] add_reference :hoges, :user, foreign_key: true add_reference :テーブ名, :カラム名, index: true add_foreign_key :対象テーブル, :指定先テーブル, :column :対象のカラム名 add_foreign_key :テーブル名, :指定先のテーブル名 [, オプション] add_foreign_key :対象のテーブル, :指定先のテーブル, name: :foreign_keyの別名 add_foreign_key :companies, :accounts add_foreign_key :companies, :accounts, name: :huga_id # 別名をつける場合 add_foreign_key :trades, :users, column: :seller_id # seller_idカラム対して外部キー制約をつける場合 # 外部キーを削除 remove_foreign_key :テーブル名, :指定先のテーブル名 remove_foreign_key :users, column: :article_id # 外部キー削除 remove_reference :users, :article, foreign_key: true # カラムも一緒に削除する場合 end※ Rails5では、デフォルトでidは
bigint
で、参照元テーブルのカラムをintegerで作成するとforeign key
が作成できない。なので、テーブルのカラムをbigint
で作成して、外部キーを設定すると良さそう!!マイグレーションファイルでよく使うメソッド
メソッド名 概要 add_column カラム追加 remove_column カラム削除 remove_columns 複数カラムを削除 rename_column カラム名変更 change_column カラムの情報変更 create_table テーブル作成 drop_table テーブル削除 rename_table テーブル名変更 add_index インデックス追加 remove_index インデックス削除 add_reference 外部キー作成 remove_reference 外部キー削除 add_foreign_key 外部キー制約付与 remove_foreign_key 外部キー制約削除 カラムの型
カラム型 概要 string 文字(〜255文字) text 文字(255文字〜) integer 通常の整数 smallint 狭範囲の整数 bigint 大きな整数 float 小数点を含む数 decimal, numeric 大きな数 ※桁数などを指定する時 boolean 真/偽 time 時刻 12:00:00 date 日付 2020-01-01 datetime 日時 2001-01-01 01:01:00 timestamp タイムスタンプ json JSONで保存 binary 画像などをバイナリデータにしたい時 references 外部キー 制約
特定のデータを保存さないためのバリデーション。
制約の種類 記述 概要 NOT NULL制約 null: false カラムに設定する制約。NULL(空)では保存さない(エラーにする)。必須項目に使う。 一意性制約 unique: true カラムに設定する制約。テーブル内で重複データの保存を禁止する 主キー制約 - フォーマットで設定。主キーの属性値が存在し、かつ、重複しないことを保証する。 主キーへの NOT NULL制約 + 一意性制約
と同義。外部キー制約 foreign_key: true 外部キーに対応するレコードが存在することを保証する ※ 外部キーのカラム名を指定したい時
モデルbelongs_to :user, foreign_key: :seller_idインデックス
- DBの機能の1つで、テーブル内のデータの検索速度向上のための仕組み。索引(目次)のイメージ。
- 検索に使うカラムに使う。
- インデックスを貼るメリット: データの読み込み/取得速度向上
(牽引で目的の場所を見つけ、データを読み取るため)- インデックスを貼るデメリット: DBの容量が圧迫されるのと、書き込み速度低下
(インデックスは、テーブルのデータとは別の領域に保存されるので、データを書き込む時(保存/更新)、テーブルへの書き込み、インデックスへの書き込みが必要なため)- 複数カラムにインデックスを貼れるが、カラム単体で検索する時は、検索速度は向上しないので、その複数カラムのセットでめっちゃ検索する場合だけ、複数カラムにインデックスを貼る。
マイグレーションファイルdef change add_index :テーブル名, :カラム名 end正規化
- DBのデータ構造を無駄のないシンプルな構造にする手順。
- 正規化後のDB構造を
正規形
、正規化していないDB構造を非正規形
という。- 正規化順番: 非正規形 → 第1正規形 → 第2正規形 → 第3正規形 → ボイスコッド正規形 → 第4正規形 → 第5正規形。(第4正規形以降は、パフォーマンス低下の可能性があり、第三正規形に留めるのが一般的。)
- 正規化のデメリット: 正規化は進めるほどに、パフォーマンスが低下する。小さなテーブルが増えていくことが理由。関連テーブルの検索のたびにSQL実行回数が増え、速度低下するため、第3正規形で終えることが多い。
【テーブルの問題点】
・ 重複するカラム:冗長なDBになる。
・ 1つのテーブルに複数の情報(エンティティ)が混在:可読性を含め、パフォーマンスが低下。
この問題を解決するのが正規化
。正規化の順番
- 第1正規形: 同一カラムが存在しない、かつ、レコードを構成する1つのマスに、値が1つずつ入る状態にすること。
- 第3正規形: 第2、第3正規形は似ており、違いは複雑な概念のため、第2正規形は割愛。第3正規形は、主キーに依存、かつ、非キー属性(主キーでないもの)同士の依存がない状態にすること。
- 混在したエンティティは、属性ごとにテーブルを分ける。
N+1 問題
モデルでDBへアクセス時、SQLが発行される。アソシエーションで、子モデルのインスタンスを複数取得する時は、
N+1問題
を考慮し、includes
メソッドなどで解消する!モデルでアソシエーション定義
- 1 対 多:
has_many :複数
対belongs_to :単数
- 1 対 1:
belongs_to :単数
。所有(まるごと含む)場合は、has_one :単数
。- 多 対 多: 中間テーブルを作成。
has_many through
オプション↓↓多対多モデルhas_many :相手のテーブル名, through: :中間テーブル名 # 中間テーブル belongs_to :テーブル名1 belongs_to :テーブル名2 # アソシエーション定義すると、自動で以下のメソッドが使えるようになる。 # 通常のレコード作成 変数名1 = モデル名1.create(カラム名: "値", ..) # 単体のインスタンス生成 変数名2 = モデル名2.create(カラム名: "値", ..) # 多対多を定義したレコード作成 変数名1.相手テーブル名2(複数形).create(カラム名: "値", ..) # 多対多の関係を使って、変数名1に関係する相手テーブル名2のインスタンス生成 # リレーション追加 変数名1.相手テーブル名2(複数形) << 変数名2 # 後からインスタンス同士を関連付けも可能 # 変数名1と関係する相手テーブル名2の配列に、モデル名1のインスタンス追加でリレーション生成。 # リレーションを利用したレコード取得 変数名.相手テーブル名(複数形) # リレーションしている要素を全て出力(返り値は、関連付けられているインスタンスが出力される)
- 投稿日:2020-07-04T17:10:45+09:00
【Rails】FullCalendarを用いたスケジュール管理昨日の実装
目標
開発環境
・Ruby: 2.5.7
・Rails: 5.2.4
・Vagrant: 2.2.7
・VirtualBox: 6.1
・OS: macOS Catalina前提
下記実装済み。
・Slim導入
・Bootstrap3導入
・Font Awesome導入
・ログイン機能実装
・投稿機能実装実装
1.Gemを導入
Gemfile# 追記 gem 'jquery-rails' gem 'fullcalendar-rails' gem 'momentjs-rails'ターミナル$ bundle2.
application.scss
を編集application.scss*= require_tree . *= require_self *= require fullcalendar /*追記*/3.
application.js
を編集application.js//= require rails-ujs //= require activestorage //= require turbolinks //= require jquery //= require moment // 追記 //= require fullcalendar // 追記 //= require_tree .4.JavaScriptファイル作成・編集
ターミナル$ touch app/assets/javascripts/calendar.jscalendar.js$(function() { function eventCalendar() { return $('#calendar').fullCalendar({}); } function clearCalendar() { $('#calendar').html(''); } $('#calendar').fullCalendar({ events: '/events.json', titleFormat: 'YYYY年 M月', dayNamesShort: ['日', '月', '火', '水', '木', '金', '土'], header: { left: '', center: 'title', right: 'today prev,next', }, defaultTimedEventDuration: '03:00:00', buttonText: { prev: '前', next: '次', prevYear: '前年', nextYear: '翌年', today: '今日', month: '月', week: '週', day: '日', }, timeFormat: 'HH:mm', eventColor: '#63ceef', eventTextColor: '#000000', }); });【解説】
① turbolinks対策の為、カレンダーを読み込む関数と削除する関数をそれぞれ用意する。
function eventCalendar() { return $('#calendar').fullCalendar({}); } function clearCalendar() { $('#calendar').html(''); }② 日本語で表示する。
// カレンダー上部を年月で表示させる titleFormat: 'YYYY年 M月', // 曜日を日本語表示 dayNamesShort: ['日', '月', '火', '水', '木', '金', '土'],③ ボタンのレイアウトを整える。
header: { left: '', center: 'title', right: 'today prev,next', },④ イベントの設定をする。
// 終了時刻がないイベントの表示間隔を設定 defaultTimedEventDuration: '03:00:00', buttonText: { prev: '前', next: '次', prevYear: '前年', nextYear: '翌年', today: '今日', month: '月', week: '週', day: '日', }, // イベントの時間表示を24時間にする timeFormat: 'HH:mm', // イベントの色を変える eventColor: '#63ceef', // イベントの文字色を変える eventTextColor: '#000000',4.モデルを作成
ターミナル$ rails g model Event user_id:integer title:string body:text start_date:datetime end_date:datetime~_create_events.rbclass CreateEvents < ActiveRecord::Migration[5.2] def change create_table :events do |t| t.integer :user_id t.string :title t.text :body t.datetime :start_date t.datetime :end_date t.timestamps end end endターミナル$ rails db:migrate5.コントローラーを作成
ターミナル$ rails g controller events new show edit my_calendarevents_controller.rbclass EventsController < ApplicationController before_action :set_event, only: [:show, :edit, :update, :destroy] def new @event = Event.new end def create @event = Event.new(event_params) @event.user_id = current_user.id @event.save ? (redirect_to event_path(@event)) : (render 'new') end def index @events = Event.where(user_id: current_user.id) end def show end def edit unless @event.user == current_user redirect_to root_path end end def update @event.update(event_params) ? (redirect_to event_path(@event)) : (render 'edit') end def destroy @event.destroy redirect_to my_calendar_path end def my_calendar end private def set_event @event = Event.find(params[:id]) end def event_params params.require(:event).permit(:user_id, :title, :body, :start_date, :end_date) end end6.ルーティングを追加
routes.rb# 追記 resources :events get 'my_calendar', to: 'events#my_calendar'7.
json.jbuilder
ファイルを作成・編集index.json.jbuilderjson.array!(@events) do |event| json.extract! event, :id, :title, :body json.start event.start_date json.end event.end_date json.url event_url(event) end【解説】
①
index
アクションで抽出したレコードを繰り返し処理し、配列を作成する。json.array! @category_children do |children|② 各データを
①
で作成した配列に格納する。json.extract! event, :id, :title, :body json.start event.start_date json.end event.end_date json.url event_url(event)8.ビューを編集
new.html.slim
を編集① HTMLを作成する。
new.html.slim.row .col-xs-3 .col-xs-6 = form_with model: @event, url: events_path, local: true do |f| = f.label :title, 'スケジュール名' br = f.text_field :title, class: 'form-control' br = f.label :body, 'スケジュール内容' br = f.text_area :body, class: 'form-control' br = f.label :start_date, '開始日時' br = f.datetime_select :start_date, {}, class: 'form-control datetime-form' br br = f.label :end_date, '終了日時' br = f.datetime_select :end_date, {}, class: 'form-control datetime-form' br br = f.submit '新規登録', class: 'btn btn-success btn-block' .col-xs-3② CSSでデザインを整える。
application.scss// 追記 .datetime-form { display: inline-block; width: auto; }
show.html.slim
を編集show.html.slim.row .page-header h3 | スケジュール管理 .row #calendar br .row table.table.table-bordered tbody tr th.active | スケジュール名 td = @event.title tr th.active | スケジュール内容 td = @event.body tr th.active | 開始日時 td = @event.start_date.to_s(:datetime_jp) tr th.active | 終了日時 td = @event.end_date.to_s(:datetime_jp) .row - if @event.user === current_user .pull-right = link_to 'スケジュール登録', new_event_path, class: 'btn btn-success' = link_to '編集', edit_event_path(@event), class: 'btn btn-primary' = link_to '削除', event_path(@event), method: :delete, data: { confirm: '本当に削除しますか?' }, class: 'btn btn-danger'
edit.html.slim
を編集edit.html.slim.row .col-xs-3 .col-xs-6 = form_with model: @event, url: event_path(@event), local: true do |f| = f.label :title, 'スケジュール名' br = f.text_field :title, class: 'form-control' br = f.label :body, 'スケジュール内容' br = f.text_area :body, class: 'form-control' br = f.label :start_date, '開始日時' br = f.datetime_select :start_date, {}, class: 'form-control datetime-form' br br = f.label :end_date, '終了日時' br = f.datetime_select :end_date, {}, class: 'form-control datetime-form' br br = f.submit '保存', class: 'btn btn-primary btn-block' .col-xs-3
my_calendar.html.slim
を編集my_calendar.html.slim.row .page-header h3 | マイカレンダー .row = link_to 'スケジュール登録', new_event_path, class: 'btn btn-success' #calendar br注意
turbolinks
を無効化しないとカレンダーが表示されない事があるので、必ず無効化しておきましょう。
- 投稿日:2020-07-04T16:57:20+09:00
Rails migration
Railsのmigrationを触るのは初めてだったので、勉強備忘録のために簡単に書きとどめてみる。
migrationについて
マイグレーションは、データベーススキーマの継続的な変更 を、統一的かつ簡単に行なうための便利な手法。
migrationファイルを作成する
サンプルとしてCompanyテーブルを準備
カラム名 データ型 id integer name string description text created_at datetime updated_at datetime $ rails g migration CrateCompanyActive Recordにはマイグレーションの実行順序をファイル名のタイムスタンプでの表示を自動でやってくれる。
作成されたファイルはdb/migrate
ディレクトリに保存される。作成されたファイル
class CrateCompany < ActiveRecord::Migration[5.2] def change end endActiveRecord::Migration[]にはバージョンが入る。
chengeの代わりにupとdownを使うことができる。
upメソッドにはmigrationする時の内容、downメソッドにはrollbackする時の内容を書く。class CrateCompany < ActiveRecord::Migration[5.2] def up end def down end end変更を加える
マイグレートファイルを作成し
rails db:migrate
を打てば実行してくる。
また直前の変更を取り消すためにはrails db:rollback
テーブルを作成する
class CrateCompany < ActiveRecord::Migration[5.2] def change create_table :companies do |t| t.string :name t.text :description t.timestamps end end endこのコマンドを打てば自動で上記のようなもの作成してくれる
$ rails g migration CrateCompany name:string description:textテーブルを変更する
class ChangeTableCompany < ActiveRecord::Migration[5.2] def change change_table :companies do |t| t.remove :name t.string :root_number t.index :root_number t.rename :description, :description_note end end end上のマイグレーションではnameカラムが削除され、stringカラムであるroot_numberが作成されてインデックスがそこに追加されます。そして最後にdescriptionカラムをリネームしている。
カラムを変更する
change_column :companies, :root_number, :textモデル名の
root_number
のカラムをtext
に変更。ここでの注意点だが、
change_column
はchange
メソッドではロールバックすることができない。
change
メソッドではサポートさていないとのこと。
change
でサポートされているマイグレーション定義はadd_column
add_foreign_key
add_index
add_reference
add_timestamps
change_column_default (:fromと:toの指定は省略できない)
change_column_null
create_join_table
create_table
disable_extension
drop_join_table
drop_table (ブロックを渡さなければならない)
enable_extension
remove_column(型を指定しなければならない)
remove_foreign_key(2番目のテーブルを指定しなければならない)
remove_index
remove_reference
remove_timestamps
rename_column
rename_index
rename_tableもしロールバックする場合はup,downメソッドであれば
change_column
を使用可能。class ChangeColumnCompany < ActiveRecord::Migration[5.2] def up change_column :companies, :root_number, :text end def down change_column :companies, :root_number, :string end endまだまだmigrationについては知らないことばかりだが、身につけていきたい。
- 投稿日:2020-07-04T16:23:46+09:00
Your Ruby version is 2.4.6, but your Gemfile specified 2.6.4
マイグレーションファイルを作成しようとしたら、RubyのバージョンがGemfileで指定しているものと違うと言われ、実行できなかった。
$ bin/rails g migration hoge Your Ruby version is 2.4.6, but your Gemfile specified 2.6.4rbenvがインストールされていることを確認。
$ which rbenv /usr/local/bin/rbenv指定されたRubyのバージョンをインストール。
$ rbenv install 2.6.4 Downloading openssl-1.1.1g.tar.gz... -> https://dqw8nmjcqpjn7.cloudfront.net/ddb04774f1e32f0c49751e21b67216ac87852ceb056b75209af2443400636d46 Installing openssl-1.1.1g... Installed openssl-1.1.1g to /Users/tamu/.rbenv/versions/2.6.4 Downloading ruby-2.6.4.tar.bz2... -> https://cache.ruby-lang.org/pub/ruby/2.6/ruby-2.6.4.tar.bz2 Installing ruby-2.6.4... ruby-build: using readline from homebrew Installed ruby-2.6.4 to /Users/tamu/.rbenv/versions/2.6.4インストールされたことを確認。
$ rbenv versions system * 2.4.6 (set by /Users/tamu/.rbenv/version) 2.6.4今回は、特定のディレクトリ配下にのみ適用したかったので、以下のように指定。
$ rbenv local 2.6.4切り替わったことを確認。
$ ruby -v ruby 2.6.4p104 (2019-08-28 revision 67798) [x86_64-darwin18]
- 投稿日:2020-07-04T15:56:30+09:00
migrateファイルの変更(rails db:rollback)
こんにちは、tt_tsutsumiです。
今回はmigrateファイルの変更方法についてです。
こちらの記事が何方かのお役に立てると嬉しいです。アプリケーション作成中にカラムの変更や追加を行った時に
普通のコードの書き方と違いが生まれたので困惑しました。
その際に自分が行った工程と流れの記載を致します。1. rails db:migrate:status
まずは現在作成しているアプリケーションのmigrateの稼働状況の確認を行いましょう。
コンソールにて下記コードを入力します。$ rails db:migrate:status Status Migration ID Migration Name -------------------------------------------------- up year/month/day Devise create users up year/month/day Create spots※ year/month/dayの部分はご自身で作成された日付等が表示されます
上記でstatusの部分がupになっている場合、migrateファイルは稼働しております。
この際にmigrateファイルの変更や追加を行っても反映されません。2. rails db:rollback
migrateファイルの稼働状況をdownします。
$ rails db:rollback $ rails db:migrate:status Status Migration ID Migration Name -------------------------------------------------- up year/month/day Devise create users down year/month/day Create spotsここで重要なのは rails db:rollback です!!
このコードを打つことにより稼働しているmigateファイルをdownさせる事ができます。※ 注意点としてこちらのコードはファイルを1つずつしかdownに出来ません。
3. rails db:migrate
downになっているのを確認してからmigrateファイルの変更や消去を行って下さい。
そして変更が終わったら保存をし、rails db:migrate を行います。$ rails db:migrate $ rails db:migrate:status Status Migration ID Migration Name -------------------------------------------------- up year/month/day Devise create users up year/month/day Create spotsこれでmigrateファイルの変更と保存は終了です。
ご覧いただきありがとうございました !!
- 投稿日:2020-07-04T15:51:13+09:00
Ruby on Rails 非同期通信(ajax)についての振り返り
カリキュラム学習・ポートフォリオ作成中、この機能を実装するのに苦戦したので、メモ。
非同期通信にしたかったのは以下の二点。前提条件
テーブル
schema.rbcreate_table "users", force: :cascade do |t| (略) end create_table "products", force: :cascade do |t| (略) end create_table "favorites", force: :cascade do |t| t.integer "user_id", null: false t.integer "product_id", null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false endモデル(アソシエーション)
favorite.rbbelongs_to :user belongs_to :product def favorited_by?(user) Favorite.where(user_id: user.id).exists? enduser.rbhas_many :favorites, dependent: :destroyproduct.rbhas_many :favorites, dependent: :destroy def favorited_by?(user) Favorite.where(user_id: user.id).exists? endルート
rutes.rbresources :products do resource :favorites, only: [:create, :destroy] end実装①(商品のお気に入り追加・削除)
productsコントローラは以下の通り
products_controller.rbdef show @product = Product.find(params[:id]) end次にお気に入りボタンをパーシャル化。(userディレクトリ内に作成)
views/users/products/_favorite_button.html.erb<% if product.favorites.where(user_id: current_user.id).exists? %> <%= link_to "お気に入りから削除", users_product_favorites_path(product_id: product.id), method: :delete, remote: true %> <% else %> <%= link_to "お気に入りに追加", users_product_favorites_path(product_id: product.id), method: :post, remote: true %> <% end %>ここで
remote: true
を付けることで、非同期通信が可能になる。products/show.html.erbで呼び出し
views/users/products/show.html.erb<div id="favorites_buttons_<%= @product.id %>"> <%= render 'users/products/favorite_button', product: @product %> </div>次はfavoritesのコントローラ。
fovorites_controller.rbdef create @product = Product.find(params[:product_id]) favorite = current_user.favorites.new(product_id: @product.id) favorite.save end def destroy @product = Product.find(params[:product_id]) @favorites = current_user.favorites favorite = current_user.favorites.find_by(product_id: @product.id) favorite.destroy endここでリダイレクト先を指定しなければ、JSの処理を探しに行ってくれる。
最後に作成・削除した場合のJS処理を作成する。views/users/favorites/create.js.erb$("#favorites_buttons_<%= @product.id %>").html("<%= j(render 'users/products/favorite_button', product: @product) %>");views/users/favorites/destroy.js.erb$("#favorites_buttons_<%= @product.id %>").html("<%= j(render 'users/products/favorite_button', product: @product) %>");show.html.erb内の、idで設定した範囲のみが、処理後書き換えられるようになる。
実装②(お気に入り一覧から削除)
fovoritesのコントローラは以下の通り作成
favorites_controller.rbdef index @favorites = current_user.favorites end非同期通信したい範囲をパーシャル化。
view/users/favorites/_form.html.erb<% if favorites.present? %> <table class="table"> <thead> <tr> <th colspan="3">商品</th> </tr> </thead> <tbody> <% favorites.each do |f| %> <tr> <td> <%= attachment_image_tag f.product, :image, :fill, 80, 80, format: 'jpeg', fallback: "no_image.jpg", size: '80x80' %> </td> <td><%= link_to f.product.name, users_product_path(f.product.id) %></td> <td><%= link_to "お気に入りから削除", users_product_favorites_path(product_id: f.product.id), method: :delete, remote: true %></td> </tr> <% end %> </tbody> </table> <% else %> <h3> お気に入りリストがありません。<br> 商品ページから追加してみましょう </h3> <% end %>fovorites/index.html.erbで呼び出し
<div class="row"> <div class="col-sm-6 offset-3" id="favorites_index"> <%= render 'users/favorites/form', favorites: @favorites %> </div> </div>JSの処理に以下の1行を追加
views/users/favorites/destroy.js.erb$("#favorites_index").html("<%= j(render 'users/favorites/form', favorites: @favorites) %>");これでお気に入り一覧にも非同期機能が実装できた。
実装した上での反省点・感想
- JSに記載しているインスタンス変数はそれぞれcreate・destroyメソッドから確認しているため、そっちでも定義してあげないといけないということが身にしみた。(考えてみれば当たり前だったが)
- 実装①についてはググればよく出てくるが、②は見かけないので自力で 解けて感動した。ただ流れを理解していれば迷うとこでも無かったなと感じた。
- 投稿日:2020-07-04T14:33:24+09:00
RSpec実行時の便利なオプションまとめ
- 投稿日:2020-07-04T14:00:59+09:00
JWTを用いたTokenAPIの実装(Rails)
JWTとは
JSON Web Tokenの略称です。
今回はJWTを用いたTokenAPIをRailsに実装するのが目的なのでJWTについては簡単な説明だけにします。
TokenAPIは単純にemail,passwordでログインチェックを行って該当ユーザーが存在する時にそのユーザーのTokenを返すAPIです。
詳細内容については以下のリンクを参考にしてください。
https://openid-foundation-japan.github.io/draft-ietf-oauth-json-web-token-11.ja.htmlJWT構成
ヘッダ、ペイロード、署名の3つのパートになっててそれぞれBase64でエンコードされている。
ヘッダ{ "typ":"JWT", "alg":"HS256" }ペイロード{ "sub": "1234567890", "iss": "John Doe", "aud": "audience", "exp": 1353604926 }署名HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)3つのパートは . (ドット) で結合されている。
ヘッダ・ペイロード・証明を簡単に作ってみたいなら以下のリンクでできます。
https://jwt.io/#debuggerJWTeyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ1c2VyIiwic3ViIjoidGVzdCIsImF1ZCI6ImF1ZGllbmNlIiwicGFyYW1zIjoie1wiZW1haWxcIjogXCJ0ZXN0QGdtYWlsLmNvbVwiLCBcInBhc3N3b3JkXCI6IFwicGFzc3dvcmRcIn0iLCJleHAiOiIxNTkzODM2ODA2In0.4fC4yLEmYTjiwaXk3R_AUUPEQSuI_ARmkoMqosWEJ-cJWTクレーム
JWT クレームセットは JSON オブジェクトであり, それぞれのメンバは JWT として送られるクレームである. JWT クレームセット内のクレーム名は一意でなければならない
今回は以下の4つのクレームを使います。"iss" (Issuer) クレーム:JWTの発行者の識別子
"sub" (Subject) クレーム:JWTの主語となる主体の識別子
"aud" (Audience) クレーム:JWTを利用することが想定された主体の識別子一覧
"exp" (Expiration Time):JWTの有効期限APIプロジェクト生成
console$ rails new jwt --apiGemfileにjwt追加
gem 'jwt'Jwt認証チェックモジュール作成
app/controllers/concerns/signature.rbmodule Signature extend ActiveSupport::Concern def verify_signature render status: 401, json: { message: '認証エラー'} if request.headers['jwt-signature'].blank? request_params = JSON.parse(request.body.read) @signature ||= JwtSignature.new(jwt: request.headers['jwt-signature']) @signature.verify!(params: request_params) rescue JwtSignature::InvalidSignature render status: 401, json: { message: '認証エラー'} end endJwt認証処理モデル作成
app/models/jwt_signature.rbclass JwtSignature class InvalidSignature < StandardError; end ALGORITHM = 'HS256' ISSUER = 'user' AUDIENCE = 'audience' SUB = "test" TOKEN_TYPE = 'JWT' # SECRET_KEYは重要なので環境ごとに定義して安全に管理してください〜 SECRET_KEY = '1gCi6S9oaleH22KWaXyXZAQccBx4lUQi' def initialize(jwt:) @jwt = jwt end def verify!(params:) raise InvalidSignature unless valid_payload? && valid_params?(params: params) && valid_header? end private def valid_payload? return false unless jwt_payload['iss'] == ISSUER return false unless jwt_payload['sub'] == SUB return false unless jwt_payload['aud'] == AUDIENCE true end def valid_header? return false unless jwt_header['alg'] == ALGORITHM return false unless jwt_header['typ'] == TOKEN_TYPE true end def valid_params?(params:) JSON.parse(jwt_payload['params']) == params end def jwt_header @jwt_header ||= decoded_jwt.second end def jwt_payload @jwt_payload ||= decoded_jwt.first end def decoded_jwt @decoded_jwt ||= JWT.decode(@jwt, SECRET_KEY, true, algorithm: ALGORITHM) rescue JWT::DecodeError raise InvalidSignature end endJWT.decodeはHashのArrayを返すので
decoded_jwt.firstでPayload、decoded_jwt.secondでHeaderを取得
User Table作成
app/models/user.rbが生成される
$ rails g model User name:string email:string token:string expired_at:datetime $ rails db:migrateroutes.rbにAPI追加
routes.rbRails.application.routes.draw do post 'tokens/create' endToken Controller作成
tokens_controller.rbclass TokensController < ActionController::API include Signature # createの時だけ認証を確認する before_action :verify_signature, only: %i(create) def create # 今回は認証確認処理だけ実装 user = User.find_by(email: params[:email], password: params[:password]) return render status: 400, json: { message: 'ユーザーが存在しません。' } unless user # Tokenが存在しない場合は更新 if user.token.blank? user.token = SecureRandom.uuid user.save end render status: 200, json: { name: user.name, email: user.email, token: user.token } end endテストユーザー登録
$rails c irb(main):001:0> User.new(name: 'test', email: 'test@gmail.com', password: 'password') (0.5ms) SELECT sqlite_version(*) => #<User id: nil, name: "test", email: "test@gmail.com", password: [FILTERED], token: nil, expired_at: nil, created_at: nil, updated_at: nil> irb(main):002:0> User.new(name: 'test', email: 'test@gmail.com', password: 'password').save (0.1ms) begin transaction User Create (0.9ms) INSERT INTO "users" ("name", "email", "password", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?) [["name", "test"], ["email", "test@gmail.com"], ["password", "password"], ["created_at", "2020-07-04 02:30:17.701626"], ["updated_at", "2020-07-04 02:30:17.701626"]] (0.8ms) commit transaction => trueJwtEncodedデータ生成
https://jwt.io/#debugger
上記のリンクからJwtEncodedデータを生成
Payload部分は自分が必要なJwtクレームとパラメータを追加
paramsにemail, passwordを追加してAPIのパラメーターのJsonとPayloadのJsonを比較している
VERIFY SIGNATUREのSecurityキーも自分のSecurityに変更(JwtSignatureモデルのSECRET_KEY使用)
満期時間を設定したい場合はexpにUnixtimeを設定unixtime(現在時間から5分後)irb(main):040:0> (Time.now + 300).to_i => 1593838401Postmanで実行してみる
Headerに「jwt-signature」を追加してJwtEncodedデータを追加
JwtEncodedデータeyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ1c2VyIiwic3ViIjoidGVzdCIsImF1ZCI6ImF1ZGllbmNlIiwicGFyYW1zIjoie1wiZW1haWxcIjogXCJ0ZXN0QGdtYWlsLmNvbVwiLCBcInBhc3N3b3JkXCI6IFwicGFzc3dvcmRcIn0iLCJleHAiOiIxNTkzODM4NDAxIn0.dSNqdhHBJKUJHnJa_2sS_3Qr4oNNdr5MKFx5ufwqLv4Bodyにjson形式でemail, password追加
json{ "email": "test@gmail.com", "password": "password"}実行
同じ状態で5分後実行
- 投稿日:2020-07-04T12:57:35+09:00
【Rails】データベースのテーブルやカラムを確認する
schema.rbを見る
db/schema.rb
を見ると視覚的にテーブルとカラム構造を確認できる。db/schema.rbActiveRecord::Schema.define(version: 2020_07_04_034908) do enable_extension "plpgsql" create_table "tasks", force: :cascade do |t| t.string "name" t.text "description" t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false end create_table "users", force: :cascade do |t| t.string "uuid" t.string "name" t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false end endschema.rbとは
schema.rb
はRailsによって自動的に作られるファイルで、現在のデータベースの状態が書かれている。
データベースに変更を加えた時、すなわちrails db:migrate
やrails db:rollback
をした時に自動的に書き変えられる。
- 投稿日:2020-07-04T10:41:30+09:00
【Rails】jquery を使わずに javascript で flashメッセージをフェードアウトさせる
はじめに
Railsアプリを開発していてflashメッセージをフェードアウトさせたく、javascriptを用いて実装したので書く。
こういったやつ↓↓(Bootstrapのalertを適用させています)
これをフェードアウトさせる
環境
Ruby: 2.5.1
Rails: 5.2.4.2実装
実装イメージとしては徐々に透明度を高くしていき、最終的に非表示にします。
要素のstyle属性
①opacityの値を減少させていく
②opacityが0になったらdisplay: none; にするviewファイル
erbはこのようになっている
_flash_messages.html.erb<% flash.each do |message_type, message| %> <div class="alert alert-<%= message_type %>"><%= message %></div> <% end %>jsファイル
flash_message.js// turbolinks:loadでページ読み込み時に実行 document.addEventListener('turbolinks:load', () => { // flashメッセージ要素を取得 const flashMessage = document.querySelector('.alert'); // フェードアウトさせる(徐々に透過し,非表示にする)関数を定義 const fadeOutFlashMessage = () => { // setIntervalを特定するために返り値を変数timer_idに格納 const timer_id = setInterval(() => { // flashメッセージのstyle属性 opacityを取得 const opacity = flashMessage.style.opacity; if (opacity > 0) { // opacityの値が0より大きければ0.02ずつ値を減少させる flashMessage.style.opacity = opacity - 0.02; } else { // opacityの値が0になったら非表示に flashMessage.style.display = 'none'; // setIntervalをストップさせる clearInterval(timer_id); }; }, 50); // 今回は0.05秒ごとにsetIntervalを実行 } // flashメッセージがある場合のみ実行 if (flashMessage !== null) { // style属性opacityをセット flashMessage.style.opacity = 1; // 今回は表示から3秒後に上記で定義したフェードアウトさせる関数を実行 setTimeout(fadeOutFlashMessage, 3000); }; });参考文献
下記記事を参考にさせていただきました。
ありがとうございました。・turbolinksチートシート
・【JavaScript】Railsのflashをフェードアウトして消す方法最後に
読んでいただきありがとうございます。
間違いや、より効率的な記述方法等あればご指摘いただけると非常に嬉しいです。
- 投稿日:2020-07-04T10:15:07+09:00
初心者がRails内のVueでライブラリを使わずに簡単なページネーションを作ってみた
Railsアプリ内でVueを使用し、SPAを実装しました。
ページネーション機能が欲しかったので、簡単に自分で実装してみました。
ライブラリを使うのが基本的だと思いますが、今回は練習したかったということでライブラリは使いませんでした。プログラミング初心者かつVue自体も初めてなので間違いもだいぶあると思います。見つけたら指摘していただいたら嬉しいです。
version
Rails 6.0.3.2
ruby 2.6.3
@vue/cli 4.4.1モデル
diary.rbclass Diary < ApplicationRecord # 省略 endコントローラ
diaries.rbdef index diaries = Diary.all render json: diaries endコンポーネントのscript部分
今回は1ページ(1つのページネーションごと)に6つの要素を表示させることにしました。
表示分の要素をマウントできるようにする
まずコントローラから全ての要素を取得し、diariesとします。
のちの繰り返し構文で6つ表示させたいのでsliceメソッドを使い、diariesから6つの要素をとり、present_diariesとします。
sliceメソッドは第一引数+1番目の要素から第2引数番目の要素までを返します。diaries.vue<script> import axios from 'axios' export default { data: function() { diaries: [], present_diaries: [] }, mounted() { let that = this; axios.get('アクションへのパス').then(function(response) { that.diaries = response.data; }).then(function() { if (that.diaries.length > 6) { that.present_diaries = that.diaries.slice(0, 6); // 6番目の要素まで取得 } else { that.present_diaries = that.diaries } }) } } </script>ページャーのカウントを算出
ページャーのカウントをいくつ出すかはcomputedで算出しました。
6つずつ出したいので取り出した要素を6で割ります。
要素が6つだったらページャーは1が表示されるようにします。
要素が7つなど、割り切れずに小数点が出た場合は切り上げてページャーのカウントを一つ増やします。
つまり、要素が7つだったらページャーのカウントは1と2が表示されるようにします。
ページャーの最大表示数は10としました。diaries.vue<script> // 省略 // computedでページャーのカウントをいくつ出すか求めます compouted: { pageCount() { let count = this.diaries.length / 6 // 6つずつ表示したいので6で割ります let page_count = Math.ceil(count) // ceilで切り上げます if (page_count > 10) { // 10以上なら10を返します return 10 } else { return page_count } } } } </script>選択されたページャーの番号によって要素が変わるようにする
ページャーの番号が押されたら、このイベントハンドラーが発動するとします。
何番が押されたかはテンプレートの方から渡し、引数countとします。
sliceメソッドを使い、引数(渡された数)に合わせて要素を取得します。
例えば、テンプレートから2が渡されたら、7 ~ 12の要素をとり、present_diariesに入れるようにします。diaries.vue<script> // 省略 methods: { changeDiaries(count) { // 引数に合わせて要素を取得 this.present_diaries = this.diaries.slice(count * 6 - 6, count * 6) } } </script>テンプレートで要素を表示する
v-forディレクティブで表示したい要素を複数表示します。
present_diariesをv-forに渡すことで、全ての要素ではなく、表示したい要素(ここでは6つ)のみを表示するようにします。diaries.vue<template> <div> <div v-for='diary in present_diaries' v-bind:key='diary.id'> {{diary}} </div> </div> </template>ページャーを表示する
computedで算出された数分の繰り返し処理を行います。3が算出されたら1,2,3と画面に表示されます。
また、v-onディレクティブでクリックに反応するようにし、先ほどのイベントハンドラを呼びます。
引数にそのボタンの数を与えます。diaries.vue<template> <div> // 省略 <div v-for='count in pageCount'> <input type='button' v-bind:value='count' v-on:click='changeDiaries(count)'> <div> </div> </template>あとはcssなどで整えてください。
こんな感じで簡単なものは一応形になりました。
- 投稿日:2020-07-04T03:16:30+09:00
【Nuxt/Rails】axiosとdevise_token_authを使ってPOSTした実装
Nuxt.jsとRuby on Railsでaxiosとdevise_token_authを利用してPOSTする時に、地味に詰まってしまったので備忘録がてらまとめます。
Ruby on Rails側の実装
devise_token_authの設定諸々は省きます。
以下のURLあたりが参考になりましたので、そちらをご覧いただけると良いかもしれません。
https://github.com/lynndylanhurley/devise_token_auth
https://qiita.com/Masahiro_T/items/6bc49a625b437a7c2f45
https://sainu.hatenablog.jp/entry/2018/08/11/194319FormObjectを導入してるので、そちらも込みで載せます。
articles_controller.rbclass ArticlesController < ApplicationController def create @article = ArticleForm.new(article_params) if @article.save # 省略 end end private def article_params json_request = ActionController::Parameters.new(JSON.parse(request.body.read)) json_request.permit( :title, :description, ).merge(user_id: current_user.id) end endarticles_form.rbclass ArticleForm include ActiveModel::Model attr_reader :title, :description validates :title, presence: true, length: { maximum: 50 } validates :description, length: { maximum: 300 } def initialize(article_params) @article = article_params end def save return false if valid? Article.create(@article) true end endこれで、POSTするまでの準備が整いました。
補足
POSTされる時にJsonをごにょごにょすることになったので、JsonをParseする処理を書いてあげる必要がありました。(ここに凄くハマりました。)
def article_params json_request = ActionController::Parameters.new(JSON.parse(request.body.read)) json_request.permit( :title, :description, ).merge(user_id: current_user.id) end以下の記事に救われた(というかJsonのParse部分はコピペ)ので載せておきます。
Nuxt.js側の実装
Componentに書いた実装は省いて、Vuexの中でやってるaxiosでPOSTした部分の実装だけ切り取ります。
article.jsexport const actions = { async postArticle({ dispatch }, article) { await this.$axios .post('/articles', article, { headers: { 'Content-Type': 'multipart/form-data', // サムネ画像を送信する想定のため 'access-token': this.state.user.userToken.accessToken, client: this.state.user.userToken.client, uid: this.state.user.userToken.uid, }, }) .then(() => dispatch('getArticleList')) .catch((error) => console.log(error)) }, }devise_token_authのtokenをCookieに詰めて、nuxtServerInit時にVuexに書き出す実装をしていて、リクエスト時にヘッダーにTokenを突っ込めるようにしました。
ちなみに、Ruby on Railsではありませんが、GOのechoを利用した時に似たような実装をしていて、その備忘録を以下にまとめています。
- 投稿日:2020-07-04T01:30:15+09:00
【開発ログ⑰】サイドメニューの表示と非表示
前提について
はじめまして、 プログラミングスクールに通っていたいりふねと申します。この記事は、スクールの課題である個人アプリの開発の記録を書くことで、自身のアウトプットに利用しています。もし、読んでいただけた方がいましたら、フィードバックをしていただけたら嬉しいです。
開発するのは「有給休暇管理ツール」です。仕様は過去記事をどうぞ。
アプリはデプロイまで行いますが、サービスとして提供するものではありません。あくまでも自学習の一環ですので、ご理解下さい。では本題へどうぞ。
今回の実施内容
作成中のアプリのビューのうち、サイドメニュー部分の表示と非表示を切り替えたいと言うのが、今回の内容になります。というのもこの個人アプリを見た方から「iPadのようなモバイル端末でも見られるようにしないと企業の管理者としては使いづらいのでは?」というフィードバックを頂いたためです。たしかに。。。
具体的には以下の手順で実装しました。
- application.html.hamlを編集
- サイドメニューのクラス名を変更
- サイドメニューのCSSを変更
- サイドメニューの表示非表示の切り替えボタンを設置
- 表示と非表示を切り替えるjsファイルの作成
開発環境
- Rails 5.2.4.3
- ビューは、HamlとSCSSで作成
- jQueryが使用できる状態
- Herokuを使用してデプロイ済み
デモ
今回は、「jQuery sidebar」というサイドバーを設置するためのプラグインを使用します。まずは、どんな動きをするのか以下の画像でご確認下さい。実際に触ってみたい方は、参考記事jQuery Sidebar 3.3.2からご確認いただけます。
・・・おわかりいただけただろうか。
サイドメニューと言っても、上や下からもメニューを呼び出せるようです。今回実装したのは、左から出てくるメニューのみですが、記述を少し変えれば色々と使えそう。
参考記事
今回もいくつか参考記事を使用しました。
【jQuery】簡単に設置できる!横から出てくるサイドバーを実装できる「jQuery Sidebar」
【公式】jQuery Sidebar 3.3.2
【GitHub】jillix/jQuery-sidebarapplication.html.hamlを編集
まずは、application.html.hamlに「jQuery Sidebar」プラグインを読み込ませる記述を追加していきます。4と5行目のscriptタグが追記箇所になります。
app/views/layput/application.html.haml%head %meta{:content => "text/html; charset=UTF-8", "http-equiv": "Content-Type"}/ %title Backyard %script{src: "//code.jquery.com/jquery-1.10.2.min.js" } %script{src: "//jillix.github.io/jQuery-sidebar/js/handlers.js" }scriptタグのsrc属性がスラッシュから記述されている点に違和感を感じると思います。こちらの引用元は先程のGitHub内の「demo.html」となります。
元の記述では、スラッシュの前に「http:」が付いた状態でしたが、この状態ではどうしてもデプロイした際にプラグインが読み込まれないという状態になりました。これについては、こちらの参考記事を読ませていただきました。herokuに上げたらニコニコ動画が取ってこれなくなった〜httpsは魔物でござるの巻〜
サイドメニューのクラス名を変更
今回サイドメニューは、部分テンプレートで切り出しています。このビューファイルの先頭部分に記入している一番の親要素にあたるdivタグのクラス名を「sidebar left」に変更します。
_side.html.haml.sidebar.left ←作成中のサイドメニューの一番大きなdivタグを変更 .sidebar__lists ←クラス名をBEMで書いている方は2行目以降もsidebarに書き換え - current_user.branches.each do |branch| .list .list__top 以下省略念のため、検証ツールで確認しておきます。以下のようなクラス名になっていればOKです。
今回は、左から出現するサイドメニューを想定していますが、それ以外の場合は、「left」部分を「right」「top」「bottom」のいずれかに変更すればOKだと思います(未検証)。
サイドメニューのCSSを変更
サイドメニューを左側に固定するためにCSSの記述を変更します。今回は、必要箇所のみ紹介していますが、アプリに合わせてサイドメニューのCSSを適宜記述して下さい。
_side.scss.sidebar{ position: fixed; } .sidebar.left { top: 0; left: 0; bottom: 0; }この段階でビューを確認します。サイドメニューが左に固定され、メイン部分のビューが下に回り込んでいればOKです。
なお、上、右、下からメニューを表示させたい場合はCSSの記述が異なってくるので、ご注意下さい。参考記事【jQuery】簡単に設置できる!横から出てくるサイドバーを実装できる「jQuery Sidebar」内にその他の場合のCSSの記述が掲載されています。リスト6と書かれたコード見本部分になります。
サイドメニューの表示非表示の切り替えボタンを設置
jQueryによって表示と非表示を切り替えますが、イベント発火させるためのボタンを設置します。カスタムデータ属性の記述がhamlは、少し特殊になります。
_mainheader.html.haml.nav__ragistation = link_to "#", {class: "btn link", data:{action:'toggle', side:'left'}} do メニューまた、今回は、サイドバーを出現させるとメインヘッダー部分のボタンが隠れてしまうので、サイドメニュー本体にも表示の切り替えボタンを設置します。全く同じ記述内容です。
_side.html.haml.sidebar__closed = link_to "#", {class: "btn", data:{action:'toggle', side:'left'}} do メニューを閉じる検証ツールで正しく属性が設定されているか確認します。
以下のように確認できればOKです。hamlではなく、htmlで記載する場合のカスタムデータ属性はそのまま「data-action」や「data-side」と入力すれば良いようです。こちらの記述もまた、上や右、下からメニューを表示させたい場合は、data-side属性がleft以外の値(right、top、bottom)になるので注意が必要です。
表示と非表示を切り替えるjsファイルの作成
app/asset/javascriptsディレクトリ内に新規で、「sidebar.js」というファイルを作成します。ファイル名はお好みで決めて大丈夫だと思います。jsファイルの中身は、GitHubのjsファイルの記述をまるごとコピーします。
app/asset/javascripts/sidebar.js長いので、具体的な記述はカットします。 リンク内の149行全てをコピー&ペーストすればOKです。動作確認
まとめ
文字量が多くなりましたが、手順としてはそれほど難しくなかったように感じます。最終的にjsファイルを丸ごとコピーというのも下品だったと思うので、今後は内容の理解に勤めたいと思います。