20200117のRailsに関する記事は20件です。

Railsで起きたRoutingErrorの対処について

RailsでRoutingErrorが起きた。

今回の対処法

至って問題はシンプルで、<%= %>内のpathに問題があった。

具体的には

example.html.erb
<%= link_to "Delete", post_path(@post), method: :delete, data:{confirm: "Are you sure?"}, class:"button is-danger" %>

としないといけなかったところで、

example.html.erb
<%= link_to "Delete", edit_post_path(@post), method: :delete, data:{confirm: "Are you sure?"}, class:"button is-danger" %>

としてしまっていた。

今後の対策

RoutingErrorが起きたら、まずPathを確認する。

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

Stripeの有料プラン変更時の挙動のまとめ

Stripeの実装にあたって、実際に自分で開発しているサービスのケースに合わせた場合のユースケースメモ。
参考: https://qiita.com/AkihikoNambu/items/bb496612f3044b2b3e1e


有料プランをアップグレードするケース(前払い制)

(例) ライトプラン(¥980) -> ベーシックプラン(¥2980)に変更するケース
4/1 ライトプランの料金(¥980)を支払う
4/15 ベーシックプランへ変更
差分を計算
ベーシックプラン => 4/15 ~ 4/30 => (2980 * (15/30)) => 1490
ライトプラン 4/1 ~ 4/15 => (980 * (15/30)) => 490
差分: 1490 - 490 = 1000
5/1 プレミアムプランの料金 + 差分を請求 = 2980 + 490 = 3470円が請求される


有料プランをダウングレードするケース(前払い制)

(例) ベーシックプラン(¥2980) -> ライトプラン(¥980)に変更するケース
4/1 ベーシックプラン(¥2980)を支払う
4/15 ライトプランへ変更
差分を計算
ライトプラン 4/1 ~ 4/15 => (980 * (15/30)) => 490
ベーシックプラン => 4/15 ~ 4/30 => (2980 * (15/30)) => 1490
差分: 490 - 1490 = ¥-1000
5/1 ライトプラン(¥980)の料金 + 差分を請求 = 980 + (-1000) = 0円が請求される(差額残り ¥-20)
6/1 ライトプラン(¥980)の料金 + 残りの差分を請求 = 980 + (-20) = ¥960が請求される(差額残り ¥0)
7/1 ライトプラン(¥980)の料金が請求される


定期課金終了時に未払いの差額が残っているケース(前払い制)

(例) ライトプラン(¥980) -> ベーシックプラン(¥2980)に変更するケース
4/1 ライトプランの料金(¥980)を支払う
4/15 ベーシックプランへ変更
差分を計算
ベーシックプラン => 4/15 ~ 4/30 => (2980 * (15/30)) => 1490
ライトプラン 4/1 ~ 4/15 => (980 * (15/30)) => 490
差分: 1490 - 490 = 1000
4/20 キャンセル手続き
差分: ¥750が残っている
5/1 差額分はStripeによって自動で決済される。

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

AWS Cloud9でRails6+Webpacker実行時に発生するエラーの修正(VM4276:1 GET https://localhost:3035/sockjs-node/info?t=111111111111 net::ERR_CONNECTION_REFUSED)

環境

  • Rails6
  • AWS Cloud9
  • Docker

エラー発生箇所

localで開発したRails6プロジェクトを、Cloud9で動かそうとしたらブラウザのコンソールでいきなり発生した

エラー内容

VM4276:1 GET https://localhost:3035/sockjs-node/info?t=111111111111 net::ERR_CONNECTION_REFUSED

修正方法

config/webpacker.ymlを変更する

# config/webpacker.yml
development:
  <<: *default
  compile: true

  # Verifies that correct packages and versions are installed by inspecting package.json, yarn.lock, and node_modules
  check_yarn_integrity: false

  # Reference: https://webpack.js.org/configuration/dev-server/
  dev_server:
    https: false
    host: localhost
    port: 3035

    # この箇所にCloud9のURLを貼る(#{}は適宜変更して下さい)
    public: "https://#{C9_PID}.vfs.cloud9.#{AWS_REGION}.amazonaws.com:3035"

参考リンク

https://github.com/webpack/webpack-dev-server/issues/230

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

[Rails] OpenAPI (swagger) で書いたAPIドキュメントをテストする。

この記事について

OpenAPI (swagger) がかなりイケてるという情報を得て、導入・テストするまでにいろいろ模索したので、その備忘録として書き留めておきます。
「これからRailsプロジェクトにOpenAPIを導入したい!」と思っている方の参考に、少しでもなればなと思います。

また、僕なりにググって、「これがいいんじゃないかなぁ」と思ったものなので
「こっちのほうがいいよ!」ってのがあれば、ぜひ教えてください。。

OpenAPIの記法については、割愛します。ググれば山のようにあるので。。

構成

サーバー:Rails(with docker)
APIドキュメント:OpenAPI
テスト:RSpec + committee (committee-rails)

OpenAPIを導入する

導入といっても、やることは大してありません。
docker hubに、公式イメージでswagger-uiがあるので、それを使います。(参照

swagger:
    image: swaggerapi/swagger-ui
    volumes:
      - './open_api/open_api.yml:/usr/share/nginx/html/open_api.yml'
    environment:
      API_URL: open_api.yml
    ports:
      - '8080:8080'

volumes./open_api/open_api.ymlのところに、APIドキュメントが書かかれているファイルまでのパスを指定してください。
environmentAPI_URLの部分は、ファイル名で大丈夫です。

これで終わりです! localhost:8080にアクセスすれば、おしゃれなSwagger UIが表示されているかと思います。

他のコンテナで8080使ってるんだよなって方がいたら、ホスト側のポート(host_port:container_port)を9000など、任意のポートを振ってあげてください。

テストできるように設定する

「実際のレスポンスとドキュメントが違う!(おこ)」ってフロントエンド様を怒らせてしまった方いるんじゃないでしょうか。
かといって人力でチェックするのも限界があると思うので、自動でテストできたら楽ですよね!
RSpecでドキュメントも一緒にテストできるように設定していきます。

gem 'committee'  
gem 'committee-rails'

まずは、上二つをインストールしてください。(committee / committee-rails

rails_helper.rb
require 'spec_helper'
require 'openapi_parser'
require 'committee'
require 'committee/rails/test/methods'

# (中略)

include Committee::Rails::Test::Methods

RSpec.configure do |config|
  # (略)

  # configurations for open api
  open_api = OpenAPIParser.parse(YAML.load_file("#{Rails.root}/open_api/open_api.yml"))
  schema = Committee::Drivers::OpenAPI3::Driver.new.parse(open_api)
  config.add_setting :committee_options
  config.committee_options = { schema: schema }
end

次に、OpenAPIでかかれたymlは、ちょっと独特の記法がはいっているので、パースする必要があります。その処理が上のものです。
config.committee_options = { schema: schema }で、パースしたschemaをテストで使ってくれと指定。

以上で設定は終了です!

実際にテストする

sample_spec.rb
it 'APIドキュメントとレスポンスが一致すること' do
  get '/sample_api'
  assert_response_schema_confirm
end

assert_response_schema_confirmこの1行で、書かれたドキュメントとレスポンスが一致してるかテストしてくれます!

期待したプロパティ以外のものが返ってきてるのに、テストが通ってしまう({ "hoge": 123 }を期待して、{ "hoge": "123", "foo": "oof"}が返ってきたのにテストが通ってしまう)場合は、ドキュメントを記載しているymlファイルの方に、additionalProperties: falseを加えてやれば大丈夫です!

最後に

冒頭にも書きましたが、「これがよかろう!」と個人的に思ったものなので、他にいいやり方など、ご存知の方がいましたら是非教えてください。
拙い記事で読みづらかったかもしれませんが、読んでくださり、ありがとうございました!

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

演算子

・論理演算子(!, &&, ||, not, and, or)

!a # a が false であれば
a && b # a かつ b が true であれば
a || b # a または b が true であれば
not a # a が false であれば
a and b # a かつ b が true であれば
a or b # a または b が true であれば

・比較演算子(==, !=, <, >, <=, >=, <=>, ===)

a == b # a と b が等しい
a != b # a と b が等しくない
a < b # a が b よりも小さい
a > b # a が b よりも大きい
a <= b # a が b 以下である
a >= b # a が b 以上である

・代入演算子(=, +=, -=, =, /=, %=, *=, &=, |=, ^=, <<=, >>=)

a = b # a に b を代入する
a += b # a = a + b に同じ
a -= b # a = a - b に同じ
a = b # a = a * b に同じ
a /= b # a = a / b に同じ
a %= b # a = a % b に同じ
a *
= b # a = a ** b に同じ
a &= b # a = a & b に同じ
a |= b # a = a | b に同じ
a ^= b # a = a ^ b に同じ
a <<= b # a = a << b に同じ
a >>= b # a = a >> b に同じ
a &&= b # a && (a = b) に同じ
a ||= b # a || (a = b) に同じ

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

【Rails】メールアドレスを記憶する

ログインフォームに「次回からメールアドレスの入力を省略する」というチェックボックスをつけて、次回ログインフォームにアクセスした時にメールアドレスが自動で入力される機能を実装したので、実装方法を記録します。

全体の流れ

  • ログインフォームにチェックボックスをを設置
  • チェックされた状態でログインしたら、メールアドレスを暗号化してcookieに保存
  • 次回以降のログイン時、cookieにメールアドレスが保存されているかを確認して、あれば利用
  • チェックを外した状態でログインしたら、cookieに保存されているメールアドレスを削除

Controller

def create
  customer = Customer.find_by(email: params[:session][:email].downcase)
  if customer&.authenticate(params[:session][:password])
    if params[:session][:remember] == '1'
     #cookieを登録
      cookies.permanent.signed[:wbEmail] = params[:session][:email]
    else
     #cookieを削除
      cookies.permanent.signed[:wbEmail] = nil
    end
    log_in customer
    flash[:success] = 'ログインしました'
    redirect_to root_url
  else
    flash.now[:danger] = 'メールアドレスとパスワードの組み合わせが正しくありません'
    render 'new'
  end
end

ログインフォーム

<%= form_with scope: :session, url: login_path, local: true do |f| %>
    <label>メールアドレス</label>
    <% if cookies.permanent[:Email].blank? %>
    <%= f.email_field :email %>
    <% else %>
    <%= f.email_field :email, value: cookies.permanent.signed[:Email] %>
    <% end %>

    <label>パスワード</label>
    <%= f.password_field :password %>

    <% if cookies.permanent[:Email].blank? %>
    <%= f.check_box :remember, id: "check1" %>
    <% else %>
      <%= f.check_box :remember, id: "check1", checked: true %>
    <% end %>
    <label for="check1">次回からメールアドレスの入力を省略する</label>

  <%= f.submit "ログイン" %>
<% end %>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Rails,位置情報] activerecord-mysql2spatial-adapterの使い方

しずおかオンラインでWeb開発しているkazuomatzです。

Rails + MySQLで位置情報を扱う需要は、少なからずあるかと思います。

Rails + MySQLで位置情報を扱う場合、activerecord-mysql2spatial-adapterが定番のGemかと思うのですが、このGemの開発が止まっていて、Railsのバージョンアップのたびに、有志の開発者の方が本家からBranchしたバージョンを独自リリースしている状態というエントリーを記事にしたのですが、あまり反響がなかったので、そういう需要はもはやないのかなと思っておりました。

そんな中、この記事を見た方から、そもそもactiverecord-mysql2spatial-adapterの使い方がまとめれているところがあまりないという問い合わせがあったので、簡単に書き留めておきます。

こちらの記事に、Rails 4.2, 5.0, 5.1, 6.0に対応したGemfileの書き方を記述してあります(Rails6.0の対応については、僕がやりました)。こちらを参照にGemfileを記述して、bundle installします。

config/database.yml

adapterをmysql2spatialにする。ここがポイントです。

config/database.yml
default: &default
  adapter: mysql2spatial
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: xxxx
  password: xxxxx
  socket: /tmp/mysql.sock
 :
 :
 :

マイグレーションファイル

位置情報を含むモデルを作成するためのマイグレーションファイルです。
t.point (t.geometry でもOK)型として宣言します。nullは許容しないようにします。
また、インデックスを作成する場合は、spatial: trueとします。

MySQL 5.6以前の場合、spatial: trueの指定をする場合は、ストレージエンジンがMyISAMである必要があります。MySQL 5.7以降であればInnoDBでOKです。

db/migrate/xxxxxxxxxxxxx.rb
class CreateLocations < ActiveRecord::Migration[6.0]
    def change
        create_table :locations do |t|
        t.string :name, comment: 'スポット名称'
        t.point :geom, null: false, comment: '緯度経度'
    end
    add_index :locations, :geom, spatial: true
end

Pointクラスを作る

Geometry型に緯度経度を設定するために便利なPointクラスを作っておきます。

Point.rb
# Geometry型に緯度経度を設定するためのPointクラス
class Point
    def self.from_x_y(x, y)
        x.present? && y.present? ? "POINT(#{x} #{y})" : nil
    end
end

緯度経度の設定と参照

あとは、以下のようにすれば、設定と参照が可能になります。

location.rb
location = Location.new
location.name = '東京駅'
location.geom = Point.from_x_y( 139.767125, 35.681236 )
location.save!

# 緯度経度参照
p location.geom.x   # -> 139.767125
p location.geom.y   # -> 35.681236
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ログイン機能の実装(SNS認証機能の追加)

記事保留

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

Capybaraでtrタグのtdタグを指定してテストする方法

はじめに

tr1行目のtd3番目と指定したいときに悩んだんで備忘録として残しておきます。

よく見かけるやつ

xxx_spec.rb
all('tbody tr')[0]
# tbodyの中の一行目のtrを取得する

all('tbody td')[0]
# tbody中から、tdの一つ目を取得する

上記のコードを自分はよく使ってます。
ただ、日付eachでまわしてtableを作成したときに、n番目のtdを取得したいときに問題発生。
1月時点で1行目のみテストする場合は問題ないですが、trの2行目にtd要素があるかどうかみたいなとき、
1月は31日までなので、2行目まで数えようとしたら、all('tbody td')[32]のようにわざわざ要素を数えて、みたいなことしてると3行目のtd数えるとかしんどーー!!ってなります。
ちなみに、こういう場合月によって末日が変わるので、travel_toでテストを書く日付を固定しないといけなくなるので良くないかもしれないです。

実装

xxx.html.haml
%table
  %thead
    %tr
      %th 名前
  %tbody
    %tr
      %td てつお君
      %td ひろみちゃん
    %tr
      %td まさし君
      %td ひろたか君

上記のようなtableがある場合、ひろみちゃんを取得したい場合はこれだけでいけます。

xxx_spec.rb
# trの1行目取得
tds = all('tbody tr')[0].all('td')
expect(tds[1]).to have_content 'ひろみちゃん'

# trの2行目のまさし君取得
tds = all('tbody tr')[1].all('td')
expect(tds[0]).to have_content 'まさし君'

これでテスト完了です。
ちなみに、('tbody tr')tbodyとしてるのは、theadの部分のテストは不要だったので書いていません。
このあたりはご自身が取得されたい要素によって使い分けてもらえればと思います。

まとめ

ひたすら数えたりとかすることの手間がこれで省ければと思います。

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

RailsアプリケーションにjQueryを導入する方法

RailsでjQueryを導入する方法について記述していく。
アプリケーションを立ち上げて毎回調べるのも面倒なので備忘録として。

手順

① 下記gemをGemfileに記述する。

Gemfile
gem "jquery-rails"

② ターミナルでbundle installを実行する。

ターミナル
$ bundle install #bundleでも可

③ Gemfile.lockに下記が追加されている事を確認する。

Gemfile.lock
jquery-rails(n.n.n) #nはinstallした時によってバージョンが異なる為

④ application.jsに //= require jquery と //= require jquery_ujs の2行を追加する。

application.js
//= require rails-ujs
//= require activestorage
//= require turbolinks
//= require jquery #これを追加
//= require jquery_ujs #これも追加
//= require_tree .

⑤ app/views/layouts/application.html.erb内の記述を確認する。

application.html.erb
<%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>

この中の'application'が記述されることで、スクリプトファイルを読み込むためのスクリプトタグが自動的に生成される。

⑥ 正常にjQueryが動くかを確認する。
5までの手順で基本的には実装は完了しているが、念の為動いているかを確認する。
適当にindex.html.erbファイルを作成。下記は1例。

index.html.erb
<p>jQuery稼働テスト(未稼働)</p>

スクリーンショット 2020-01-17 14.26.51.png
こう表示される。
ではjQueryの記述を追加しよう。

index.html.erb
  <p>jQuery稼働テスト(未稼働)</p>

#ここから追加
  <script type="text/javascript">
  $(document).ready(function() {
    $("p").text("jQuery稼働テスト(稼働中)");
  });
</script>
#ここまで

記述としては、ページを読み込んだ際にpタグのテキストを"jQuery稼働テスト(稼働中)"に置き換える、という物だ。
再度ページを読み込む。
スクリーンショット 2020-01-17 14.26.25.png
無事jQueryが動作している事が確認できた。

参考サイト
https://qiita.com/ngron/items/95846bd630a723e00038
https://www.sejuku.net/blog/57790

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

Docker for Windowsでwebpack-dev-serverのオートリロードが効かない

環境

Ruby on Rails
React
Docker for Windows

問題

Dockerコンテナ上でwebpack-dev-serverを動かしている時に、
Macでは、ホストでのファイルの変更を検知してauto-reloadされるのに、
Windowsではauto-reloadされない。

調査

Docker for Windowsのボリューム機能がうまくいっていないらしい。
それにより、ホスト側で行ったファイルの変更がコンテナ内に通知されず、auto-reloadが機能しない。

対処

WindowsのローカルにPython環境を構築し、以下のライブラリをインストール。

merofeev/docker-windows-volume-watcher

$ docker-volume-watcher コンテナ名 C:\some\directory

Windows上で実行することで、代わりにファイルの変更をコンテナ内に通知してくれる。

参考文献

・Cant get webpack hotreload with create-react-app and docker windows
・File system watch does not work with mounted volumes

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

Nginx経由でRailsをAWS環境で起動できないエラー時にまず確かめたいこと、使えるコマンド。

表題の内容に関して、チェックすべきポイントを
備忘録として以下に記す。

そもそもURLは正しいか?

Nginx導入の際に、unicorn.rb内の記述を変更していると思うので、
改めてブラウザからElastic IPでアクセスしてみる。

データベースは起動しているか?

Mysql使用時は以下を実行して確認。

ターミナル
[ec2-user@ip-XXX-XX-XX-XXX <リポジトリ名>] $ sudo service mysqld start
#上記実行後running...と表示されていればOK

Railsは起動しているか?

unicorn使用時には以下を実行して確認。

ターミナル
[ec2-user@ip-XXX-XX-XX-XXX <リポジトリ名>] $ bundle exec unicorn_rails -c config/unicorn.rb -E production -D #エラー文が表示されなければOK

master failed to start, check stderr log for details #このようなエラー文がでた場合は以下を実行して詳細を確認する。

[ec2-user@ip-172-31-23-189 <リポジトリ名>]$ less log/unicorn.stderr.log #実行すると上から古い順にエラーのログが表示されるので、原因を探す。

I, [2016-12-21T04:01:19.135154 #18813]  INFO -- : Refreshing Gem list
I, [2016-12-21T04:01:20.732521 #18813]  INFO -- : listening on addr=0.0.0.0:3000 fd=10
E, [2016-12-21T04:01:20.734067 #18813] ERROR -- : Can't connect to local MySQL server through socket '/tmp/mysql.sock' (2)  #このようにエラーの履歴がいくつか表示されるので、未解決のものを下から潰していく
/home/ec2-user/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activerecord-5.0.0.1/lib/active_record/connection_adapters/mysql2_adapter.rb:29:in `rescue in mysql2_connection'
/home/ec2-user/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activerecord-5.0.0.1/lib/active_record/connection_adapters/mysql2_adapter.rb:12:in `mysql2_connection'
#以下略

unicornのプロセスは停止されているか?

まず以下を実行してunicornのプロセスを確認。

ターミナル
[ec2-user@ip-172-31-23-189 <リポジトリ名>]$ ps aux | grep unicorn #左記を実行すると以下にプロセスが表示される。

ec2-user 17877  0.4 18.1 588472 182840 ?       Sl   01:55   0:02 unicorn_rails master -c config/unicorn.rb -E production -D
ec2-user 17881  0.0 17.3 589088 175164 ?       Sl   01:55   0:00 unicorn_rails worker[0] -c config/unicorn.rb -E production -D
ec2-user 17911  0.0  0.2 110532  2180 pts/0    S+   02:05   0:00 grep --color=auto unicorn

上記のように停止されていないことが確認された場合
unicorn_rails masterの行のプロセスID(PID)を確認する。
左から2列目、5桁の数字がPIDで、今回の場合は17877となる。

その後以下の通りにkillコマンドを実行する。

ターミナル
kill 17877

再度プロセスの確認を行い、以下のように config/unicorn.rb の記述がある行が削除されていれば成功。

ターミナル
[ec2-user@ip-XXX-XX-XX-XXX <リポジトリ名>]$ ps aux | grep unicorn
ec2-user 17911  0.0  0.2 110532  2180 pts/0    S+   02:05   0:00 grep --color=auto unicorn

Nginx内の記述を確認してみる。

確認するための記述は以下。

ターミナル
[ec2-user@ip-XXX-XX-XX-XXX <リポジトリ名>]$ sudo vim /開きたいファイルのURI

アプリケーション名や、Elastic IP、その他記述に誤りがないか確認する。

※vimの操作に関しては、以下にとても参考になる記事のリンクを載せました。
https://qiita.com/knife0125/items/5e91176c8401acd6bb0a

現状の理解

ターミナルでの作業はなかなか理解が難しい。
特にvimを使用する場面は理解が浅いので今後学習を強化していきたい。

誤り等ございましたらご指摘のほどよろしくお願いいたします。

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

【第9章】Railsチュートリアル 5.1(第4版) 発展的なログイン機能

はじめに

筆者は非IT業界から独学でRails学習中で、備忘録目的で執筆しています。
個人的に本章はこれまでの章より難易度が高かったです?(2周目確認・整理が必須と感じました、不足等あれば訂正致しますのでおっしゃっていただければ幸いです)
筆者は安川さん講義の動画版を模写するような形で学んだため、演習でなく全体を流す程度に参考にしてもらえたら嬉しいです。

<参考>
Ruby on Rails チュートリアル 第9章 永続的セッション(cookies remember me 記憶トークン ハッシュ)を解説
個人的に非常に理解しやすい記事でした。
(Cookieの攻撃手法などは改めて追記したいほどです)

9.1 Remember me 機能

Remember me とは

主にアカウント認証に用いられる機能の一つで、ユーザーがログイン時に入力したアカウント情報をサーバ側で持つ(UI視点での)機能。
ログインのときに「このユーザーIDとパスワードを情報を記録しますか?」のあれ。

< 参考 >
認証におけるRemember Meの仕組み
Remember-Me認証とSpringSecurity

今回のRemember me導入にはCookiesを用いる。

Cookies

Cookiesはユーザー(クライアント側)のブラウザにあるデータ保存領域。

<参考>
セッションとかクッキーとかよくわからないのでRailsチュートリアルでWebアプリケーション作りながら勉強してみた

前記事8章の基本的なログイン機構のsessionメソッドではユーザーIDを保存できたが、この情報はブラウザを閉じると消えてしまう。
 なので今回はセッションの永続化の第一歩として記憶トークン (remember token) を生成しcookiesメソッドによる永続的Cookiesの作成や、安全性の高い記憶ダイジェスト (remember digest) によるトークン認証にこの記憶トークンを活用し、セキュリティを考慮して以下の方針で永続的セッションを作成する。(公式より)

< 手順 >
1. 記憶トークンはランダムな文字列を生成して用いる。
2. ブラウザのcookiesにトークンを保存するときは有効期限を設定する。
3. トークンはハッシュ値に変換してからDBに保存する。
4. ブラウザのcookiesに保存するユーザーIDは暗号化しておく。
5. 永続ユーザーIDを含むcookiesを受け取ったら、そのIDでDBを検索し、記憶トークンのcookiesがDB内のハッシュ値と一致することを確認する。

 
※ トークンとはパスワードの平文と同じような秘密情報(コンピューターが作成・管理する情報)のこと。
 
ブランチ作成&チェックアウト

git checkout -b advanced-login

 
まずはパスワードダイジェストと同じように記憶トークンを保存する場所remember_digestの作成

$ rails generate migration add_remember_digest_to_users remember_digest:string

 
確認後、

$ rails db:migrate

 
 
手順1の記憶トークンのためのランダム文字列を出す。urlsafeはURLで使える文字列をランダムで生成するもの。これでできた文字列をユーザーさんのブラウザに置かせてもらう。

トークン生成用メソッドを追加する

app/models/user.rb
# ランダムなトークンを返す
  def User.new_token
    SecureRandom.urlsafe_base64
  end

 
rememberメソッドを追加する。このメソッドは、記憶トークンをユーザーと関連付け、記憶トークンに対応する記憶ダイジェストをDBに保存する。
ハッシュ変換するためhas_secureパスワードと同じように実装するが、attr_accessor(メソッドを定義するメソッド)を使って仮想のremember_token属性をUserクラスに追加する(rememberはよく使われるのでこれで下記意義メソッドも定義される)。

参考
Rails Tutorialの知識から【ポートフォリオ】を作って勉強する話 #9 永続セッション, cookie編
クッキー(cookie)とは?初心者でも分かるように図解

attr_accessor :remember_token

 || 下記の略
\\//
 \/

def remember=(taken)
  @remember = token
end

def remember
  @remember
end

今回はインスタンスメソッドなので、(ローカル変数にならないよう)selfを使う。

app/models/user.rb
class User < ApplicationRecord
  attr_accessor :remember_token

#省略

# 永続セッションのためにユーザ-をデータベースに記憶する(クッキー認証のための準備 トークン残し)
  def remember
    # new_tokenを発行する
    self.remember_token = User.new_token
    # remember_digestの中にUser.digest(remember_token)を入れる
    # update_attributeで無駄にvalidationをかける必要がなくハッシュ化して入る
    self.update_attribute(:remember_digest,User.digest(remember_token))
  end

ユーザのブラウザ内にあるキー?とremember_digestを使って認証させる必要がある。今回はauthenticateメソッドのレシーバに署名付きuser_id(cookie)を使う。これはcookieをブラウザに保存する前に安全に暗号化するためのもの(暗号化したuser_id)でブラウザに置いておく。

authenticateメソッド

引数を渡すと暗号化し、その文字列がパスワード(〇〇digest等)と一致するとUserオブジェクトを返し、間違っているとfalseを返すメソッド(公式第6章より)。

ユーザー(ブラウザ)保存のクッキーとremember_digestを一致させるのにauthenticateメソッドを使うが、このメソッドは@user(今回はemailでなく署名付きで暗号化されたuser_id ex.数字892350)を復号化して(数字892350 → user_id:5)をfind_byメソッドで呼び出してユーザーオブジェクトを引っ張って、その後authenticateメソッドで認証がはじまる。

引数にremember_tokenを渡せばDBの中身と確認できる(左selfが自分、右がDB)

 # 渡されたトークンがダイジェストと一致したらtrueを返す
  def authenticated?(remember_token)
    BCrypt::Password.new(self.remember_digest).is_password?(remember_token)
  end
app/controllers/sessions_controller.rb
def create

  log_in user
  remember user #=> SessionsHelperの。 ログイン後にrememberでnew_token発行してDB保存 save to DB
      #=> (cokies[:token] クッキー追加必要なのでsessionsヘルパーに引数付きremember(user)を追加する)

#略

ユーザーを記憶する

app/helpers/sessions_helper.rb
# ユーザーのセッションを永続的にする
  def remember(user) # => DB: remember_digest
    user.remember
    cookies.permanent.signed[:user_id] = user.id #=> coolies(クッキーに指定)、permanent(期限指定20年とする)、signed→暗号化
    cookies.permanent[:remember_token] = user.remember_token
  end

ヘルパー内のcurrent_userメソッドに追加する。

app/helpers/sessions_helper.rb
# 記憶トークンcookieに対応するユーザーを返す(新型)
  #=> ユーザオブジェクトが帰るか、nilが帰るか どちらか
  def current_user
    if (user_id = session[:user_id])
      @current_user ||= User.find_by(id: user_id)
    elsif (user_id = cookies.signed[:user_id]) #=> signedで復号化
      user = User.find_by(id: user_id)
      if user && user.authenticated?(cookies[:remember_token])
        log_in user
        @current_user = user
      end
    end
  end

9.1.3 ユーザーを忘れる

ユーザーのログアウトのためにはユーザーを忘れる(DBから消す)必要があるので

app/models/user.rb
# ユーザーのログイン情報を破棄する
  def forget
    self.update_attribute(:remember_digest, nil)
  end

Cookie側からも消す

app/helpers/sessions_helper.rb
# 永続的セッション(Cookie)を削除する
  def forget(user)
    user.forget
    cookies.delete(:user_id)
    cookies.delete(:remember_token)
  end

# 現在のユーザーをログアウトする
  def log_out
    forget(current_user)
    session.delete(:user_id)
    @current_user = nil
  end

9.1.4 2つの目立たないバグ

1つ目のバグは、この2つのタブで順にログアウトさせると、current_userがnilとなってlog_outメソッド内のforget(引数current_user → nilで)失敗してエラーになる(SS参照)。

スクリーンショット 2020-01-15 16.02.11.png

ユーザーがログイン中の場合にのみログアウト(log_outメソッドを実行)させるため、sessionsコントローラを編集する。

app/controllers/sessions_controller.rb
 # DELETE /logout
  def destroy
    log_out if logged_in? # ログイン中の場合のみログアウト(log_outメソッドを実行)する
    redirect_to root_url
  end

回帰バグを防ぐため統合テストにrootへリダイレクト後にdeleteを追記して確かめる。

test/integration/users_login_test.rb
assert_redirected_to root_url
# 2番目のウィンドウでログアウトをクリックするユーザーをシミュレートする
delete logout_path

テストしてRED → GREEN。

  
もう一つのバグは、2種類のブラウザで以下のようなときに発生。
1.ChromeとFirefoxでログイン(両方にCookieが入る)
2.Chromeでログアウト
3.Firefoxでログアウトせず閉じる
4.Firefoxでホームにアクセスしようとすると、エラー発生。

ChromeではログアウトしてるのでCookieの中身は削除され、(user.forgetメソッドによって)remember_digestもnilでBCryptは失敗するが、
FirefoxではCookieはあるが、remember_digestがnilなのでBCryptが失敗するだけでなく(デフォルトで)エラーを返してしまう。

digestダイジェストが存在しない場合のauthenticated?のテストとauthenticated?メソッドにBCrypt前にreturnを追加。

test/models/user_test.rb
 test "authenticated? should return false for a user with nil digest" do
    assert_not @user.authenticated?('')
  end
app/models/user.rb
def authenticated?(remember_token)
    return false if remember_digest.nil? #authenticated?を更新して、ダイジェストが存在しない場合に対応
    BCrypt::Password.new(self.remember_digest).is_password?(remember_token)
  end

備考: failuresとerrorsの違い

failures → 期待された値にならなかったときに出る。クライアントが欲しいシステムまたはコンポーネントの機能が性能要件を満足していない時などで発生。テスターが開発中に発見できる問題とされる。

errors → 期待の値とか関係なく異常・例外的なケースに出る。変数名のミス、間違ったログイン、誤ったループ条件などで発生する。筆者がチュートリアル迷走中によく現れる

<参考>
wrong、extra、error、bug、failure、faultの違い


9.2 [Remember me] チェックボックス

[remember me] チェックボックスをログインフォームview画面に追加する

app/views/sessions/new.html.erb
   <%= f.label :remember_me, class: "checkbox inline" do %>
        <%= f.check_box :remember_me %>
        <span>Remember me on this computer</span>
   <% end %>
app/assets/stylesheets/custom.scss
.checkbox {
  margin-top: -10px;
  margin-bottom: 10px;
  span {
    margin-left: 20px;
    font-weight: normal;
  }
}

#session_remember_me {
  width: auto;
  margin-left: 0;
}

チェックだけして「Log in」でエラーを表示すると、チェックなしの文字列「'0'」が選択されているのでok(「'1'」はあり)

スクリーンショット 2020-01-16 12.57.14.png

 remember_me: '0'

ログインフォームの編集が終わったので、チェックボックスがオンのときにユーザーを記憶し、オフのときには記憶しないようにする。

app/controllers/sessions_controller.rb
if user && user.authenticate(params[:session][:password])
  log_in user
  #  [remember me] チェックボックスの送信結果を処理する
  params[:session][:remember_me] == '1' ? remember(user) : forget(user)

9.3 [Remember me] のテスト

9.3.1 [Remember me] ボックスをテストする

単体テスト、統合テストとも同じメソッド名でテストヘルパーに入れる。
統合テストの特徴としては下記が注意どころ。
1.ActionDispatch::IntegrationTestクラスの中で定義
2.統合テストではsessionを直接取り扱えない為、代わりにSessionsリソースにpostリクエストを送信することで代用

test/test_helper.rb
class ActiveSupport::TestCase
省略

# テストユーザーとしてログインする
  def log_in_as(user)
    session[:user_id] = user.id
  end

end


class ActionDispatch::IntegrationTest

  # テストユーザーとしてログインする
  def log_in_as(user, password: 'password', remember_me: '1')
    post login_path, params: { session: { email: user.email,
                                          password: password,
                                          remember_me: remember_me } }
  end
end
test/integration/users_login_test.rb
 test "login with remembering" do
    log_in_as(@user, remember_me: '1')
    assert_not_empty cookies['remember_token']
  end

  test "login without remembering" do
    # クッキーを保存してログイン
    log_in_as(@user, remember_me: '1')
    delete logout_path
    # クッキーを削除してログイン
    log_in_as(@user, remember_me: '0')
    assert_empty cookies['remember_token']
  end

raiseの追加

raiseはコードブロック中に例外を発生させるもので、Kernelモジュールのインスタンスメソッドにあたる。

<参考>
begin~rescue~ensureとraiseを利用した例外処理の流れと捕捉について

今回はテストしていなかったcurrent_use内にraiseを挿入し、もう一度テストがパスすれば、この部分がテストされていない(マズい)ことがわかる。

app/helpers/sessions_helper.rb
# 記憶トークンcookieに対応するユーザーを返す
  def current_user
    if (user_id = session[:user_id])
      @current_user ||= User.find_by(id: user_id)
    elsif (user_id = cookies.signed[:user_id])
      raise       # テストがパスすれば、この部分がテストされていない(マズい状況な)ことがわかる
      user = User.find_by(id: user_id)

raiseで例外を入れてもテストが通過(error 0)してしまうので、別途ファイルにテストを追加する。
テスト内容としては以下の通り(公式より)
1. user変数(@user)を定義
2. 渡されたユーザー(@user)をrememberメソッドで記憶
3. current_userが、渡されたユーザー(@user)と同じであることを確認
4. ユーザーの記憶ダイジェストが記憶トークンと正しく対応していない場合に現在のユーザーがnilになるかどうかをチェック

test/helpers/sessions_helper_test.rb
# 永続的セッションのテスト
require 'test_helper'

class SessionsHelperTest < ActionView::TestCase

  def setup
    @user = users(:michael)
    remember(@user)
  end

  test "current_user returns right user when session is nil" do
    assert_equal @user, current_user
    assert is_logged_in?
  end

  test "current_user returns nil when remember digest is wrong" do
    @user.update_attribute(:remember_digest, User.digest(User.new_token))
    assert_nil current_user
  end
end

無事エラー確認できたのでok(raise外すとGREEN)

Gitコミット、herokuデプロイ(本番意識ならメンテナンスモード意識などが必要だが、今回は利用中のユーザー無視のためmasterへ)。

$ git add -A
$ git commit -m "Finish ch09"
$ git checkout master
$ git merge advanced-login
$ git push heroku master
$ heroku run rails db:migrate

備考: メンテナンスモード

herokuにデプロイしても、heroku上でマイグレーションを実行するまでの間は一時的にアクセスできない状態 (エラーページ) になるのでトラフィック( (一定時間に)通信回線やネットワーク上で送受信される信号やデータ量のこと )の多い本番サイトでは変更する前にメンテナンスモードをオンが推奨とされる。

$ heroku maintenance:on
$ git push heroku
$ heroku run rails db:migrate
$ heroku maintenance:off

2つ開いて片方でログイン/ログアウトしてCookieクリア等の確認ができれば完了!

終わりに

最後まで見て頂きありがとうございました!

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

rspecの始め方

Gemfileに

gem 'rspec'
group :development, :test do
  gem 'rspec-rails'
end

コマンドラインで

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

webpackerでBootstrapをRailsアプリに反映させたい!

webpackerはwebpackをRailsで使えるように作られたgemパッケージである。
先日、以下の記事に従いwebpackerを使ってBootstrapをRailsのアプリにインストールしたのだが、上手く反映されていない。

RailsでBootstrapとFont-AwesomeはWebpackerで今時っぽく使おう

結果として、windows環境で開発を行っていたことが原因であった。
この記事ではwindows環境でwebpackerを使ったBootstrapの反映方法について示す。
(※2020/01/18に上記記事の執筆者の方からご連絡を頂き一部を修正)

前提

僕のスペック

  • プログラミング学習を始めて1ヶ月
  • HTML/CSS/Javascript/Ruby/Railsをprogateで一応学習済
  • Ruby on rails でポートフォリオを作成中

開発環境

  • 端末 : LENOVO ideapad 530S-14ARR
  • OS : Windows 10 Home ver.1809
  • シェル : PowerShell 5.1.17763.771
  • Ruby : 2.6.4
  • rails : 5.2.4.1

なぜ反映されないのかを探る

Bootstrapをwebpackerを利用してRailsアプリに反映させたい!だけど、そのやり方が分からない。BootstrapをRailsアプリにインストールするのは以下の記事通りにやれば上手くいくはず。

RailsでBootstrapとFont-AwesomeはWebpackerで今時っぽく使おう

しかし、リンク先の通りにインストールしたが、どうやらBootstrapがアプリに反映されていない。何かやり方を間違えたのかしら?なぜ反映されないのかを探したくても、どういう仕組みでBootstrapがアプリに反映されるのかがそもそも分かっていないので、探しようがない。そこでその仕組みについてまずは理解することにした。

BootstrapがRailsアプリに反映される仕組みとは

webpackerを導入すると、app/javascript/packというディレクトリが自動で作られる。どうやらwebpackはデフォルトでここのディレクトリを認識できるように設定されているようだ(これをエントリーポイントと呼ぶらしい)。
だからこのディレクトリの中にcssやjavascriptのファイルを置いておけば読み込んでくれるという訳だ。

そして読み込んだこれらのファイルを呼び出す方法として、
javascript_pack_tag(←javascriptファイルを呼び出すコマンド)
stylesheet_pack_tag(←css(scssも)ファイルを呼びだすコマンド)
があるという訳みたい。こいつらを任意のerbファイルに書き込めば読み込んでくれるという訳だ。

ここまで分かったうえで、Bootstrapを利用するには、Bootstrapの仕様を読み込むコマンドをcssファイルに書き込み、それをapp/javaxcript/packにおいておけばいいことになる。さらに、BootstrapはjQueryやpoper.jsも必要とするのでそいつらを呼び込むコマンドを書いたjavascriptファイルもapp/javascript/packにおいておけば使えるはずなのである。

具体的にcss、javascriptファイルにどのようなコマンドを書き込めばよいのか?

CSS

app/javascript/pack/application.scssというディレクトリを作り以下の記事に書かれていた下記のコードを記述すると
(再掲)RailsでBootstrapとFont-AwesomeはWebpackerで今時っぽく使おう

app/javascript/pack/application.scss
@import '~bootstrap/scss/bootstrap';

このコードの意味は「'~bootstrap/scss/bootstrap'にあるファイルを読み込むっす!」という意味。ここでこのpathの先頭にある「~」はホームディレクトリの位置を指すそうだ。windows10の場合にはホームディレクトリにはC:\Users\ユーザー名がデフォルトとして設定されている。なのでこのpathをきっちり書くとC:\Users\ユーザー名\bootstrap\scss\bootstrapということになる。…はずだった

しかし、僕のPCのコマンドプロンプトに以下を入力しても

$ cd ~
指定されたパスが見つかりません。

となってしまう。windows10のコマンドプロンプトでは~はホームディレクトリを指すものとして定義されていないのか???(多分そう。このコマンド等はMacOSやLinuxを対象としたもの)

何はともあれこのpathが機能していないので、Bootstrapが使えなかったのであろう。このpathを以下のように書き直すことにした。

app/javaxcript/pack/application.scss
@import 'bootstrap/dist/css/bootstrap.css';

すると無事Bootstrapが反映されることとなった。
このPATHについて説明すると、

$ yarn add bootstrap@4.3.1 jquery popper.js

でインストールしたbootstrapのcssファイルはアプリ名/node_modules/bootstrap/dist/css/bootstrap.cssに保存される。なのでこれを呼び出す必要がある。しかし、node_module以上のPATHは明記しなくても理解してくれるので、それより下のPATHを書いてあげればよいということだ。

javascript

一方で、Bootstrapを機能させる上で必要となるiQuerypopper.jsなどのファイルはそれぞれ、
アプリ名/node_modules/jquery/dist/jquery.js
アプリ名/node_modules/popper.js/dist/popper.js
に保存されているため、こいつらのPATHをjavascritのファイルに書き込んでやればよい。具体的には以下のようにやる。

app/javaxcript/pack/application.js
import 'jquery/dist/jquery.js'
import 'popper.js/dist/popper.js'
import 'bootstrap/dist/js/bootstrap'

3行目にiQueryでもpopperでもないものがインポートされている。このファイルの正体は僕は分かっていないのだが、こいつがどうも必要らしい。今は、jQueryやpopper以外に必要なその他のファイル的なイメージだが、いずれ詳しく調べるつもりだ。

現状を整理しよう

これまでの工程でそれぞれ、application.scssapplication.jsにpathを書き込んだ。こいつらはエントリーポイントのjavascript/packのところにおいておけば、読み込みの対象として認識されるという訳だ。
あとは、このファイルたちを読み込むコマンドを以下のように書いてやれば無事Bootstrapは反映されるはず!

app/views/layout/pplication.html.erb
<%= stylesheet_pack_tag 'application.scss' %>
<%= javascript_pack_tag 'application.js' %>

この様にやってみると、上手くBootstrapが反映されているようでした。

まとめ

何故、以下の記事の通りにやってもうまく動かなかったのか?
(再再掲)RailsでBootstrapとFont-AwesomeはWebpackerで今時っぽく使おう
その原因は

上記記事はMacOSやLinuxを使用を前提としていたから。

でした。

また、ご指摘頂いた方に対する感謝の意をここに示します。
引き続き、この記事に誤りがあった場合にはご指摘頂けると幸いです。

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

[Rails]serializeなcolumnのdefault値を空の配列にしたい。

やりたいこと

migration
class Users < ActiveRecord::Migration[5.1]
  def change
    create_table :users do |t|
      t.integer  :id
      t.string   :hoges, null: false, default: []    # これ
    end
  end
end

でもこれじゃ

== 20200109052420 Users: migrating ============================
-- create_table(:search_histories)
rails aborted!
StandardError: An error has occurred, all later migrations canceled:

can't quote Array
/Users/lyrical_school/project/disappearing_planet/db/migrate/20200109052420_create_users.rb:3:in `change'
bin/rails:9:in `require'
bin/rails:9:in `<main>'

空配列を登録した時って何が保存されるんだっけ?

DB確認してみる。

--- []
が登録されてた。

じゃあそれをdefaultに設定してみる。

migration
class Users < ActiveRecord::Migration[5.1]
  def change
    create_table :users do |t|
      t.integer  :id
      t.string   :hoges, null: false, default: '--- []'
    end
  end
end

完璧。

殴り書きだから後日ちゃんとまとめる。まとめないかも。

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

[Rails] ActiveAdmin編集フォームをカスタマイズする!

最近の勉強で学んだ事を、ノート代わりにまとめていきます。
主に自分の学習の流れを振り返りで残す形なので色々、省いてます。
Webエンジニアの諸先輩方からアドバイスやご指摘を頂けたらありがたいです!

編集できる項目をカスタマイズしたい

ActiveAdminで作成した管理画面で編集できる項目を指定して表示するのが今回の目的です!

公式ドキュメントによると
formの出力を調整するやり方が書いてありました。

app/admin/users.rb
ActiveAdmin.register User do
 省略〜
  PERMIT_PARAMS = [:name, :email, :gender, :age] # 編集を許可するフィールドを定義
  permit_params PERMIT_PARAMS

  form do |f|
    f.semantic_errors # エラーメッセージ表示
    f.inputs do
      PERMIT_PARAMS.each {|attr| f.input attr} # 入力フィールドを表示
    end
    f.actions # submit/cancelボタンを表示
  end
end

これで完成!ActiveAdmin凄いですね!

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

【Rails】簡単にDatetimepickerを使う方法

はじめに

個人的にオススメなDatetimepickerについて、紹介しようと思います。
導入がめちゃくちゃ簡単です。

できあがり例

こんな感じで、日付と時間を選ぶと実装することができます。

image.png

個人的にこのdatetimepickerが優れていると感じる点は以下です。

  • はじめからカレンダーと時間が表示されていること
  • ボタンをクリックする回数が2回で入力ができること

他のdatetimepickerだと、日付をクリックしないと時間の欄が表示されないため作業量を一瞬で把握できません。
微妙な差ではありますが、ユーザーが少しでも楽な方を考えました。

使い方

まず、GemFileに以下のgemを記載して、bundle installをしてください。

GemFile
gem 'jquery-datetimepicker-rails'
$ bundle install

次に、app/assets/stylesheets/application.cssに以下の1行を追加してください。

app/assets/stylesheets/application.css
*= require jquery.datetimepicker

次に、app/assets/javascripts/application.jsに以下の1行を追加してください。
(コメントはそのままで大丈夫です。)

app/assets/javascripts/application.js
//= require jquery.datetimepicker

下記の2行を追加することで、日本語対応のdatetimepickerを適用する準備が整いました。
(私の環境では日本語対応になりませんでした。どなたか分かる方がいれば教えていただきたい。。)

application.js に 下記を書いていますが、各モデルのみに適用したい場合はcoffee scriptに書いても問題ありません。(ただし、その場合はcoffee scriptへの変換が必要。)

app/assets/javascripts/application.js
// 日本語化対応用
$.datetimepicker.setLocale('ja');

// datetimepickerクラスにdatetimepickerを適用する。
$('.datetimepicker').datetimepicker();

今回は一例として、postモデルのpost_datetimeカラムにdatetimepickerを適用しています。
ポイントは、class に datetimepicker をつけることです。そうすることでその部分に適用されます。
slimに馴染みのない方、ごめんなさい。。

new.html.slim
= form_for @post do |f|
  = label :post, :post_datetime
  = f.text_field :post_datetime, value: "", class: "datetimepicker", autocomplete: "off"
// class に datetimepicker をつけるとその部分に適用される。

まとめ

いかがでしょうか。導入は簡単でしたでしょうか。
不備などがあれば、申しつけください。

参考

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

【YAML】Railsのdatabase.ymlについてなんとなく分かった気になっていた記法・意味まとめ

はじめに

「そういえば、yamlって良く使うくせにちゃんと調べたことがないな」
と思い、特徴的な書き方をする機能3つに絞ってまとめました。

YAMLの公式はこちら

Ruby on Railsで良く使う、database.yamlを例に説明します。

環境

OS: macOS Catalina 10.15.1
Ruby: 2.6.5
Rails: 6.0.2.1

そもそもyamlって何?

YAML is a human friendly data serialization
standard for all programming languages.

公式より抜粋しました。

「YAMLは全てのプログラミング言語で使える、人間に優しいデータの書き方だよ!」ということですね。

要するに、複雑なデータ構造をシンプルに表現するファイル形式です。

ここからRuby on Railsapp/config/database.ymlを例に、他ではあまり見ない記法について説明していきます。

例:database.yml

コメントでどこで何を使っているのか書いています。
それぞれの説明は後述します。

app/config/database.yml
default: &default #アンカー
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password: password
  host: db

development:
  <<: *default #エイリアス・ハッシュのマージ
  database: app_name_development

test:
  <<: *default #エイリアス・ハッシュのマージ
  database: app_name_test

production:
  <<: *default #エイリアス・ハッシュのマージ
  database: app_name_production
  username: app_name
  password: <%= ENV['APP_NAME_DATABASE_PASSWORD'] %>

アンカー: 名前をつける機能

&default

これをアンカーと呼びます。
該当部に名前をつける機能です。

実例

default:
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password: password
  host: db

上記ハッシュ(連想配列)のことをdefaultと呼ぶよという宣言をするなら

アンカーを追記する。

app/config/database.yml
default: &default
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password: password
  host: db

アンカーを書いておくことにより、次のエイリアスが使えるようになります。

エイリアス: 名前をつけたものを呼び出す

*default

アンカーを付けたものを呼び出すときに使います。

これがどう使われているのかは以下ハッシュのマージを絡めて後述します。

ハッシュのマージ: 一つにまとめる

※2020/01/17追記 YAMLでの正式名称はハッシュではなくマッピングです。
ここではRuby on Railsを例にしているため、わかりやすくするためハッシュと表現しています。
(scivolaさんアドバイスありがとうございます:relaxed:
参考:プログラマーのための YAML 入門 (初級編)

<<: *default

<<:の右側にあるものを、該当部にまとめるという意味になります。

以下例をご覧ください。

実例

ここまで出てきた

  • アンカー
  • エイリアス
  • ハッシュのマージ

の知識を使うと、以下のdevelopmentで何を意味しているのかが説明できます。

app/config/database.yml
default: &default #ここで共通部分にアンカーで名前をつけておくと
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password: password
  host: db

development:
  <<: *default #ここでdefaultとして呼び出し、スッキリ書ける
  database: app_name_development


つまり、developmentに以下が書かれているのと同じ意味になります。

app/config/database.yml
#...略
development:
  #ここからdefault
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password: password
  host: db
  #ここまでdefault
  database: app_name_development

以上です!

おわりに

最後まで読んで頂きありがとうございました:bow_tone1:

どなたかの参考になれば幸いです:relaxed:

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

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

rails server 起動しない。 undefined local variable or method `index' for #<ActionDispatch::Routing::Mapper:

忘備録用

undefined local variable or method `index' for #<ActionDispatch::Routing::Mapper:

routes.rbファイルの記述内容に間違いがあった。(コロン(:)の書き忘れ)

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