- 投稿日:2019-10-09T23:52:45+09:00
【50日目】コメントの削除機能、多対多のアソシエーションの設定
コメントの削除機能
destroyアクションの作成
コメントコントローラーにdestroyアクションを記載していきます。
comments_controller.rbdef destroy comment = Comment.find(params[:id]) comment.delete redirect_to comment.board, flash: {notice: 'コメントが削除されました' } endコメント削除ボタンの作成
コントローラーでアクションを定義したら、viewを作成します。
link_toの第二引数に渡すパスを調べるために、http://localhost:3000/rails/info/routesを確認しますと、
deleteアクションのパスはcomment_pathとなっています。
よって下記のようにリンクを作成します。_comments.html.erb<span><%= link_to '削除', comment, method: :delete, data: { confirm: '削除してもよろしいでしょうか?'} %></span> # 「link_to 'リンク名', リンク先へのURL又はパス, method: :メソッド名」で指定したメソッドの指定したパスに飛べる'リンク'を作成します。 # methodオプションを指定しない場合、第二引数のパスはGETメソッドのものを参照します。 # data-confirm属性を設定すると、リンクを踏んだ際に確認のダイアログボックスを表示させます。編集機能にもエラーメッセージを表示する。
まず前提として、updateアクションには下記のとおりフィルタリングされています
boards_controller.rbbefore_action :set_target_board, only: %i[show edit update destroy] # 中略 private def set_target_board @board = Board.find(params[:id]) endこれによって、@boardには該当のidのパラメータが渡されております。
その上でupdateアクションを下記のようにします。
boards_controller.rbdef update if @board.update(board_params) flash[:notice] = "「#{@board.title}」の掲示板を編集しました。" redirect_to @board else redirect_to edit_board_path, flash: { board: @board, error_messages: @board.errors.full_messages } end endこれによって、パラメータが保存できた場合は編集したことを示すメッセージが表示されて、掲示板のshowにリダイレクトします。
バリデーションエラーでboardが保存できなかった場合は、編集画面にリダイレクトしてエラーメッセージのリストを表示します。
又この時、リダイレクト後も掲示板のフォームには編集時に入力したパラメータは保持されます。多対多のリレーションの考え方
例えば掲示板にタグをつけることを考えます。
ここでは簡略化するため、各テーブルはidとnameのカラムのみを持ちます。boardsテーブル
id name 1 1日目 2 2日目 3 3日目 tagsテーブル
id name 1 ruby 2 SQL 3 初心者 このとき、例えば
1日目はruby, 初心者
2日目はSQL
3日目はruby, SQL, 初心者
とタグ付けしたかったりした場合、この関係を表そうとすると下記のようになります。
id board tag tag tag 1 1日目 ruby 初心者 2 2日目 SQL 3 3日目 ruby SQL 初心者 でもこれではカラムの数が事前に定められない他、カラムに空白が生まれてしまいます。
そこで「中間テーブル」を用いて、各id同士を繋げて表します。
board_id tag_id 1 1 1 3 2 2 3 1 3 2 3 3 このように表現することで、例えば
board_id = 1 である「1日目」には、
tag_id = 1 である「ruby」と
tag_id = 2 である「初心者」がタグづけされていることがわかります。では以下でこのアソシエーションをどのように実装するかを見ていきます。
多対多アソシエーションの実装
まず最初に中間テーブルとtagsテーブルのモデルを作成します。
テーブルの作成
tagsテーブル
docker-compose exec web bundle exec rails g model tag name:string中間テーブル
docker-compose exec web bundle exec rails g model board_tag_relation board:references tag:referencestagがnullであることを
db/migrate/xxxxxx_create_tags.rbt.string :name, null: false書き換えたら、マイグレーションを実行します。
docker-compose exec web bundle exec rake db:migrateモデルのアソシエーションの設定
中間モデルの確認
中間モデルは最初からboardモデルとtagモデルにbelongs_toによってassociateされています。
これによって中間モデルはboard_idとtag_idをカラムに持つことになり、上記で示したような2つのidを結びつけることが可能になります。app/model/board_tag_relations.rbclass BoardTagRelation < ApplicationRecord belongs_to :board belongs_to :tag endtagモデルの修正
タグから掲示板を関連づける設定をする。
tag.rbclass Tag < ApplicationRecord has_many :board_tag_relations has_many :boards, through: :board_tag_relations # throughオプションによって中間テーブルを経由して複数のboardを持つことを示している。 endboardsモデルの修正
掲示板からタグを関連づける設定をする。
board.rbclass Board < ApplicationRecord has_many :comments has_many :tag_board_relations has_many :tag, through: :tag_board_relations validates :name, presence: true, length: { maximum: 10 } validates :title, presence: true, length: { maximum: 30 } validates :body, presence: true, length: { maximum: 1000 } end次回はアソシエーションのdependentオプションを作成していきます。
- 投稿日:2019-10-09T21:47:22+09:00
Rails datetime_selectのエラー対応
datetime_selectエラーがでた
new.html.erb<div>新規プロジェクト</div> <%= form_for(@project) do |f| %> <div><%= f.label :title %> <div><%= f.text_field :title %></div> <div><%= f.label :state %></div> <div><%= f.text_field :state %></div> <div><%= f.label :limit_date %></div> <div><%= f.datetime_select :released_at, start_year: 2000, end_year: Time.current.year + 1, use_month_numbers: true %></div> <div><%= f.submit %></div> <% end %>上記のnewのviewを実装後にlocalhost:3000/projects/newをリクエストしたら下記のエラーが出た。
ターミナルActionView::Template::Error (undefined method `map' for "translation missing: ja.date.order":String Did you mean? tap): 5: <div><%= f.label :state %></div> 6: <div><%= f.text_field :state %></div> 7: <div><%= f.label :limit_date %></div> 8: <div><%= f.datetime_select :released_at, 9: start_year: 2000, end_year: Time.current.year + 1, 10: use_month_numbers: true %></div> 11: <div><%= f.submit %></div>解決策
ja.ymlを作成すると解決する
config/locales/ja.ymlja: date: order: - :year - :month - :dayもしくはi18nを無効化しても良いということが調べたら載ってました。
参考: https://jangajan.com/blog/2014/09/12/perfect-rails-i18n/
- 投稿日:2019-10-09T21:34:20+09:00
A server is already running の対処法
Railsでアプリを作成中にa server is already runningが出てlocalサーバが起動しなくなってしまった。
already runningってことはサーバが閉じられてないってこと?
調べてみると
Railsプロジェクト/tmp/pids/server.pidのserver.pidを削除して直るらしい。server.pidはサーバを起動毎に作成されているようなのでサーバが閉じている時にserver.pidファイルがpidsファイルの中にあるのがそもそもおかしいみたい。
server.pidを削除して新たにサーバを立ち上げ直し。
server.pidファイルが新たに作られる。
解決!
- 投稿日:2019-10-09T20:21:39+09:00
routingでtypoしてるとrspecでActionController::UrlGenerationErrorがでる件
概要
routingでtypoしていると、railsは無視して解釈してアクセスできるようにしてくれますが、rspecはそのコントローラーのテストでエラーを返します。
例えば
config/routes.rbresources :examples, excpet: %i[new update destroy]exceptを
excpet
のように書く。こんな感じで解決される。
ちなみにrails routesはこんな感じになる
examples GET /examples(.:format) examples#index {:excpet=>[:new, :update, :destroy]} POST /examples(.:format) examples#create {:excpet=>[:new, :update, :destroy]} new_example GET /examples/new(.:format) examples#new {:excpet=>[:new, :update, :destroy]} edit_example GET /examples/:id/edit(.:format) examples#edit {:excpet=>[:new, :update, :destroy]} example GET /examples/:id(.:format) examples#show {:excpet=>[:new, :update, :destroy]} PATCH /examples/:id(.:format) examples#update {:excpet=>[:new, :update, :destroy]} PUT /examples/:id(.:format) examples#update {:excpet=>[:new, :update, :destroy]} DELETE /examples/:id(.:format) examples#destroy {:excpet=>[:new, :update, :destroy]}routingではtypoは解釈されず、resourcesは全て解釈される。
そしてcontrollerを作りアクセスすると
examples_controller.rbclass ExamplesController < ApplicationController def index end{"method":"GET","path":"/examples","format":"html","controller":"ExamplesController","action":"i ndex","status":200,"duration":4429.18,"view":4345.9,"db":3.32,"backtrace":null,"host":"localhost ","message":"[200] GET /terms_of_uses (TermsOfUsesController#index)"}そして普通にアクセスできてしまう。
しかしこの状態でrspecのcontroller_specを作成して実行すると
require "rails_helper" RSpec.describe ExamplesController, type: :controller do describe "GET #index" do it "returns a 200 status code" do get :index expect(response.status).to eq(200) end end endspec/controller/exmaples_spec.rb1) AnnouncementsController GET #index responds successfully with an HTTP 200 status code Failure/Error: get :index ActionController::UrlGenerationError: No route matches {:action=>"index", :controller=>"announcements"} # /usr/local/bundle/gems/devise-4.7.1/lib/devise/test/controller_helpers.rb:35:in `block in process' # /usr/local/bundle/gems/devise-4.7.1/lib/devise/test/controller_helpers.rb:102:in `catch' # /usr/local/bundle/gems/devise-4.7.1/lib/devise/test/controller_helpers.rb:102:in `_catch_warden' # /usr/local/bundle/gems/devise-4.7.1/lib/devise/test/controller_helpers.rb:35:in `process' # ./spec/controllers/youtube_channel_platform_histories_controller_spec.rb:58:in `block (3 levels) in <top (required)>'このように解釈してくれない。
結論
typoするやつ許すまじ(ブーメラン)
- 投稿日:2019-10-09T20:18:13+09:00
何をやってもRails Serverが切れなかった
何をやってもRails Serverが切れなかった話
いつも通りrails severを立ち上げると
=> Booting Puma => Rails 5.2.3 application starting in development => Run `rails server -h` for more startup options Exitingと表示される。
とりあえずkillコマンドをググる
定番のコマンドを試しまくってもダメ...
ターミナルを停止して立ち上げ直したり、エディターを再起動してもダメ...(killコマンド一覧)
lsof -wni tcp:3000
ps ax | grep puma + kill -9 PID
ps ax | grep rails + kill -9 PID
kill -9 $(lsof -i tcp:3000 -t)
結果
ただのRoute設定ミスでした...
よくエラー文を見てみると下にplease check your routes の文が書かれていました。
=> Booting Puma => Rails 5.2.3 application starting in development => Run `rails server -h` for more startup options Exiting (中略) in `check_part': Missing :controller key on routes definition, please check your routes. (ArgumentError)まさかRouteの設定ミスでもserverがExitingと表示されるとは思わなかった。
長いエラー文でもしっかり見ないといけないなと勉強になりました。色々試してダメな方はエラー文見直すかルーティング確認するといいかもしれません。
- 投稿日:2019-10-09T19:21:03+09:00
メモ(form・パスワード・redirectとrenderの違い)
form helperについて
form_forとform_tagの二種類ある
特定のモデルを編集したいときform_for
編集の前提となるモデルが存在しないときform_tagを使う(検索フォーム)DBに入る要素を限定した場合、データの検証は行わなくても良いのか?
パスワード関連の開発
gemfileにbcryptの記載・bundle install
+
password_digestモデルの設置
↓
has_secure_passwordが使用可能に
→password_digestに暗号化したパスワードを保存できるように
→仮想属性passwordと、password_confirmation が使えるように。この二つが一致しているか?を検証するためのバリデーションも追加
→authenticateメソッドが使用可能に。(authenticateは上記のpasswordとpassword_confirmationで入力されたパスワードが、password_digest一致しているとUserオブジェクトを渡す、間違っているとfalse)redirectとrenderの違い
redirectは呼び出すViewのみ指定
redirect_toはHTTPリクエストを送る(基本はGET)
- 投稿日:2019-10-09T19:00:46+09:00
RailsのmigrateをAWS Fargateに移行した時の話
前提
- ECSでRailsアプリが起動されていること
- migrateはfargateでやってなかった
- テスト環境でやること
作業内容
https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/userguide/ecs_run_task_fargate.html
これ見ながら作業する。実際は、コマンドでやると何をしているのかよくわからないので、まずはGUIでやる。把握した後にコード化するというのが良い。そうすることで、全てのオプションやらパラメータの世界が見えてくる。念の為、新規DBを作成
t2.microなどで良い
何らかのエラーが出たときの切り分けにしたい。
インスタンスが作れたら、アプリケーションデプロイ用のタスクにDBの環境変数を設定する(System Managerが使えるなら ECS containerからvalueFrom
で呼び出せるのでjson自体をgit管理できる。AWSでマネージドされているのが良い)taskを定義する
実際は、コマンドなどを付与したりするのでENTRYPOINTで設定している。
これは、Initial,Migrate,Seedそれぞれリビジョンで分けるだけで良い。Initial,Seed,Migrateの順で作る。
※なお、実際の運用ではInitialやらSeedは使わない。これは、今回新規でRDSを立ち上げたので必要になっただけ。LATESTリビジョンだけがあれば良い。
key value ネットワークモード awsvpc 互換性が必要 FARGATE タスクメモリ(GB) 0.5GB タスクCPU(vCPU) 0.25 vCPU containerの環境設定(Initial)
key value エントリポイント bundle,exec,rails,db:create
作業ディレクトリ /app
(アプリがある場所)"containerDefinitions": [ { "entryPoint": [ "bundle", "exec", "rails", "db:create" ], "workingDirectory": "/app" } ]containerの環境設定(Migrate)
key value エントリポイント bundle,exec,rails,db:migrate
作業ディレクトリ /app
(アプリがある場所)"containerDefinitions": [ { "entryPoint": [ "bundle", "exec", "rails", "db:migrate" ], "workingDirectory": "/app" } ]containerの環境設定(Seed)
key value エントリポイント bundle,exec,rails,db:seed,a=b
作業ディレクトリ /app
(アプリがある場所)"containerDefinitions": [ { "entryPoint": [ "bundle", "exec", "rails", "db:seed", "a=b" ], "workingDirectory": "/app" } ]taskを実行する
- VPCを設定、subnetを設定
- ネットに繋がるものを設定しておく
2つエラーが発生した
- メモリ不足エラー → メトリクスをチェックして、スペックの問題ならスペックを上げる
- コンテナインスタンスのリソースから、メモリ使用量を見る
- ECRからイメージをpullしてこれず、エラー → インターネットにつながるネットワークを設定する
結果を見る
- PROVISIONING→PENDING→RUNNING→STOPのようなフローだったような気がする
- タスクのStoppedの中でログを確認する。
- Migrateタスクならば、以下のようなログが吐かれているだろう。
2019-10-09 18:16:45D, [2019-10-09T09:16:45.290941 #1] DEBUG -- : [1m[35m (572.8ms)[0m [1m[35mCREATE DATABASE "test_database" ENCODING = 'unicode'[0m 2019-10-09 18:16:45Created database 'test_database'
- 実際にアプリケーション側でも確認する。
- 問題がなければCodeDeployやCIなどで連携する。
- 投稿日:2019-10-09T18:24:49+09:00
Railsチュートリアル 第5章<復習>
第5章の復習メモです。
個人的に重要と思ったことを書きます。
調べたことや、知っていたことも含めて書きます。Bootstrap
Twitterが作成したフロントエンドのフレームワーク。
デザインが予め用意されているので、いい感じのデザインを素早く実装できる。railsで使うには、Gemfileに
bootstrap-sass
を追加する。source 'https://rubygems.org' gem 'rails', '5.1.6' gem 'bootstrap-sass', '3.3.7' # ←追加 . . .追加後、
$ bundle install
を忘れずに実行する。また、Bootstrapを読み込ませるため、
CSSファイルには以下を追記する。~.scss@import "bootstrap-sprockets"; @import "bootstrap";補足
railsでは、generate controller
した時、
ビューと一緒に、対応するCSSファイルが作られる。
今回は、簡略化のため、全てのCSSを一つにまとめた物として、
app/assets/stylesheets/custom.scss
を作成した。
(カスタムCSSというらしい)部分テンプレート
ビューファイルのコードの冗長化を防ぐため、
共通する部分を、別ファイルに記載できる。
これを部分テンプレートと呼ぶ。ファイル名は、
app/views/layouts/_<任意の名前>.html.erb
の形式にする必要がある。呼び出す際は、renderメソッドを用いて、
<%= render 'layouts/<任意の名前>' %>
と記載する。<例>
部分テンプレートapp/views/layouts/_shim.html.erb<!--[if lt IE 9]> <script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/r29/html5.min.js"> </script> <![endif]-->呼び出す側
app/views/layouts/application.html.erb<!DOCTYPE html> <html> <head> <title><%= full_title(yield(:title)) %></title> <%= csrf_meta_tags %> <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %> <%= render 'layouts/shim' %> # ←ここで呼び出している </head> ・ ・以下も参考にさせていただきました。
https://qiita.com/taca10/items/dd4a0eae6864e2bdbf3aアセットパイプライン、Sass
アセットパイプライン
以下を参考にさせていただき、学習しました。
https://qiita.com/hogehoge1234/items/9a94ebc93c5f937502cdSass
CSSの強化版(?)で、
- ネスト
- 変数の使用
ができるみたい。書き方等、詳細は省略。
リンクの貼り方
ビューファイルでリンクを貼る場合、link_toメソッドを使う。
<%= link_to "文字列", "URL" %>ルーティングの変更、名前付きルート
ルーティングの変更
generate controller
コマンドでコントローラ等を作成すると、
ルーティングが自動生成される。config/routes.rbRails.application.routes.draw do get 'static_pages/about' endこの場合、
ルート/static_pages
にGETリクエストが来ると、
static_pagesコントローラの、aboutアクションにルーティングされる。
即ち、コントローラ名とアクション名に対して、URLが固定されている。これを、以下のように書き換えることで、URLを自由に決めることができる。
config/routes.rbRails.application.routes.draw do get '/about', to: 'static_pages#about' end上の例では、
ルート/about
にGETリクエストが来ると、
static_pagesコントローラの、aboutアクションにルーティングされる。名前付きルート
railsでは、
config/routes.rb
に記載したURLに対して、
ルート以降の相対パス、ルートを含めた絶対パスを表す変数が
それぞれ用意され、格納される。
<例>config/routes.rbRails.application.routes.draw do get 'static_pages/about' endの場合
- 相対パス('/static_pages/about') : 変数
static_pages_about_path
- 絶対パス('http://www.~.com/static_pages/about') : 変数
static_pages_about_url
ルーティングを変更すると、変数名も更新される。
<例>config/routes.rbRails.application.routes.draw do get '/about', to: 'static_pages#about' endの場合
- 相対パス('/about') : 変数
about_path
- 絶対パス('http://www.~.com/about') : 変数
about_url
統合テスト
テストについては、以下を参考にさせていただき、学習中です。
https://qiita.com/duka/items/2d724ea2226984cb544f復習記事を書いてみると、理解不足がとても実感できます・・・
- 投稿日:2019-10-09T16:35:05+09:00
Railsのバリデーションで特定の値が入って欲しくない場合
はじめに
3回目の投稿です。
分からないことだらけで勉強中なので、アウトプットの練習にQiitaで投稿しています。
何か間違いなどあれば教えてください。Validation機能
エンドユーザーから入力された値は、まず「正しくないこと」を前提に、アプリは実装されるべきです。善意であるか悪意であるかに関わらず、ユーザーとは間違える生き物であるからです。
(中略)もっとも、このような検証機能を一から実装するのは、なかなか面倒なことです。
しかし、Active ModelのValidation機能を利用することで、(たとえば)必須検証や文字列検証、正規表現検証のように、アプリでよく利用するような検証処理をシンプルなコードで実装できるようになります。format
format
は、Active Modelで利用できる検証機能のうちの一つで、「正規表現パターンに合致しているか否か」を検証できるものです。with
VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i #メールアドレスフォーマットの検証(完璧な正規表現ではない) validates :email, format: { with: VALID_EMAIL_REGEX }「正規表現パターンに合致しているか」を検証する
without
validates :email, format: { without: <入ってほしくない値の正規表現> }「
without
以下の正規表現パターンに合致していないこと」を検証するおわりに
without
の方はRailsのリファレンスに載っていませんでした。フォームのバリデーションで、特定の文字をはじく機会があったので存在を知りました。
最初はwith
に正規表現の^
を使っていたのですが上手くいかず、without
を使うことでイメージ通りの実装が出来ました。参考
- 投稿日:2019-10-09T16:17:10+09:00
【Rails5】ドロップダウンリストの実装(コントローラー編)メルカリクローンサイトの作成
背景
カリキュラムの過程でご存知メルカリのクローンサイトを作りました。
その際に、ヘッダーメニューにあるドロップダウンリスト(ハンバーガーメニュー?)を実装したのでその記録です。DB構造など
今回、メルカリのサイトをクローンするにあたり、カテゴリーは、単一テーブル継承にすることで実装しています。
例えば、以下のような構造をしています。
id category parent_id 1 レディース 2 メンズ 13 トップス 1 14 ジャケット 2 115 Tシャツ 13 116 ダウンジャケット 14 この例だと、レディース>トップス>Tシャツ、 メンズ>ジャケット>ダウンジャケットという階層になります。
ちなみにビューで値を呼び出すときは、@category.childrenや@category.parentで子要素や親要素を呼び出すことが可能。むむっ、カテゴリーの数多くない??
さて、実装に入りましょう。
色々な記事を参考にさせていただくと、ドロップダウンはCSSだけでいけることが判明。(後日別記事書きます)
じゃぁhtmlでカテゴリー名を全部入れればOKでしょ?いや、待て待て。
親、子、孫合わせて
1,325!!うん、多いね。
クラス名とかも一緒に書くから全部書くと4,000行の大作になっちゃうじゃない。
スマートじゃないね。
これ地道にやるならプログラミングやってる意味ないじゃん。DBにデータはあるんだし、なんとかそこから引っ張ってこれないんか??
面倒だし繰り返し処理しちゃおうぜ
じゃぁ該当するカテゴリー名を変数に入れてrenderをeachさせればいいんじゃね?
まずは親カテゴリ。
親カテゴリはDBから呼び出さずにビューに記載。そして子カテゴリの呼び出し(@category_children1)views/product/_header.html.haml%ul.header__menu-box--left__parents %li.header__menu-box--left__parents__parent = link_to "レディース", category_path(id: 1) %ul.header__menu-box--left__children = render partial: "header_child", collection: @category_children1, as: "category"続いて子カテゴリ。そして孫カテゴリーの呼び出し(category.children)
views/product/_header_child.html.haml%li.header__menu-box--left__children__child = link_to "#{category.category}", category_path(id: category.id) %ul.header__menu-box--left__grand-children = render partial: "header_grand-child", collection: category.children, as: "children"さらに孫カテゴリ
views/product/_header_grand-child.html.haml%li.header__menu-box--left__grand-children__grand-child = link_to "#{children.category}", category_path(id: children)また、コントローラー側ではこのような処理がされています。
products_controller.rbdef set_categories @categories = Category.where(params[:id]) @category_children1 = Category.where(parent_id: 1) @category_children2 = Category.where(parent_id: 2) @category_children3 = Category.where(parent_id: 3) @category_children4 = Category.where(parent_id: 4) @category_children5 = Category.where(parent_id: 5) @category_children6 = Category.where(parent_id: 6) @category_children7 = Category.where(parent_id: 7) @category_children8 = Category.where(parent_id: 8) @category_children9 = Category.where(parent_id: 9) @category_children10 = Category.where(parent_id: 10) @category_children11 = Category.where(parent_id: 11) @category_children12 = Category.where(parent_id: 12) @category_children13 = Category.where(parent_id: 13) endよし、早速動かしてみよう!!!
おやおや、、、
確かに動くんだけど、リンクをクリックしたらエラーが。。。
読み込むテンプレートないじゃん!って怒られている
部分テンプレートはちゃんとあるし、Missingな訳ないんだけどな
あれ、よーく見ると、ビューを読み込もうとしてるのってcategory_controllerじゃん
product_controllerじゃないのね。。。そうか、部分テンプレートが同じcategoryフォルダにないから読めないのね。
次のように変更しましょ。= render partial: "products/header_child"こうすれば違うフォルダの部分テンプレートも読めるようになるね。
じゃ更新して、、、
あれれ? あれれ?
今度は子と孫のカテゴリーが出なくなったぞ。。。ページごとで読み込むコントローラーが違う
ページごとに読み込むコントローラーが違うため、それぞれのコントローラーで変数の設定が必要になるのか。。。
全部のコントローラーに上のあれを書くのは流石に可読性が悪くなるし、
DRYの法則に逆らうねだるいわ〜
なんとか共通化したりできないの??
共通のメソッドにしてしまう
ありました、ちゃんとそういう機能。
参考記事
Ruby on RailsでConcernsを利用してControllerの処理を共通化する早速以下のように書きます。
controllers/concerns/common_actions.rbmodule CommonActions extend ActiveSupport::Concern def set_categories @categories = Category.where(params[:id]) @category_children1 = Category.where(parent_id: 1) @category_children2 = Category.where(parent_id: 2) #中略 @category_children12 = Category.where(parent_id: 12) @category_children13 = Category.where(parent_id: 13) end end必要なコントローラー.rbinclude CommonActions before_action :set_categories, only: [必要なアクション] #最初に読み込む必要があるため、最初に追記します。でもこれ、コントローラー全部に書き込むのもどうかと思うのでこうしてみました。
application_controller.rbclass ApplicationController < ActionController::Base include CommonActions before_action :set_categories #中略 endこうすることで、全部のコントローラーに記述する必要がなくなりました!!
少しはいいの作れたかな〜
もっといい方法があればご教授くださいまし。
- 投稿日:2019-10-09T13:21:54+09:00
【個人開発】至高の技術選定
はじめまして、ダンと申します。
日々情報収集をしている中で、
「エンジニアとしての市場価値を高めるために重要となる技術」だったり、
「最先端のweb系企業で使われている注目の技術」に関する情報が多く、
「良いプロダクトを速く作る技術」を求めている私向きの情報ではないと感じることが多くなってきました。個人開発や、小規模プロジェクトの際に利用すると良い技術について、現在の雑感をまとめていこうと思います。
前提
本来、技術選定は作りたいサービスが明確にあってこそのものです。
本記事は「webアプリの個人開発や小規模プロジェクトで力を発揮しやすい技術は何か」という基準で書いていきます。私はいわゆるサービス志向エンジニアで、技術そのものよりも「その技術を使って何ができるか」の方に強い関心があります。
また技術力は他のみなさんと比較するとかなり低いです。
プログラミング歴はもうすぐ2年になりますが、本格的な実務経験はありません。
PHPで書いたサイトをレンタルサーバーに公開して1年半ほど運用したり、コーディングやWordPress関係の仕事をいくつか受けたことがある程度です。
その他、いろんな技術のチュートリアル的なものにちょこちょこ手を出してます。
あと、最近AWS SAAを取得しました。今後は自分で本格的なWebサービスを作って、販売して行きたいと考えていて、技術情報はある程度追っている、といった状態です。
前置きが長くなりました。
以下、私が積極的に使っていこうと思っている技術になります。HTML、CSS
HTML、CSSが書けなければ始まりません。
CSSは直接書くのではなく、Sassで書いてコンパイルする方が良いです。すごく書きやすくなります。
はじめてSassを使った時は感動しました。学習コストもかなり低いので、使わない手はありません。また、CSSフレームワークはBootstrapを使うことが多いです。
定番で情報量も多いですし、これといった不満もないので。ついでに名前も好きです。Javascript
Javascriptの基本的な文法に関しても、必ずどこかのタイミングで必要になってくるので、学習する必要があります。
フレームワークに関しては、jQueryを使うか、ReactやVueなどを使うかの選択があります。
この点については色々と考えましたが、現状ではjQueryを使うのが良いのではないかと考えています。
理由としては以下の通りです。SPAに興味が持てない
SPAのメリットとして、サクサク動く、表現が豊かになるなどがあげられますが、実装できる機能自体が大幅に増えることはなさそうだと感じています。
表示速度は通信技術の発達に伴ってどんどん上がっていくでしょうし、表現に関してもHTMLの進化で改善されていく気がします。(限界があるとは思いますが)
また、サーバーサイドレンダリング(SSR)をしっかりやらないと、SEOが弱くなってしまうとの話を聞いたことがあります。
普通のSEOを実現するために、工数を増やすのはあまり気持ちの良いことではありません。
しかもSSRはなかなか学習コストが高いみたいです。学習コスト
前述のSSRの件もそうですが、全体的にjQueryの方がサクッと使える感じがします。
React等のメリットとして「複雑なコードをわかりやすい形で書ける」というのがあるようですが、それが特に力を発揮するのは人数の多いプロジェクトのような気がしています。
逆に、シンプルな動きをつける時のコードを見比べると、jQueryの方がスッキリしている気がしました。情報量、ライブラリの数
歴史が違う分、情報量やライブラリの数ではjQueryが圧倒しています。
React等の情報はこれからどんどん増えていくと思われますが、React、Vue、Angularと3つの人気フレームワークが乱立している状況では、増え方もこれまでのjQueryと比較すると緩やかなのかな、と思っています。ネイティブアプリについて
Reactが書けるようになると、ReactNativeというフレームワークを使って、スマホ用のネイティブアプリが比較的簡単に(元のコードを活かしながら)作れるというメリットがあります。
これについて、個人的にスマホのネイティブアプリにそこまで興味がないので、それだけのためにReactを使うのは違うな、と思っています。
あとスマホのネイティブアプリについてはあんまり調べてないので、よくわかってません。
webviewがどんどん進化してるらしいですね。Ruby on Rails
サーバーサイドはやはりRuby on Railsかなって結論です。
そもそもRails自体が少人数のスタートアップでのプロダクト開発用に作られたフレームワークらしいです。
疎結合を犠牲に、速さと綺麗さの両立を追求して作られたとのこと。最近はすごい勢いで人気が落ちているらしいですが、言語自体の問題というより
- 他のモダンな技術の登場
- 大規模サービスのRails離れ(Railsは大規模サービスにはあまり向いていないらしい)
- Railsに対する飽き
などから、「イケてる感」がなくなってきたことが原因なんじゃないかと個人的に推測しています。Ruby on Railsは、ライブラリや情報量、学習コストなど、どこを取っても小規模プロジェクトにはもってこいの技術なんじゃないかと思っています。
ちなみに、Node.jsを使ってサーバーサイドもフロントエンドもJavaScriptを使うと良いのでは?って考えもありましたが、別にSPAを作りたい訳ではないので、JavaScriptをゴリゴリ書くことにこだわる必要もないと思ってやめました。
大半のロジックはRuby on Railsで書いて、必要な時にだけJavaScript及びjQueryを使うのが良いのではないかと思っています。
Heroku
インフラはもうHerokuかなと思ってます。インフラに工数をかけたくないです。多少割高になっても、パフォーマンスが落ちても、コスト、リスク、時間とかを総合的に考えたらHerokuだろうと。
個人的にインフラ周りの勉強が好きじゃないので、EC2とかで自前のインフラを構築することが必須になるサービスはそもそもやりたくないと思ってます。Lambda、SQSなど
実際にほとんど使ったことがないのですが、LambdaとかSQS、SES、CloudFront、S3のような便利な外部サービスは積極的に活用したいと思っています。
AWS SAAを取得して1ヶ月ということもあり、AWSのサービスばかり挙げてしまいましたが、AWS以外のサービスでも便利なものはどんどん活用していきたいです。
どんなサービスがあって、何ができるのかをある程度頭に入れるようにして、必要に応じて勉強していきます。必要かどうかわからないこと
Docker
Docker、使うのが常識になっているみたいですけど、どうなんですかね。
ドットインストールのチュートリアルを半年くらい前にやったっきりなので、Dockerに対する理解がかなり浅いです。
- 複数人で開発する時に、開発環境のセットアップが楽になる
- 開発環境と本番環境の差分を減らすことができる
というあたりはざっくりと理解しているのですが、
- Herokuを使う場合にも利用するのが一般的なのか
- Gitのように、開発環境をそのまま丸ごと本番環境にデプロイできたりしないのか
- アプリケーションとDBは別のコンテナにするもの?判断の基準は?
などなど、「何言ってんだこいつw」と思われてしまうかもしれないレベルの疑問がたくさんあります。
1つでもわかる方がいらっしゃいましたら教えてください!CD/CI
HerokuとGithubを連携させて、Githubの更新を検知して自動でデプロイする、という動きは超簡単に作れます。
CircleCIやGithubActionsを使うと、何が嬉しいのでしょうか。
テストというものを書いたことがないため、その辺りを学習するとわかるのかもしれません。勉強します。終わりに
以上が、個人開発の技術選定に関する現状の考えです。
少しでも違和感を感じた箇所があれば、どんどん指摘してください。
ちょっとした感想も、否定的なコメントも大歓迎です。
みなさんのご意見、感想を受けて理解を深めていきたいと考えています。
よろしくお願いいたします!参考
- 投稿日:2019-10-09T12:16:49+09:00
Rails6 のちょい足しな新機能を試す92(config.disable_sandbox 編)
はじめに
Rails 6 に追加された新機能を試す第92段。 今回は、
config.disable_sandbox
編です。
Rails 6 では、rails console --sandbox
を実行したときに、エラーメッセージを表示して終了するオプションconfig.disable_sandbox
が追加されました。
本番環境などで、rails console --sandbox
したときにトランザクションログが大きくなりすぎて、メモリが不足してサービスがダウンしてしまうことを防ぐ目的で導入されたようです。Ruby 2.6.4, Rails 6.0.0 で確認しました。
$ rails --version Rails 6.0.0プロジェクトを作る
$ rails new rails_sandbox $ cd rails_sandboxconfig.disable_sandbox を設定する
今回は、
config/environments/development.rb
で設定します。config/environments/development.rbRails.application.configure do ... config.disable_sandbox = true endrails console を実行する
--sandbox
オプションつきで、rails console
を実行してみます。$ bin/rails c --sandbox Running via Spring preloader in process 48 Error: Unable to start console in sandbox mode as sandbox mode is disabled (config.disable_sandbox is true).エラーメッセージが表示されて実行できないことが確認できます。
試したソース
試したソースは以下にあります。
https://github.com/suketa/rails_sandbox/tree/try092_console_sandbox参考情報
- 投稿日:2019-10-09T11:13:23+09:00
Pryでpbcopy的なことをする
経緯
pryって便利ですよねえ。
と言いつつ「pryのコピーってどうするの・・・?」とずっと思ってました。
まあ、もちろん普通のコピペのやり方であれば無難に出来るんですけど長文の結果が出てきたときにコピーするの大変じゃないですか・・・。
pbcopyどこなの・・・という気持ちを抑えながら(´ε` )
なので、とりあえず簡易的にコピペできるコマンドを書きました!やりたいこと
- 最後に実行したコマンドをコピー
- 最後に実行したコマンドの結果をコピー
- 最後に実行したコマンドと結果をコピー
コマンド
- ということで、下記コマンドを
.pryrc
にぺぺっと貼ってください。- それで、pryを立ち上げてaliasに設定したコマンドを実行するとうまくいくと思います。
def pbcopy(str) IO.popen('pbcopy', 'r+') {|io| io.puts str } _pry_.output.puts text.green("--- Copy to ClipBoard ---") _pry_.output.puts str end Pry.config.commands.command "copy-history", "History copy to clipboard" do |n| pbcopy _pry_.input_ring[n ? n.to_i : -1] end Pry.config.commands.command "copy-result", "Last result copy to clipboard" do pbcopy _pry_.last_result end Pry.config.commands.command "copy", "Copy to clipboard" do |str| unless str str = "#{_pry_.input_ring[-1]}#=> #{_pry_.last_result}\n" end pbcopy str end Pry.config.commands.alias_command 'co', 'copy' Pry.config.commands.alias_command 'ch', 'copy-history' Pry.config.commands.alias_command 'cr', 'copy-result'実行結果
pry(main)> 1+1+1 => 3 pry(main)> ch --- Copy to ClipBoard --- 1+1+1 # ペーストすると 1+1+1 がコピーされていることが分かる pry(main)> 1+1+1 => 3 pry(main)> cr --- Copy to ClipBoard --- 3 # ペーストすると 3 がコピーされていることが分かる pry(main)> 1+1+1 => 3 pry(main)> co --- Copy to ClipBoard --- 1+1+1 #=> 3 # ペーストすると # 1+1+1 # #=> 3 # がコピーされていることが分かるちなみに
_pry_.input_ring
の中身はこうなっているらしいです。bufferから値を取得してるのかな。pry(main)> 1+1+1 => 3 pry(main)> 2+2+2 => 6 pry(main)> 3+3+3 => 9 pry(main)> _pry_.input_ring => #<Pry::Ring:0x00007fe7b36b0308 @buffer=[nil, "1+1+1\n", "2+2+2\n", "3+3+3\n", "_pry_.input_ring\n"], @count=5, @max_size=100, @mutex=#<Thread::Mutex:0x00007fe7b36b02e0>>一応、chをこう使うこともできます。引数を与えてあげるとindexに基づいた値を取得してくれる。
最新の値は-1
から始まって-1
ずつ古くなっていきます。
(Enter押すと""
空文字がBufferに入っていく)# 上記の状態から pry(main)> ch -2 --- Copy to ClipBoard --- 3+3+3Pry#input_array is deprecated
input_array
を使ってたんですけど推奨されてないようなのでinput_ring
を使用しています。warning: method Pry#input_array is deprecated. Use Pry#input_ring insteadpryのwikiはこちらです。
まとめ
これでめちゃくちゃ長い値も簡単にコピーできるようになって嬉しい・・・!
- 投稿日:2019-10-09T09:48:48+09:00
君はRailsの「Yay! You’re on Rails!」の画面がどこで記述されているか知っているか?
- 投稿日:2019-10-09T09:41:56+09:00
Railsのテスト環境でのいろいろなエラーでつらい
railsのテスト環境でやったときのいろいろなエラーの詰め合わせの記事です。
タイトルが抽象的ですみません。rails s -e test 2019-10-06 18:13:28 WARN Selenium [DEPRECATION] Selenium::WebDriver::Chrome#driver_path= is deprecated. Use Selenium::WebDriver::Chrome::Service#driver_path= instead. => Booting Puma => Rails 5.2.3 application starting in test => Run `rails server -h` for more startup options Puma starting in single mode... * Version 3.12.1 (ruby 2.6.3-p62), codename: Llamas in Pajamas * Min threads: 5, max threads: 5 * Environment: test * Listening on tcp://0.0.0.0:3000 Use Ctrl-C to stophttp://0.0.0.0:3000/
へアクセス。Puma caught this error: No route matches [GET] "/" (ActionController::RoutingError) # 以下、略こんな、エラがー起こる。
config/routes.rbroot 'rails/welcome#index'を追加する。
「Yay! You’re on Rails!」のおなじみの画面が出て安心する。ちなみにこのページのファイルはこのパスにあるようだ。
/Users/hoge/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/railties-5.2.3/lib/rails/templates/rails/welcome/index.html.erbcurlで叩いてみる
curl localhost:3000/blogs/index/ Puma caught this error: PG::UndefinedTable: ERROR: relation "blogs" does not exist LINE 1: SELECT "blogs".* FROM "blogs" ^ : SELECT "blogs".* FROM "blogs" (ActiveRecord::StatementInvalid) # 以下、略。なんだかすごい。エラーが出ている。
落ち着いてみる。冒頭に
Puma caught this error: PG::UndefinedTable: ERROR: relation "blogs" does not exist
とある。
データベースがなんかおかしい?単に
rails s
だと正常に返ってくる。DBがないのか?
test用のDBを作成する。rails db:create RAILS_ENV=test 2019-10-06 17:55:03 WARN Selenium [DEPRECATION] Selenium::WebDriver::Chrome#driver_path= is deprecated. Use Selenium::WebDriver::Chrome::Service#driver_path= instead. Database 'todo_slim_test' already existsDBはあるようだ。
test用にmigrateしなくてはいけないのか?$ rails db:migrate RAILS_ENV=test 2019-10-06 17:55:36 WARN Selenium [DEPRECATION] Selenium::WebDriver::Chrome#driver_path= is deprecated. Use Selenium::WebDriver::Chrome::Service#driver_path= instead. == 20190818133341 CreateBlogs: migrating ====================================== -- create_table(:blogs) -> 0.0645s == 20190818133341 CreateBlogs: migrated (0.0646s) =============================ついでにseedsもやっておく。
$ rails db:seed RAILS_ENV=test 2019-10-06 17:55:50 WARN Selenium [DEPRECATION] Selenium::WebDriver::Chrome#driver_path= is deprecated. Use Selenium::WebDriver::Chrome::Service#driver_path= instead.再度やる。
$ rails s -e test 2019-10-06 17:55:58 WARN Selenium [DEPRECATION] Selenium::WebDriver::Chrome#driver_path= is deprecated. Use Selenium::WebDriver::Chrome::Service#driver_path= instead. => Booting Puma => Rails 5.2.3 application starting in test => Run `rails server -h` for more startup options Puma starting in single mode... * Version 3.12.1 (ruby 2.6.3-p62), codename: Llamas in Pajamas * Min threads: 5, max threads: 5 * Environment: test * Listening on tcp://0.0.0.0:3000 Use Ctrl-C to stop 2019-10-06 17:56:05 +0900: Rack app error handling request { GET /blogs/index } #<AbstractController::ActionNotFound: The action 'show' could not be found for BlogsController>なんだか、まだなにかしなくてはいけないようだ。
developではこんなことを言われないので、DBがおかしいのかと疑ってみる。
テスト環境のDBの状態を見に行く。rails db RAILS_ENV=test DEPRECATION WARNING: Passing the environment's name as a regular argument is deprecated and will be removed in the next Rails version. Please, use the -e option instead. (called from <top (required)> at /Users/hoge/product/ruby_sandbox/rails-blog-slim/bin/rails:9) config.eager_load is set to nil. Please update your config/environments/*.rb files accordingly: * development - set it to false * test - set it to false (unless you use a tool that preloads your test environment) * production - set it to true-eオプションつけてやってと言われた。
dbを初期化する。
migrateもする。rails db:migrate:reset rails db:migrate rails db -e testつないで、DBができていることを確認した。
$ curl localhost:3000/blogs/index [{"id":1,"title":"今日のできごと","article":"ごはんを食べて寝た","created_at":"2019-10-06T09:02:16.207Z","updated_at":"2019-10-06T09:02:16.207Z"}][値が返ってきた。
いろいろ試した結果、たぶんDBが良くなかったんだろうという結論。雑なポエム的なまとめ
test環境はdevelopと結構違ってつらい。
動きがおかしかったらDBをresetしてtest用に作り直す。参考サイト
Ruby - rails db:migrate RAILS_ENV=test ができない|teratailRuby - rails s -e testでlocalhost:3000を見るとエラーが出てしまいます。エラーを消したいです。|teratail
- 投稿日:2019-10-09T09:37:36+09:00
Ruby、Rails を学ぶ上でのおすすめの本まとめ
これは何?
Ruby、Rails に関して何によってどう学んできましたか?というのをよく聞かれるので、まとめた。
あくまで個人の経験に基づく感想です。(書籍へのリンクはアフィリエイトではありません)
対象
とりあえず Ruby でちょっとしたスクリプトは書けるし、Rails も軽く触ったことはあるよ、という方。
(全くRuby触ったことない人向けではないと思います)本
Rubyの言語仕様を学ぶ
効能
- Ruby っぽい書き方ってどんな書き方なんだろう?を学べる(map 使うとか)。
- Rails を読む上でのおまじないが減る。
- なぜ attr_accessor を書くとセッター/ゲッターが生えるのかとか、モデルがActiveRecord を継承していることの意味とかがわかるようになり不安が減る。
紹介
- Effective Ruby
- リファレンス的な本ではなく、もっと現実的に使われる文脈を意識して書かれているように感じた。読みやすかった。
- 写経しやすいのが良い。書いて身につけることができる。
- メタプログラミングRuby第2版
- メタプログラミングという切り口で Ruby の言語仕様を教えてくれる本だと思った。
- クラス定義の話(継承ツリーとか特異クラスとかinclude/extendとかの話)はこの本が一番分かりやすかった。
- 「ブロックがクロージャである」という表現を直感的に理解できるようになった。
設計力を高める
効能
- 以下のような疑問に対する自分なりの意見を持てるようになる。
- model/controller/view 以外にも app ディレクトリ以下に service とか作ることがあるっぽいけど、どこに何を書くのが美しいの?
- controller/model が肥大化してしまってつらいんだけどどうすればいいの?
- 実装に関して戦略レベルで大きなヘマをすることが減る。したがってコードレビューで大きくちゃぶ台返しされることが減る。
注意事項
- ちょっと読んでみて「難しくてほとんど全部よくわからん、」「言っている意味はわかるがこれを学んでなにか意味があるの?」となったら今読むべきではないと考えたほうが良いかもしれません。
- 知識というか考え方を学ぶ本だと私は解釈しており、理解できていない状態で読んでもほとんど学びはないのではないか?となっている。
- 具体的に開発を通じて悩んだ経験があると、「なるほどこういうふうに書けるのね!」となりやすいが、経験があまりないとピンとこないので記憶に残らない、ということもありそう。
紹介
- リファクタリング:Rubyエディション
- 神本。しかし絶版っぽい?
- 写経しやすいのが良い。書いて身につけることができる。
- リファクタリングの本なので、before の状態から after の状態に帰る方法としてどのような方法があるのかを学ぶことができる。
- before の状態を見てその問題点を列挙する力、after の候補を列挙する力、after の候補から最も良いものを選び出す力、を身につけるとアウトプットの質が大幅に改善される。
- Ruby によるデザインパターン
- 絶版っぽい?
- デザインパターンを Ruby で実装してみた、みたいなもの。
- 『リファクタリング:Ruby』 でしっかり学んでいると、だいたい「まぁそうだよね」、となるかもしれない。
- 「デザインパターンって言葉よく聞くけど何なのだろう?ちょっと学んでみたいな」みたいな人には特に向いているかと思う。
- オブジェクト指向設計実践ガイド―Rubyでわかる進化しつづける柔軟なアプリケーションの育て方(サンディメッツ本)
- 絶版じゃない!!!
- 上の2つの本で学んだこととかぶるところが多いので途中で読むのをやめたが、良本。
- 登場する具体例が自転車なので、自転車好きなら良いかも。個人的には特に興味も知識もないので、オブジェクトの例としてギアとかそういうの出てきてもピンとこなくてちょっとつらかった。
- 投稿日:2019-10-09T09:26:17+09:00
【devise】confirmableを利用しているとき、メールアドレスを変更する際のメールによる確認作業をスキップする
config/initializers/devise.rb
を以下のように変更して保存。config/initializers/devise.rb... # If true, requires any email changes to be confirmed (exactly the same way as # initial account confirmation) to be applied. Requires additional unconfirmed_email # db field (see migrations). Until confirmed, new email is stored in # unconfirmed_email column, and copied to email column on successful confirmation. - config.reconfirmable = true + config.reconfirmable = false ...この時点でサーバーを起動していたなら再起動する。これでユーザー情報を変更する際に、確認作業なくメールアドレスを変更できる。
もし
users
テーブルにunconfirmed_email
カラムが残っているなら、不要なので削除する。$ rails g migration remove_unconfirmed_email_from_users unconfirmed_email:string $ rails db:migrate
- 投稿日:2019-10-09T09:26:17+09:00
【devise】confirmableを適用しているとき、メールアドレスを変更する際のメールによる確認作業をスキップする
config/initializers/devise.rb
を以下のように変更して保存。config/initializers/devise.rb... # If true, requires any email changes to be confirmed (exactly the same way as # initial account confirmation) to be applied. Requires additional unconfirmed_email # db field (see migrations). Until confirmed, new email is stored in # unconfirmed_email column, and copied to email column on successful confirmation. - config.reconfirmable = true + config.reconfirmable = false ...この時点でサーバーを起動していたなら再起動する。これでユーザー情報を編集する際に、メールアドレスを確認作業なく変更できる。
もし
users
テーブルにunconfirmed_email
カラムが残っているなら、不要なので削除する。$ rails g migration remove_unconfirmed_email_from_users unconfirmed_email:string $ rails db:migrate
- 投稿日:2019-10-09T09:26:17+09:00
【devise】confirmableを適用時の、メールアドレスを変更する際のメール確認をスキップする
config/initializers/devise.rb
を以下のように変更して保存。config/initializers/devise.rb... # If true, requires any email changes to be confirmed (exactly the same way as # initial account confirmation) to be applied. Requires additional unconfirmed_email # db field (see migrations). Until confirmed, new email is stored in # unconfirmed_email column, and copied to email column on successful confirmation. - config.reconfirmable = true + config.reconfirmable = false ...この時点でサーバーを起動していたなら再起動する。これでユーザー情報を編集する際に、メールアドレスをメールによる確認作業なしで変更できる。
もし
users
テーブルにunconfirmed_email
カラムが残っているなら、不要なので削除する。$ rails g migration remove_unconfirmed_email_from_users unconfirmed_email:string $ rails db:migrate
- 投稿日:2019-10-09T09:15:53+09:00
Deviseでログイン機能を追加・日本語化・Bootstrap適用まで
Deviseの日本語化やBootstrapの導入方法の記事はよく見かけます。ところが……
- ビューファイルを作成するときに,結局どのコマンドを使えばよいか分からない
$ rails g devise:views $ rails g devise:i18n:views $ rails g devise:views:bootstrap_templates
- 全て導入したはずなのにメール文が日本語化されない
など,順序を間違えると問題が発生します。そこで,ログイン画面を最低限のスタイルで実装するところまでをまとめてみました。
開発環境
- macOS Mojave 10.14.6
- Ruby 2.6.4
- Rails 5.2.3
- Bootstrap 4.3.1
- Devise 4.7.1 (confirmableなどの導入過程は記載していません)
0. 準備
以下の前提で進めていきます。(
-d postgresql
はお好みで)$ rails _5.2.3_ new devise_sample -d postgresql $ rails g controller homes indexconfig/routes.rbRails.application.routes.draw do root 'homes#index' endapp/views/layouts/application.html.erb(略) <body> <%= render 'shared/header' %> <%= yield %> </body>app/views/homes/index.html.erb<%= render 'shared/flash_messages' %>app/views/shared/_flash_messages.html.erb<% flash.each do |name, msg| %> <div class="alert alert-<%= name %>" role="alert" id="alert"> <a href="#" class="close" data-dismiss="alert">×</a> <%= msg %> </div> <% end %>app/views/shared/_header.html.erb<header> <nav class="navbar navbar-expand navbar-light"> <%= link_to "Deviseサンプル", root_path, class: 'navbar-brand' %> <div id="Navber"> <ul class="navbar-nav"> <% if user_signed_in? %> <li class="nav-item active"> <%= link_to 'ログアウト', destroy_user_session_path, method: :delete, class: 'nav-link' %> </li> <% else %> <li class="nav-item active"> <%= link_to "新規登録", new_user_registration_path, class: 'nav-link' %> </li> <li class="nav-item active"> <%= link_to "ログイン", new_user_session_path, class: 'nav-link' %> </li> <% end %> </ul> </div> </nav> </header>1. Gemの追加
- Gemfileに以下を追加して
$ bundle install
gem 'bootstrap', '~> 4.3.1' gem 'devise' gem 'devise-i18n' gem 'devise-i18n-views' gem 'devise-bootstrap-views', '~> 1.0' gem 'jquery-rails' gem 'rails-i18n'2. Bootstrapの導入
application.css
の拡張子をscss
に変更
application.scss
から,*= require_tree .
と*= require_self
を削除
application.scss
に@import "bootstrap";
を追加スタイルも追加
app/assets/stylesheets/application.scss@import "bootstrap"; // ログイン画面 .container-login { @extend .container-fluid; max-width: 576px; padding: 2rem; } // 「ログインしました」などのフラッシュ用スタイル .alert-notice { @extend .alert-info; } .alert-alert { @extend .alert-danger; }
application.js
に3つ追加app/assets/javascripts/application.js//= require jquery3 //= require popper //= require bootstrap-sprockets3. Deviseの導入
Devise
をインストール(user
の箇所は,任意のモデル名
でOKです)rails g devise:install rails g devise user rails db:create db:migrate
- 問題がなければ,
$ rails s
の後,http://localhost:3000
からログイン
ボタンを押せば,ログイン画面が表示されます。
gem 'devise-bootstrap-views'
を追加しているので, Bootstrapもある程度適用されています。
- 公式(hisea/devise-bootstrap-views)
- 横幅一杯表示されるのはちょっと……と思われるかもしれませんが,さらに見た目を整える作業は最後にします。
4. Deviseの日本語化
config/application.rbmodule AssociationTutorial class Application < Rails::Application # 以下を追加すれば日本語に + config.i18n.default_locale = :ja # タイムゾーンも変更するなら,以下を追加 + config.time_zone = 'Asia/Tokyo' end end
- サーバーを落として
$ rails s
で再起動すれば日本語に変更されます。5. メールを送信できるようにする(Gmailかつ開発環境のみ対応)
送信元の表示名
を変更しておきます。
- タイトル名はこの設定が反映されますが,メールアドレスの箇所は実際の送信するアドレスになります。
config/initializers/devise.rbDevise.setup do |config| - config.mailer_sender = 'please-change-me-at-config-initializers-devise@example.com' + config.mailer_sender = ''タイトル名 <noreply@example.com>' end
Gmailから送信する場合,安全性の低いアプリのアクセスを有効にする必要があります。
development.rb
のconfig.action_mailer.raise_delivery_errors = false
の箇所を置き換えます。
- メールアドレスやパスワードをgitの管理下に入れるのを避け,
credentials
管理にしておきます。本番環境で無駄にエラーが出ないようダミー情報を入れておきます。config/environments/development.rb# 削除 config.action_mailer.raise_delivery_errors = false # 以下に置き換え config.action_mailer.default_url_options = { host: 'localhost', port: 3000 } if Rails.application.credentials.gmail.present? mail_address = Rails.application.credentials.gmail[:address] password = Rails.application.credentials.gmail[:password] else mail_address = 'admin@example.com' password = 'password' end config.action_mailer.raise_delivery_errors = true config.action_mailer.delivery_method = :smtp config.action_mailer.smtp_settings = { enable_starttls_auto: true, address: "smtp.gmail.com", port: 587, user_name: mail_address, password: password, authentication: "plain" }
$ EDITOR=vi rails credentials:edit
で,credentials
の中に,メールアドレスとパスワードを記載します。gmail: address: 送信に使用するメールアドレス password: 送信に使用するパスワード
- メール送信の確認をするには,あらかじめ新規登録をした上で,ログイン画面の
パスワードを忘れましたか?
をクリックしてパスワード再設定メールを送信すればOKです。日本語にはなっているもののどうも不自然ですね……これは次の作業で解決します。
6. 日本語訳を変更
日本語訳を変更したい場合は,次のコマンドで
config/locales/devise.views.ja.yml
を作成し,編集すればOKです。rails g devise:i18n:locale ja
- なお,このコマンドの時点で,パスワード再設定メールの本文が自然なものに変わります。
- 例えば
アカウント登録
を新規登録
に変更したい場合は,devise.views.ja.yml
の該当文字を置換すればOKです。7. ログイン画面などの変更
- まず,次のコマンドでビューファイルを作成します。
rails g devise:i18n:views rails g devise:views:bootstrap_templates -f
【参考】それぞれのコマンドの最後に例えば
user
をつけることで,users
ディレクトリ内にファイルを作成することもできますが,その場合は,次の3つの作業を行わないと反映されません。
devise.views.ja.yml
30行目のdevise
をusers
に変更config/initializers/devise.rb
にあるconfig.scoped_views = false
のコメントアウトを外してtrue
に変更- サーバーを落として
$ rails s
で再起動例えば以下のファイルを
<div>
で囲むと,無駄に横長になっている状態を改善できます。
- ここから先はお好みで。
ファイル名app/views/devise/confirmations app/views/devise/passwords app/views/devise/registrations app/views/devise/sessions app/views/devise/unlocks<div class="container-login"> # 元のプログラム </div>
- エラーメッセージの表示がいまいちなので変更するためにオーバーライドします。
app/helpers/devise_helper.rbmodule DeviseHelper def bootstrap_devise_error_messages! return "" if resource.errors.empty? html = "" messages = resource.errors.full_messages.each do |errmsg| html += <<-EOF <div class="alert alert-danger alert-dismissible" role="alert"> <button type="button" class="close" data-dismiss="alert"> <span aria-hidden="true">×</span> <span class="sr-only">close</span> </button> #{errmsg} </div> EOF end html.html_safe end end
- ログイン画面だけエラーメッセージが表示されないので,追加しておきます。
app/views/devise/sessions/new.html.erb<div class="container-login"> <h1><%= t('.sign_in') %></h1> + <%= render 'shared/flash_messages' %> <%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>
app/views/devise
ディレクトリ内のファイルのbtn btn-primary
をbtn btn-primary btn-block
に置換すればボタンの横幅が自然になります。
- バリデーションはフロント側にも簡単に入れられます。
f.email_field
,f.password_field
にrequired: true
を入れることで空欄投稿できなくなります。- 新規登録(アカウント登録)画面では,例えば,
f.password_field
にrequired: true, minlength: '6', maxlength: '30'
を追加すれば,文字数のバリデーションも追加できます。app/views/devise/registrations/new.html.erb<div class="form-group"> <%= f.label :password %> - <%= f.password_field :password, autocomplete: 'current-password', - class: 'form-control' %> + <%= f.password_field :password, autocomplete: 'current-password', + class: 'form-control', + required: true, + minlength: '6', + maxlength: '30' %>
_links.html.erb
を編集して,一番下のリンクをボタンにしてみます。app/views/devise/shared/_links.html.erb<hr class="border-dark my-5"> <div class="form-group"> <%- if controller_name != 'sessions' %> <%= link_to t(".sign_in"), new_session_path(resource_name), class: 'btn btn-info btn-block' %><br /> <% end -%> <%- if devise_mapping.registerable? && controller_name != 'registrations' %> <%= link_to t(".sign_up"), new_registration_path(resource_name), class: 'btn btn-info btn-block' %><br /> <% end -%> <%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %> <%= link_to t(".forgot_your_password"), new_password_path(resource_name), class: 'btn btn-secondary btn-block' %><br /> <% end -%> <%- if devise_mapping.confirmable? && controller_name != 'confirmations' %> <%= link_to t('.didn_t_receive_confirmation_instructions'), new_confirmation_path(resource_name), class: 'btn btn-secondary btn-block' %><br /> <% end -%> <%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %> <%= link_to t('.didn_t_receive_unlock_instructions'), new_unlock_path(resource_name), class: 'btn btn-secondary btn-block' %><br /> <% end -%> <%- if devise_mapping.omniauthable? %> <%- resource_class.omniauth_providers.each do |provider| %> <%= link_to t('.sign_in_with_provider', provider: OmniAuth::Utils.camelize(provider)), omniauth_authorize_path(resource_name, provider), class: 'btn btn-info btn-block' %><br /> <% end -%> <% end -%> </div>
- さらに,リンクの
ログイン
,新規登録(アカウント登録)
を次のように変更してみます。# 上2つを次に置き換え <%- if controller_name != 'sessions' %> <%= link_to "アカウントをお持ちの方", new_session_path(resource_name), class: 'btn btn-info btn-block' %><br /> <% end -%> <%- if devise_mapping.registerable? && controller_name != 'registrations' %> <%= link_to "アカウントをお持ちでない方", new_registration_path(resource_name), class: 'btn btn-info btn-block' %><br /> <% end -%>単純なスタイルですが,ログイン画面がだいぶ整ったのではないでしょうか。
- 投稿日:2019-10-09T08:35:24+09:00
アンチパターン:Railsで外部キーとassociation名は同じにしない
要約
belongs_to :name
とforeign_key :name
が同じだと、その逆のhas_manyをとるときにどうがんばってもうまく取れなかった- 同じにしない
- 外部キーには
_id
つける詳細
- 以下のコードだと
to_messages
、from_messages
がとれないclass User < ApplicationRecord has_many :to_messages, class_name: 'Message', foreign_key: 'to', dependent: :destroy has_many :from_messages, class_name: 'Message', foreign_key: 'from', dependent: :destroy end class Message < ApplicationRecord has_many :to, class_name: 'User', foreign_key: 'to' has_many :from, class_name: 'User', foreign_key: 'from' end >Message.last.to =>#<User id: 373...> >User.last.to_messages =>[]
- has_manyの名前を変えたらいけた
class Message < ApplicationRecord has_many :to_user, class_name: 'User', foreign_key: 'to' has_many :from_user, class_name: 'User', foreign_key: 'from' end
- そもそも
foreign_key
は_id
がいいね- 自分では慣例的にやらないけどレビューであがってくることはあるので覚えておく
- 投稿日:2019-10-09T06:00:14+09:00
マイグレーションファイルの命名規則
目的
マイグレーションファイルを作成する際に
作成のみでなく閲覧者などにもわかりやすい命名にするため規則などを記載していくtaskテーブルを作成
rails g migration CreateTasks name:string content:text
マイグレーションファイル例class CreateTasks < ActiveRecord::Migration[5.2] def change create_table :tasks do |t| t.string :name t.text :content end endtaskテーブルにカラムを追加
rails g migration AddStateToTasks state:string
rails g migration add_state_to_tasks state:string
マイグレーションファイル例class AddStateToTask < ActiveRecord::Migration[5.2] def change #[形式]add_column(テーブル名,カラム名,データ型) add_column :tasks, :state, :string end endadd_columnには次のオプションを指定することができる。
・ null:true … NOT NULL制約を削除
・ null:true … NOT NULL制約を追加
・ limit: size … ファイルのサイズに対する制限を設定
・default:[val]… [val]に設定した値をレコード作成じのカラムのデフォルト値とするtaskテーブルに追加したカラム名を変更
rails g migration RenameFromStateToTasks
rails g migration rename_state_to_tasks
マイグレーションファイル例class RenameFromStateToTasks < ActiveRecord::Migration[5.2] def change #[形式]rename_column(テーブル名,変更前のカラム名,変更後のカラム名) rename_column :tasks,:state,:status end endtaskテーブルに追加したカラムのデータ型を変更
rails g migration ChangeStatusOfTasks
rails g migration change_datatype_status_of_tasks
マイグレーションファイル例class ChangeDatatypeStatusOfTasks < ActiveRecord::Migration[5.2] def up #[形式]change_column(テーブル名,カラム名,データタイプ,オプション) change_column :tasks, :status, :integer # オプション # limit - カラム長の最大数 # change_column :tasks, :status, :integer, limit: 120 # default - カラムのデフォルト値を設定。NULLにしたい場合は、nilを指定 # change_column :tasks, :status, :integer, default: "タイトルがありません" # null - null制約を設定。false -> null制約がON。true -> null制約がOFF # change_column :tasks, :status, :integer, null: true end endtaskテーブルに追加したカラムにインデックスやユニーク制約の追加
rails g migration AddIndexStatusToTasks
rails g migration add_index_Status_to_tasks
マイグレーションファイル例class AddIndexStatusToTasks < ActiveRecord::Migration[5.2] def change add_index :tasks, :status #add_index :tasks, :status ,unique: true ユニーク制約も付加可能 end endtaskテーブルに追加したカラムにNULL制約の追加
rails g migration ChangeNotNullToTasks
rails g migration change_notnull_to_tasks
マイグレーションファイル例class ChangeNotNullToTasks < ActiveRecord::Migration[5.2] def change #tasksテーブルのstatusにNOT NULL制約を追加 change_column_null :tasks, :status, false end end
- 投稿日:2019-10-09T05:48:29+09:00
DockerでRails・PostgreSQL構築
概要
- Dockerを使用したRailsの開発環境を構築
- DBはPostgreSQLを使用
- ローカル開発環境で構築(PostgreSQLのユーザー名、パスワードは簡易なものを使用)
前提
- Mac、Windows10のローカル開発環境で動作
- 「Docker for Windows」か「Docker for Mac」がインストール済みであること
ディレクトリ構造
├─src │ ├─Gemfile │ └─Gemfile.lock ├─Dockerfile └─docker-compose.yml
準備するファイル
ディレクトリ構造にある4つのファイルの内容は下記になります。
Dockerfile
DockerfileFROM ruby:2.6.3 ENV LANG C.UTF-8 RUN apt-get update -qq && \ apt-get install -y build-essential \ libpq-dev \ nodejs RUN mkdir /app RUN mkdir /app/src ENV APP_ROOT /app/src WORKDIR $APP_ROOT ADD ./src/Gemfile $APP_ROOT/Gemfile ADD ./src/Gemfile.lock $APP_ROOT/Gemfile.lock RUN bundle install ADD . $APP_ROOTdocker-compose.yml(※WindowsとMacで記述を分ける箇所有)
docker-compose.ymlversion: '3' services: postgres: image: postgres ports: - "3306:3306" volumes: - ./tmp/db:/var/lib/postgresql/data #MacOSの場合 - app_postgre:/var/lib/postgresql/data #Windowsの場合 environment: POSTGRES_USER: 'admin' POSTGRES_PASSWORD: 'admin-pass' restart: always app: build: . image: rails container_name: 'app' command: bundle exec rails s -p 80 -b '0.0.0.0' ports: - "80:80" environment: VIRTUAL_PORT: 80 volumes: - ./src:/app/src depends_on: - postgres restart: always #下記はWindowsの場合追加 volumes: app_postgre: external: true※PostgreSQLはWindowsではマウントできないようなので、参照先を外部に指定します。
Gemfile
src/Gemfilesource 'https://rubygems.org' ruby '2.6.3' gem 'rails', '5.2.3'Gemfile.lock
ファイルの中身は空でOK
src/Gemfile.lock手順
ボリューム作成(※Windowsの場合のみのコマンド)
$ docker volume create --name app_postgre※PostgreSQLはWindowsではマウントできないようなので、参照先を外部にするためのコマンドです。
スケルトンアプリ作成
$ docker-compose run app rails new . --force --database=postgresql --skip-bundledatabase.ymlを修正
src/config/database.ymldefault: &default adapter: postgresql encoding: unicode # For details on connection pooling, see Rails configuration guide # http://guides.rubyonrails.org/configuring.html#database-pooling pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> username: admin password: admin-pass host: postgresイメージ再ビルド
※処理完了までちょっと時間がかかることがあります。。。
$ docker-compose build
コンテナ立ち上げ
$ docker-compose up -dPostgreSQLにRailsのDBを作成
$ docker-compose run app rails db:create
これで、http://localhostにブラウザでアクセスすると、Railsのインストール完了画面がでてきました。
トップページ作成
とりあえずトップページを表示させたい場合は下記コマンドを実行
$ docker-compose run app rails g controller Home top
「get 'home/top'」を「root to: 'home#top'」を変更
config/routes.rb# ・・・ root to: 'home#top' # ・・・インストール後の作業
私の記事ですが、下記の手順もまとめましたので、参考にしていただければ幸いです。
【Rails】「config」の使い方 - Qiita
サイトから送信するSMTPサーバー情報や、ソーシャルログインなどで使用するAPIキーをGitにアップロードする際に、外部に情報が流出させないようにする設定です。【Rails】Bootstrap4・jQueryを適用
フロントでBootstrap4とjQueryを適用して構築したい時は便利Font Awesome適用
アイコンを気軽に使用したい時はこちらが便利。メールアドレスの登録・アンケート回答すると、無料でたくさんのアイコンが使用できます。【devise】メール認証のサインアップ・イン・アウト機能
メール認証で、サイトに新規会員登録機能や、ログイン・ログアウト機能を搭載する設定です。【Rails】【Devise】twitter・Facebookログイン実装
上記のメール認証に加えてソーシャルログイン機能を実装する手順です。
- 投稿日:2019-10-09T01:11:29+09:00
【49日目】Rails学習日誌 コメントの保存、表示、パーシャル作成、バリデーション、エラーの表示
68 コメント保存処理の実装 続き
コメントのコントローラーの編集
binding.pryでコメント入力時のパラメーターを確認
Rails flogによってパラメータが下記のように見やすく表示されるParameters: { "utf8" => "✓", "authenticity_token" => "(略)", "comment" => { "board_id" => "5", "name" => "例", "comment" => "コメント" }, "commit" => "送信" }ストロングパラメータの設定
保存できるパラメータはboard_id, name, commentのみにする
comments_controll.rbprivate def comment_params params.require(:comment).permit(:board_id, :name, :comment) endcreateアクションの作成
ストロングパラメータによって弾かれるかどうかをif文で分岐
分岐した結果、成功をエラーをflashメッセージを用いてリダイレクト先に表示comments_controll.rbdef create # コメントオブジェクトを作成 # フォームに入力されたパラメータで初期化 comment = Comment.new(comment_params) # コメントの保存。保存前にバリデーションチェック # 保存の可否によってif文で分岐 if comment.save flash[:notice] = 'コメントを投稿しました' #flashでリダイレクト先に一時的にメッセージを渡す redirect_to comment.board else # 以下はRails5.1以降専用 # コメントが保存できなかった場合入力したコメントオブジェクトとエラーメッセージのリストを返す。 # コメントオブジェクトの中身をリダイレクト先のフォームに戻したり、エラー表示は今後作る flash[:comment] = comment flash[:error_messages] = comment.errors.full_messages redirect_back fallback_location: comment.board # redirect_backで一つ前の画面に戻るようリダイレクト end end69 コメントの表示
showの問題を修正
修正前のshowアクション
boards_controller.rbdef show @comment = @board.comments.new endこの状態では、新しく作成されたが保存していない空のコメントが含まれてしまう。
=>常にからのコメントがコメント欄に表示されてしまうので修正する。boards_controller.rbdef show @comment = Comment.new(board_id: @board.id) endコメントの初期化の際にboard_idを用いることで、該当のboardのコメントで保存されたコメントのみを表示するようにした。
viewの作成
show.html.erb<div class="p-comment__list"> <div class="p-comment_listTitle">コメント</div> <%= render @board.comments %> # 特定のモデル(comment)のオブジェクトのリスト(comments)をrenderの引数に渡した場合、自動的にモデル名(comment)のviewが使用される。 # オブジェクトの数だけ繰り返しviewがレンダリングされる。 # 今回のrenderの対象は「comments」なので、/app/views/comments/_comments.html.erbがオブジェクトの数レンダリングされる </div>## コメント表示用のパーシャルの作成
/app/views/commentsを作成し、_comment.html.erbを作成。_comment.html.erb<div class="p-comment__item"> <p><%= simple_format(comment.comment) %></p> # simple_formatヘルパーで改行を<br>タグに変換してくれる <div class="p-comment__bottomLine"> <span><%= comment.name %></span> <span><%= comment.created_at.to_s(:datetime_jp) %></span> #以前作った日本語時刻表示 </div> </div>リファクタリング
commentを投稿するためのviewはcommentsディレクトリの下にある。
しかし、_comment_formはboardsディレクトリの下にある。
=>役割的にcommentsディレクトリ配下にあるべきなので修正する。_form.html.erb としてcomments配下にコピーした上で、shownのパーシャルの呼び出し方を変えれば完了。
show.html.erb<%= render partial: 'comments/form', locals: { comment: @comment } %>70 コメントモデルへのバリデーション追加
モデルにバリデーションを作成
comment.rbvalidates :name, presence: true, length: { maximum: 10 } validates :comment, presence: true, length: { maximum: 1000 }## エラーメッセージを作成
comments_controller.rbのcreateアクションのelse以下がバリデーションエラーの表示になる
以前作成した掲示板のcreate時のエラーメッセージを流用するapp/views/sharedディレクトリを作成
_error_messages.html.erbを作成<% if flash[:error_messages] %> <div class="alert alert-danger"> <ul> <% flash[:error_messages].each do |msg| %> <li><%= msg %></li> <% end %> </ul> </div> <% end %>各viewのエラー呼び出しを編集
まずはboardsから
こちらではすでに作成している_form.html.erb内にエラー表示の部分があるため、sharedを呼び出すように書き換える。_form.html.erb<%= render 'shared/error_messages' %>commentsにおいても_form.html.erbの先頭にsharedを呼び出すように上記と同じ記載をする。
エラーの日本語化
config/locales/ja.yml を編集
ja.ymlja: activerecord: attributes: board: name: 名前 title: タイトル body: 本文 comment: name: 名前 comment: コメント views: pagination: first: '最初' last: '最後' previous: '前' next: '次' truncate: '...'