20200913のRailsに関する記事は23件です。

rails generate controllerした時に不要なファイルを出さない設定(RSpec編)

Ruby on Railsで新しくコントローラーを作る時に、不要なファイルを出さない様に設定をいれるか、オプションを指定する方法です。

システム環境

環境に依存する話ではないと思いますが、私の環境は以下の通りです。

  • Ruby 2.6.3
  • Rails 6.0.3.3
  • RSpec 3.9

Before:設定がない時

デフォルトの状態でrails g controller hogesすると、以下の様なファイルが生成されます。昔はcontroller specも生成された気がしますが、controller specは公式が推奨していないので、今は出ないみたいです。

$ rails g controller Hoges new
      create  app/controllers/samples_controller.rb
       route  get 'samples/new'
      invoke  erb
      create    app/views/samples
      create    app/views/samples/new.html.erb
      invoke  rspec
      create    spec/requests/samples_request_spec.rb
      create    spec/views/samples
      create    spec/views/samples/new.html.erb_spec.rb
      invoke  helper
      create    app/helpers/samples_helper.rb
      invoke    rspec
      create      spec/helpers/samples_helper_spec.rb
      invoke  assets
      invoke    scss
      create      app/assets/stylesheets/samples.scss

でも、「controllerごとにcssを分けないよね」とか「Helperも使わないよね」ということもあるんじゃないでしょうか(←私)。
生成したファイルを手動で毎回消していましたのですが、面倒なので設定してしまうことにしました。

設定の方法

RSpecをテストフレームワークに設定している方はすでにconfig/application.rbにconfig.generators doの記載があると思いますので、その前後に以下の様に記述を足します。

config/application.rb
config.generators do |g|
      g.skip_routes true #routesの自動記述をoff
      g.helper false #app/helperの自動生成をoff
      g.assets false #同名のcssやjsの生成をoff
      g.test_framework :rspec, #標準のテストをRSpecに変更
      controller_specs: false, #公式で非推奨のためoff
            view_specs: false, #Request/Systemで代替するのでoff
         routing_specs: false #使用頻度低いのでoff
end

skip_routesは結局resouces :hoge, only: :newの様にresourcesで書き換えてしまうことが多いので、デフォルトではオフにしています。
app/hoge_helper.rbも意外と使わないのでオフ。assetsも使用頻度が少ないのでオフ。このあたりの不要なファイルは、後から他の人が見た時のことを考えて記載しない場合は消しておいた方が良いらしいです(確認の手間も増えますしね)。

After:設定がある時

設定を入れてrails g controller hoges newを実行すると、結果は以下の通りです。

$rails g controller Samples new
      create  app/controllers/samples_controller.rb
      invoke  erb
      create    app/views/samples
      create    app/views/samples/new.html.erb
      invoke  rspec
      create    spec/requests/samples_request_spec.rb

これである程度スッキリしたのではないでしょうか。

ご参考:コマンドのオプションで指定する方法

config/application.rbに記載して設定するのではなく、都度オプションで指定する方法もあります。

$rails g controller samples new --no-assets
Option 省略されるもの
--no-assets app/assets/以下の.js, .cssファイル
--no-helper app/helpers以下のHelperファイル
--no-test-framework Specファイル。上の場合だとrequest specが省略されます
--skip-routes
--skip-template-engine
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

herokuでのデプロイの仕方

【概要】

1.手順

2.方法

1.手順

❶brew tap heroku/brew && brew install heroku
❷gem 'rails_12factor'
❸heroku用ファイルの作成
❹heroku用DBの作成
❺環境変数の設定
❻heroku上でmasterにpush
❼heroku上でマイグレーションの実施


2.方法

❶brew tap heroku/brew && brew install heroku

ターミナル
% brew tap heroku/brew && brew install heroku

を実行しましょう!ミスしないようにherokuに公開したいアプリの該当ファイルで行うことをお勧めします!
そして% heroku login --interactive
でherokuにログインしておきます!

❷gem 'rails_12factor'

gemfile
group :production do
  gem 'rails_12factor'
end

と記載し、% bundle installをします!
そしてmasterにコミットしておきます。

❸heroku用ファイルの作成

ターミナル
% heroku create *****

でherokuに公開したいアプリの名前を作成します。

❹heroku用DBの作成

ターミナル
% heroku addons:add cleardb

と記載しデーターベースを作成します。

ターミナル
% heroku config | grep CLEARDB_DATABASE_URL

これにより、CLEARDB_DATABASE_URLが出てくるので、使用状況によりmysql2を使用している方は、変更しましょう。

>変更方法<

ターミナル
% heroku config:set DATABASE_URL = 'mysql2:*****'

****の部分は% heroku config | grep CLEARDB_DATABASE_URLでてきたものをコピペします。

❺環境変数の設定

ターミナル
% heroku config:set RAILS_MASTER_KEY=`cat config/master.key`

こうすることで、パスワードをファイルの中に書き込まず、見られないようにしてサイトを見るようにできます。

❻heroku上でmasterにpush

ターミナル
% git push heroku master

こちらでherokuにpushできます。

❼heroku上でマイグレーションの実施

ターミナル
% heroku run rails db:migrate

DBを❹で作っているのでそれをローカルと同じようにherokuでもマイグレーションしていきます。

これでherokuにアプリを公開できるようになります。
ただパスワードを設定していないので誰でもそのサイトを覗ける状態になっているので注意してください。
環境変数はあくまでも自分のサイトを乗っ取られないようにするためものです。

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

【Rails,heroku】多量csvを分割splitしてDBにseedして流し込む

参考記事

http://yu0105teshi.hateblo.jp/entry/2016/08/03/180603
https://qiita.com/masaki7555/items/d65f56958020cbca5ee0

この記事の内容を使っているサイト:薬価計算medipra

この記事で紹介すること

行数が多いCSVファイルをコマンドで分割する方法
CSVファイルを使ってDBにデータを入れる

行数が多いCSVファイルをコマンドで分割する方法

ではまずCSVの準備をします。

CSVファイルの準備

必要なCSVファイルを用意します。
今回は医薬品データベースを使用します。

参照:社会保険診療報酬支払基金医薬品マスター

ここから、「全件分ファイル」をダウンロードすると、約21,000行あるCSVファイルが手に入ります。
ターミナルを開いてCSVファイルをダウンロードしたディレクトリに移動しましょう。
そこで wc -l [ファイル名] を実行すると、指定したファイルの行数がカウントされます。
Image from Gyazo

ちなみにwcコマンドの主なオプションは以下の通りです。

短いオプション 長いオプション 意味
-c --bytes バイト数の表示
-m --chars 文字数の表示
-l --lines 改行の数を表示
-w --words 単語数を表示

CSVファイルの分割split

それでは次にダウンロードしたCSVファイルを分割します。

そもそもなぜ分割するのかというと、
今回の場合は、使ってるherokuのAdd-onの設定によります。

今、薬価計算サイトmedipraで使用しているclearDB MySQLの有料プランがPunch($9.99)で、
1時間当たり18,000のクエリしか受け付けないためです。
なので、ダウンロードしたファイルをそのままDBに流し込もうとすると、クエリが足りなくなりエラーとなってしまいます。
たまに更新するDBのためにより上の有料プランを契約するのも、ランニングコストが増えるので初めの段階では不要ですよね。

また、CSVを1行流し込むのにクエリが3回飛ぶようなので、6,000行以下でファイルを分割します(今回は余裕を持って5,900行で分割してます)

さて、分割してみましょう。

分割コマンドは$ split -l [行数] [分割するファイル] [分割したファイルの接頭辞]で、行数を指定して分割できます。

Image from Gyazo

赤い下線の--additional -suffix=.csvのところは、分割後のファイルの拡張子をCSVにしたくて付け足したのですが、デフォルトでマックに入っているのではこのオプションは使えないようです。

参考:ファイル分割時に拡張子を揃えたい-teratail

というわけで次のコマンドで分割。

$ split -l 5900 y_ALL20200825.csv medipra-

このコマンドで
medipra-aa, medipra-ab, medipra-ac, medipra-adの4つのファイルができました。

今回はcsvファイルにしたいので、名前の変更で.csvにすればCSVファイルとして使えるようになります。

分割後のCSVファイルが文字化けしていたら

分割後にVSCodeで開いてみると文字化けしていることがあります。
そんな時はExcelで開いて「名前をつけて保存」をクリックして、
ファイル形式をCSV UTF-8と文字コードを指定して保存してみましょう。

Image from Gyazo

CSVファイルを使ってDBにデータを入れる

大まかな解説

ファイル構造は以下のようになります。
Image from Gyazo
(seeds.rbは不要ファイルなので気にしないでください)

dbディレクトリの直下にCSVファイルをおき、これらをseeds>product1.rbでDBに流し込みます。

先に、今回使うコマンドはこちら。(herokuの環境を想定しています)

$ heroku login
$ heroku run bundle exec rake db:migrate:reset
$ heroku run bundle exec rake db:seed:product1

db:migrate:resetについてはこちらを参照してください。
参考記事:rake db:reset と rake db:migrate:reset の違い

seeds>product1.rbの準備

今回は、多量のCSVのデータを複数に分けて流すのでseedファイルを必要な分作ります。
今回なら、CSVファイルが4つありますので4つのseedが必要になります。

seedsディレクトリを作って、そのしたのディレクトリにファイルを作ります。

例えば以下のようにファイルを作ります。

product1.rb
require "csv"
date = "2020-04-01"
i = 1

CSV.foreach('db/medipra20200825-a.csv') do |info|
  Product.create!(
    price: info[11],
    change_category: info[0],
    master_type: info[1],
    pharmaceutical_code: info[2],

    ・・・中略・・・ 

    expiration_date: info[33],
    standard_name: info[34]
  )
end

db/medipra20200825-a.csvの部分をそれぞれファイル名を変えてproduct1.rb~product4.rbまで作ります。

こちらも参考にどうぞ
【Rails】rake seedコマンドでCSVファイルからDBに読み込ませる方法

seedコマンドでDBに流し込む

seedファイルを用意したら、コマンドで流し込みます。

まずherokuにログイン

$ heroku login

続いて、前の古いDBがあるならresetします

$ heroku run bundle exec rake db:migrate:reset

これで最新のマイグレーションファイルのDBが作り直すことができます。

最後にseedを実行。
product1~4まであるので、4回実行することになります。

$ heroku run bundle exec rake db:seed:product1 

クエリが1時間当たり18,000までなので、
1回このコマンドをやったら、1時間開けて~:product2に変更して、
再度コマンドを実行します。

若干手間ではありますが、これ以上課金するのもまだもったいないので。(費用対効果、大事ね)

以上で完成です。

間違いのご指摘などありましたらご教示いただけましたら幸いですm(__)m

それでは今回は以上です。
最後までありがとうございます。

この記事の内容を使っているサイト:薬価計算medipra

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

【Rails,heroku】多量csvをsplitしてseedして流し込む

参考記事

http://yu0105teshi.hateblo.jp/entry/2016/08/03/180603
https://qiita.com/masaki7555/items/d65f56958020cbca5ee0

この記事の内容を使っているサイト:薬価計算medipra

この記事で紹介すること

行数が多いCSVファイルをコマンドで分割する方法
CSVファイルを使ってDBにデータを入れる

行数が多いCSVファイルをコマンドで分割する方法

ではまずCSVの準備をします。

CSVファイルの準備

必要なCSVファイルを用意します。
今回は医薬品データベースを使用します。

参照:社会保険診療報酬支払基金医薬品マスター

ここから、「全件分ファイル」をダウンロードすると、約21,000行あるCSVファイルが手に入ります。
ターミナルを開いてCSVファイルをダウンロードしたディレクトリに移動しましょう。
そこで wc -l [ファイル名] を実行すると、指定したファイルの行数がカウントされます。
Image from Gyazo

ちなみにwcコマンドの主なオプションは以下の通りです。

短いオプション 長いオプション 意味
-c --bytes バイト数の表示
-m --chars 文字数の表示
-l --lines 改行の数を表示
-w --words 単語数を表示

CSVファイルの分割split

それでは次にダウンロードしたCSVファイルを分割します。

そもそもなぜ分割するのかというと、
今回の場合は、使ってるherokuのAdd-onの設定によります。

今、薬価計算サイトmedipraで使用しているclearDB MySQLの有料プランがPunch($9.99)で、
1時間当たり18,000のクエリしか受け付けないためです。
なので、ダウンロードしたファイルをそのままDBに流し込もうとすると、クエリが足りなくなりエラーとなってしまいます。
たまに更新するDBのためにより上の有料プランを契約するのも、ランニングコストが増えるので初めの段階では不要ですよね。

また、CSVを1行流し込むのにクエリが3回飛ぶようなので、6,000行以下でファイルを分割します(今回は余裕を持って5,900行で分割してます)

さて、分割してみましょう。

分割コマンドは$ split -l [行数] [分割するファイル] [分割したファイルの接頭辞]で、行数を指定して分割できます。

Image from Gyazo

赤い下線の--additional -suffix=.csvのところは、分割後のファイルの拡張子をCSVにしたくて付け足したのですが、デフォルトでマックに入っているのではこのオプションは使えないようです。

参考:ファイル分割時に拡張子を揃えたい-teratail

というわけで次のコマンドで分割。

$ split -l 5900 y_ALL20200825.csv medipra-

このコマンドで
medipra-aa, medipra-ab, medipra-ac, medipra-adの4つのファイルができました。

今回はcsvファイルにしたいので、名前の変更で.csvにすればCSVファイルとして使えるようになります。

分割後のCSVファイルが文字化けしていたら

分割後にVSCodeで開いてみると文字化けしていることがあります。
そんな時はExcelで開いて「名前をつけて保存」をクリックして、
ファイル形式をCSV UTF-8と文字コードを指定して保存してみましょう。

Image from Gyazo

CSVファイルを使ってDBにデータを入れる

大まかな解説

ファイル構造は以下のようになります。
Image from Gyazo
(seeds.rbは不要ファイルなので気にしないでください)

dbディレクトリの直下にCSVファイルをおき、これらをseeds>product1.rbでDBに流し込みます。

先に、今回使うコマンドはこちら。(herokuの環境を想定しています)

$ heroku login
$ heroku run bundle exec rake db:migrate:reset
$ heroku run bundle exec rake db:seed:product1

db:migrate:resetについてはこちらを参照してください。
参考記事:rake db:reset と rake db:migrate:reset の違い

seeds>product1.rbの準備

今回は、多量のCSVのデータを複数に分けて流すのでseedファイルを必要な分作ります。
今回なら、CSVファイルが4つありますので4つのseedが必要になります。

seedsディレクトリを作って、そのしたのディレクトリにファイルを作ります。

例えば以下のようにファイルを作ります。

product1.rb
require "csv"
date = "2020-04-01"
i = 1

CSV.foreach('db/medipra20200825-a.csv') do |info|
  Product.create!(
    price: info[11],
    change_category: info[0],
    master_type: info[1],
    pharmaceutical_code: info[2],
    kanji_significant_digits: info[3],
    kanji_name: info[4],
    kana_significant_digits: info[5],
    kana_name: info[6],
    unit_code: info[7],
    unit_significant_digits: info[8],
    unit_name: info[9],
    price_type: info[10],
    spare: info[12],
    n_p_s_p_drugs: info[13],
    nerve_destroyer: info[14],
    biologics: info[15],
    generic: info[16],
    spare_2: info[17],
    dental_drugs: info[18],
    contrast_agent: info[19],
    injection_volume: info[20],
    listing_type: info[21],
    product_name_relations: info[22],
    old_price_type: info[23],
    old_price: info[24],
    name_change_category: info[25],
    kana_name_change_category: info[26],
    dosage_form: info[27],
    spare_3: info[28],
    changed_date: info[29],
    abolition_date: info[30],
    standard_code: info[31],
    order_number: info[32],
    expiration_date: info[33],
    standard_name: info[34]
  )
end

db/medipra20200825-a.csvの部分をそれぞれファイル名を変えてproduct1.rb~product4.rbまで作ります。

こちらも参考にどうぞ
【Rails】rake seedコマンドでCSVファイルからDBに読み込ませる方法

seedコマンドでDBに流し込む

seedファイルを用意したら、コマンドで流し込みます。

まずherokuにログイン

$ heroku login

続いて、前の古いDBがあるならresetします

$ heroku run bundle exec rake db:migrate:reset

これで最新のマイグレーションファイルのDBが作り直すことができます。

最後にseedを実行。
product1~4まであるので、4回実行することになります。

$ heroku run bundle exec rake db:seed:product1 

以上で完成です。

間違いのご指摘などありましたらご教示いただけましたら幸いですm(__)m

この記事の内容を使っているサイト:薬価計算medipra

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

【Rails】データを削除する様々な方法

早見表

メソッド 説明 削除する範囲
destroy 一件のデータを削除 対象データと関連データを削除
destroy_all 複数データを削除 対象データと関連データを削除
delete 一件のデータを削除 対象データのみ削除
delete_all 複数データを削除 対象データのみ削除

関連するデータdependent: :destroyで依存関係にあるデータのこと

使い方

# destroyメソッド
User.find(1).destroy

# destroy_allメソッド
User.where(id: 1..5).destroy_all

# deleteメソッド
User.find(1).delete

# delete_allメソッド
User.where(id: 1..5).delete_all
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[heroku] run rails db:migrateがうまくいかない

環境

・Rails 6.0.3.2
・mysql Ver 14.14 Distrib 5.6.47
・osx10.15
・herokuへデプロイ

はじめに

Railsでアプリを作成しherokuへアップロードするときにうまくいかなかった時の対応です。
開発環境ではMySQLherokuではDBがPostgreSQLのためその対応が必要。
その途中段階でエラーが生じた。
順番に書いていきます。

PostgreSQLへの対応

まずはGemfileへ以下を記述。本番環境でPostgreSQLを使うというものです。自分は一番下に書きました。

group :production do
  gem 'pg'
end

Gemfileを変更したので忘れずにbundle installをします。
まずここで一つ目のエラーでました。そのままbundle installすると自分の開発環境にはPostgreSQLがないのでエラー発生。

対応はgroup :produciton endのところを飛ばすコマンドをターミナルで実行します。

$ bundle install --without production 

データベースへ接続する記述をconfigフォルダの中にあるdatabase.ymlへします。
一番下へ追記しました。

production:
  <<: *default
  adapter: postgresql
  encoding: unicode
  pool: 5

自分の場合は記述は以上でした。
参考になった記事はコチラです!
【初心者向け】railsアプリをherokuを使って確実にデプロイする方法【決定版】
https://qiita.com/kazukimatsumoto/items/a0daa7281a3948701c39

herokuへデプロイ

今回ここは割愛します。
デプロイ完了まで進みます。

ここでdb:migrate

heroku run rails db:migrateをしますがエラー発生!
いろいろ見ているうちにこのエラーにやっと出会う。。。

ターミナルには中ほどに以下のような記述が。

Caused by:
PG::ConnectionBad: could not connect to server: No such file or directory
    Is the server running locally and accepting
    connections on Unix domain socket "/var/run/postgresql/.s.PGSQL.5432"?

原因

調べてみると単純なことでherokuにはデフォルトではPostgreSQLが入ってないので、追加してあげる必要があるということです!
なんと!!!!
まだまだ慣れてないのでこの単純なことに気づけませんでした。。。。

対応

ターミナルでPostgreSQLを追加するコマンド

$ heroku addons:create heroku-postgresql

その後に

$ heroku run rails db:migrate

そうするとマイグレーションが完了しました!

別のところが誤っていると思い全然違うことをして数時間を費やしてしまいましたが、単純なことでした!
無事アプリが動作し完了しました!

参照

【初心者向け】railsアプリをherokuを使って確実にデプロイする方法【決定版】
https://qiita.com/kazukimatsumoto/items/a0daa7281a3948701c39

【Rails】「heroku run rake db:migrate」を実行しようとすると発生するエラーについて
https://qiita.com/suzuki-x/items/b878723080aea1a673ed

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

【Rails】ビューに画像を表示する方法

初投稿です。
至らぬ点もあるかと思いますので暖かい目で見守っていただけると幸いです・・・

Railsで画像を表示する方法にかなり手こずったのでメモとしての記録です。
それでは早速参りましょう!

※haml・scssで記述しています。

画像を表示する方法

まずは、スタンダードに画像を表示する方法。
haml・scssどちらに書いても表示されるみたいです。
どっちに書くのが正解なのと思うところです。

個人的な印象としては画像を背景として使いたい時、
例えば画像の上にテキスト情報表示したい時とか。
そんなときは、background-imageとしてscssに記述したほうが良さげ。
ここの違いよくわからないので違いがわかる方いたらご教授いただきたいです:frowning2:

まず、表示する画像がないといけません。
app/assets/images
こちらのディレクトリーに表示したい画像を格納して準備完了です。

hamlに記述する場合

ファイル名.html.haml
= image_tag ("画像名.jpg")

これじゃまだ表示できないんですよね。
widthとheightを設定してあげないと表示されません。
そんな時は、scssに画像を整える記述を書く必要があります。

_ファイル名.scss
        img {
          width: 100%;
          height: 100%;
        }

こんな感じで、width: 100%;とheight: 100%;を指定すると無事表示されます。
今回の場合は「%」ですが「px」でも可能!
hamlで書く時は”= image_tag”これが必須ですね。

scssに記述する場合

_ファイル名.scss
.クラス名 {
  height: 560px;
  width: 100%;
  background-image: image-url("ファイル名.jpg");
  background-size: cover;
  background-repeat: no-repeat;
  background-position: top center;
}

こんな感じで書くと表示できます。
background-sizeとかその辺のプロパティ書かないと、
画像が「ばぁぁぁぁあぁぁん」ってなります。(悲惨)
あとは、widthとheightの設定も必要です。
実装したいイメージにあったプロパティを選択する必要があります。

本番環境でのみ画像が表示されない

ローカル環境で表示されるのに、本番環境で表示されない・・・
なんてこともあります。

表示できないときのコードがこちら

_ファイル名.scss
.クラス名 {
  height: 560px;
  width: 100%;
  background-image: url("ファイル名.jpg");
  background-size: cover;
  background-repeat: no-repeat;
  background-position: top center;
}

上のコードと見比べて違うところは

_ファイル名.scss
.クラス名 {
  background-image: url("ファイル名.jpg");
}

この部分です。
この書き方だと、ローカルでは表示できるけど、本番では表示されません。

_ファイル名.scss
.クラス名 {
  background-image: image-url("ファイル名.jpg");
}

このように修正することでで本番でもローカルでも表示できるようになります。

データベースにある画像を表示する方法

例えば、商品を登録するときに画像も登録することがあります。
登録した画像を表示したい!!そんな時の方法です。

◇条件1◇画像は別テーブルで保存する場合

別テーブルで保存する理由は、複数枚画像を登録するためです。
一枚だけ登録する場合は、テーブルを分ける必要はないかと思います。

コントローラー名.rb
  def index
    @items = Item.includes(:item_images).order("created_at DESC").limit(5)
  end

こんな感じで情報を取得。
item_imagesは画像を保存している箇所です。

ファイル名.html.haml
- @items.each do |item| #eachで展開
 = image_tag item.item_images[0].image.url
_ファイル名.scss
        img {
          width: 100%;
          height: 100%;
        }

このように書くと表示できました。
正直、めっちゃ記述長い・・・そんな印象
[0]→画像は配列で格納されているみたいで何枚目かの指定をする必要があります。

◇条件2◇画像は別テーブルで保存しない場合

html.erbの記載となります。

コントローラー名.rb
  def index
    @items = Item.order("created_at DESC").page(params[:page]).per(5)
  end

こんな感じで情報を取得。

ファイル名.html.erb
<% @items.each do |item| %>
 <%= image_tag item.image.to_s, :size =>'220x220'%>

ちゃっかり、ファイル名.html.erb内でサイズ指定しちゃっています。
これで表示できました:clap:

他に、表示する方法あればご教授いただきたいです:pray:

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

(ギリ)20代の地方公務員がRailsチュートリアルに取り組みます【第8章】

前提

・Railsチュートリアルは第4版
・今回の学習は3周目(9章以降は2周目)
・著者はProgate一通りやったぐらいの初学者

基本方針

・読んだら分かることは端折る。
・意味がわからない用語は調べてまとめる(記事最下段・用語集)。
・理解できない内容を掘り下げる。
・演習はすべて取り組む。
・コードコピペは極力しない。

 
 第8章はログインと認証システムの開発・第3段回目、基本的なログイン機構を実装していきます。(第9章でさらに発展させていきます)
 情報技術の用語が飛び交いますので、それぞれの用語の意味・動作も理解しながら進めていきましょう。
 
本日のBGMは趣向を変えて。
TVアニメ「ゆるキャン△」オリジナル・サウンドトラック
やっと涼しくなってきました。キャンプに最適なシーズンです。コーディングで疲れた眼と頭のリフレッシュに行きましょう。

 

【8.1.1 Sessionsコントローラ 演習】

1. GET login_pathとPOST login_pathとの違いを説明できますか? 少し考えてみましょう。
→ GETはログインページのビュー取得(newアクション)、POSTはフォームに入力したデータを送信してログインを実行(createアクション)。

 
2. ターミナルのパイプ機能を使ってrails routesの実行結果とgrepコマンドを繋ぐことで、Usersリソースに関するルーティングだけを表示させることができます。同様にして、Sessionsリソースに関する結果だけを表示させてみましょう。現在、いくつのSessionsリソースがあるでしょうか? ヒント: パイプやgrepの使い方が分からない場合は Learn Enough Command Line to Be Dangerousの Section on Grep (英語) を参考にしてみてください。
→ パイプ機能:(コマンド) | (コマンド) のようにコマンド同士を繋ぐ機能。
  grepコマンド:ファイル中の文字列を検索するコマンド。
  ということで下記。signupが入ってしまうのは仕方ない?

$ rails routes | grep users#
      signup GET    /signup(.:format)         users#new
             POST   /signup(.:format)         users#create
       users GET    /users(.:format)          users#index
             POST   /users(.:format)          users#create
    new_user GET    /users/new(.:format)      users#new
   edit_user GET    /users/:id/edit(.:format) users#edit
        user GET    /users/:id(.:format)      users#show
             PATCH  /users/:id(.:format)      users#update
             PUT    /users/:id(.:format)      users#update
             DELETE /users/:id(.:format)      users#destroy
$ rails routes | grep sessions#
    login GET    /login(.:format)          sessions#new
          POST   /login(.:format)          sessions#create
   logout DELETE /logout(.:format)         sessions#destroy

 

【8.1.2 ログインフォーム 演習】

1. リスト 8.4で定義したフォームで送信すると、Sessionsコントローラのcreateアクションに到達します。Railsはこれをどうやって実現しているでしょうか? 考えてみてください。ヒント:表 8.1とリスト 8.5の1行目に注目してください。
→ routesファイルのpost '/login', to: 'sessions#create'の一文でしょう。form_forでpostリクエストを発行→ルーティングでsessionsコントローラのcreateアクションに割り当て。

 

【8.1.3 ユーザーの検索と認証 演習】

1. Railsコンソールを使って、表 8.2のそれぞれの式が合っているか確かめてみましょう. まずはuser = nilの場合を、次にuser = User.firstとした場合を確かめてみてください。ヒント: 必ず論理値オブジェクトとなるように、4.2.3で紹介した!!のテクニックを使ってみましょう。例: !!(user && user.authenticate('foobar'))
→ 下記

>> user = nil
=> nil
>> !!(user && user.authenticate("foobar"))
=> false

>> user = User.first
  User Load (0.1ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> #<User id: 1, name: "Rails Tutorial", email: "example@railstutorial.org", created_at: "2020-09-12 09:09:50", updated_at: "2020-09-12 09:09:50", password_digest: "$2a$10$hrOEzw0faSd4yurmH8bQJOnggeNnUqTZg33yE9g7Tnk...">
>> !!(user && user.authenticate("matigatteruyo"))
=> false
>> !!(user && user.authenticate("hogehoge"))
=> true

 

【8.1.5 フラッシュのテスト 演習】

1. 8.1.4の処理の流れが正しく動いているかどうか、ブラウザで確認してみてください。特に、flashがうまく機能しているかどうか、フラッシュメッセージの表示後に違うページに移動することを忘れないでください。
→ 試してみましょう。違うページに行くとフラッシュが消えます。

 

【8.2.1 log_in メソッド 演習】

1. 有効なユーザーで実際にログインし、ブラウザからcookiesの情報を調べてみてください。このとき、sessionの値はどうなっているでしょうか? ヒント: ブラウザでcookiesを調べる方法が分からない? 今こそググってみるときです! (コラム 1.1)
 
2. 先ほどの演習課題と同様に、Expiresの値について調べてみてください。
→ まとめて下の画像。Expires = 期限切れなので、緑の四角内です。ブラウザセッション終了時が有効期限になっていますね。
スクリーンショット 2020-09-13 14.16.12.png

 

【8.2.2 現在のユーザー 演習】

1. Railsコンソールを使って、User.find_by(id: ...)で対応するユーザーが検索に引っかからなかったとき、nilを返すことを確認してみましょう。
→ 下記

>> User.find_by(id: 8)
  User Load (0.2ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 8], ["LIMIT", 1]]
=> nil

 
2. 先ほどと同様に、今度は:user_idキーを持つsessionハッシュを作成してみましょう。リスト 8.17に記したステップに従って、||=演算子がうまく動くことも確認してみましょう。
→ 下記

>> session = {}
=> {}
>> session[:user_id] = nil
=> nil
>> @current_user ||= User.find_by(id: session[:user_id])
  User Load (0.2ms)  SELECT  "users".* FROM "users" WHERE "users"."id" IS NULL LIMIT ?  [["LIMIT", 1]]
=> nil
>> session[:user_id] = User.first.id
  User Load (0.1ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> 1
>> @current_user ||= User.find_by(id: session[:user_id])
  User Load (0.1ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
=> #<User id: 1, name: "Rails Tutorial", email: "example@railstutorial.org", created_at: "2020-09-12 09:09:50", updated_at: "2020-09-12 09:09:50", password_digest: "$2a$10$hrOEzw0faSd4yurmH8bQJOnggeNnUqTZg33yE9g7Tnk...">
>> @current_user ||= User.find_by(id: session[:user_id])
=> #<User id: 1, name: "Rails Tutorial", email: "example@railstutorial.org", created_at: "2020-09-12 09:09:50", updated_at: "2020-09-12 09:09:50", password_digest: "$2a$10$hrOEzw0faSd4yurmH8bQJOnggeNnUqTZg33yE9g7Tnk...">

 

【8.2.3 レイアウトリンクを変更する 演習】

1. ブラウザのcookieインスペクタ機能を使って (8.2.1.1)、セッション用のcookieを削除してみてください。ヘッダー部分にあるリンクは非ログイン状態のものになっているでしょうか? 確認してみましょう。
→ やってみるだけ。非ログイン状態になりました。

 
2. もう一度ログインしてみて、ヘッダーのレイアウトが変わったことを確認してみましょう。その後、ブラウザを再起動させ、再び非ログイン状態に戻ったことも確認してみてください。注意: もしブラウザの [閉じたときの状態に戻す] 機能をオンにしていると、セッション情報も復元される可能性があります。もしその機能をオンにしている場合、忘れずにオフにしておきましょう (コラム 1.1)。
→ やってみるだけー。

 

【8.2.4 レイアウトの変更をテストする メモと演習】

 ここのdigestメソッドのコードの書き方は正直??状態。:を2つ書くのってどういう意味なんでしょうか。前からちょくちょく出てきてるけど。調べると、PHPの記法は出てくるけど、同じような意味にとっていいのだろうか。
テストのアサーションなんかは用語集にまとめてます。

1. 試しにSessionヘルパーのlogged_in?メソッドから!を削除してみて、リスト 8.23が redになることを確認してみましょう。
 
2. 先ほど削除した部分 (!) を元に戻して、テストが greenに戻ることを確認してみましょう。
→ まとめて。当然失敗します。ヘッダーの表示がログイン・非ログイン時で逆になっちゃうから。戻せばGREENです。
 

【8.2.5 ユーザー登録時にログイン メモと演習】

 この章ではいろんなヘルパーメソッドを定義していますね。どこで使うために、どこに定義しているの意識しながらコードを書きましょう。コントローラで使うのか、テストで使うのか等。

1. リスト 8.25のlog_inの行をコメントアウトすると、テストスイートは red になるでしょうか? それとも green になるでしょうか? 確認してみましょう。
→ REDになります。テストにログイン状態か確かめるコード書いているので。

 
2. 現在使っているテキストエディタの機能を使って、リスト 8.25をまとめてコメントアウトできないか調べてみましょう。また、コメントアウトの前後でテストスイートを実行し、コメントアウトすると red に、コメントアウトを元に戻すと green になることを確認してみましょう。ヒント: コメントアウト後にファイルを保存することを忘れないようにしましょう。また、テキストエディタのコメントアウト機能については Test Editor Tutorial の Commenting Out (英語) などを参照してみてください。
→ (Macの場合)command+Aで全選択、command+/でコメントアウト。当然コメントアウトの前後でRED/GREENになります。

 

【8.3 ログアウト メモと演習】

1. ブラウザから [Log out] リンクをクリックし、どんな変化が起こるか確認してみましょう。また、リスト 8.31で定義した3つのステップを実行してみて、うまく動いているかどうか確認してみましょう。
→ ちゃんと動きます。

 
2. cookiesの内容を調べてみて、ログアウト後にはsessionが正常に削除されていることを確認してみましょう。
→ 削除されてます。

 

第7章まとめ

・sessionメソッドで一時的な状態保存。
・ログインではActive RecordのUserモデルを使わないので、関連づけられたエラーメッセージは使えない。
・flash.nowでrenderしたページのみフラッシュメッセージを表示。
・form_forはform_withに置き換わってるようなので参考までに。
・renderとredirect_toの使い分け。
・flashメッセージを表示。
・統合テストでログインまわりの実装を一通りテスト(ログイン/ログアウトできているか、ヘッダーは切り替わっているか)

 
 この章は大きなエラーなく進められました。次の9章から発展的な機構を導入していきます。遂に1周しかしていない章に入っていきますね!気合入れていきましょう!

 
⇦ 第7章はこちら
学習にあたっての前提・著者ステータスはこちら
 

なんとなくイメージを掴む用語集

・ステートレス・プロトコル
 状態の保持をしない独立した情報のやりとり。ログイン機能に例えると、一回ブラウザを閉じて再度入り直すと、ログインし直しになる。

・セッション(session)
 一連の通信のこと(ログインからログアウトまで等)。ェブサイトでは、初めて訪問した場合にブラウザのcookieに書き込まれる識別子のこと。

・cookie
 HTTPにおけるウェブサーバとウェブブラウザ間で状態を管理する通信プロトコル、またそこで用いられるウェブブラウザに保存された情報のこと。ECサイトの買い物カゴやログイン機能等で使われている。溜まってきたなと思ってブラウザの設定とかから消せるアレ。

・assert_redirected_to
 このアサーションの直前で呼び出されたリダイレクト先と、to以下のリダイレクト先が一致するかどうかテストする。

・follow_redirect!
 実際にそのページに移動する。移動した先で他の要素をテストする際に利用。

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

Rails チュートリアル バージョン違いで rails new できない時

超超初心者向けです。

railsチュートリアルで新しいアプリの作成時に

$ rails 6.0.3 new hello_app

を実行すると、

Traceback (most recent call last):
.
.
can't find gem railties (= 6.0.3) with executable rails (Gem::GemNotFoundException)

このようなエラーが出てしまいました。

https://teratail.com/questions/80979

↑こちらの記事を参考にさせていただいて

$ gem install rails -v 6.0.3

でgem をインストールしてもう一度

$ rails 6.0.3 new hello_app

することで解決しました。

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

アウトプット2 ~ユーザ登録機能~

ユーザ登録機能を作ります。

users/newもしくは、自らルーティングした/signupで。
userscontrollerのアクションですが、

userscontroller.rb
 def new
  @user = User.new

 def create
  @user = User.new(user_params)
  if @user.save
   flash[:success] = "ユーザを登録しました。"
     redirect_to root_path
   else
     flash.now[:danger] ="ユーザの登録に失敗しました"
     render :new

private
 def user_params
  params.require(:user).permit(:name,:email,:password,:password_confirmation)
 end

としました。

privateはこのクラスのみで使えるプライヴェーとメソッド。
user_paramsはストロングパラメータです。
送信されてきたデータに対して、フィルターをかけようというものです。
今回、送られてきたuserモデルからのパラメータに対し、permitで指定したデータ以外は許可しないようにしています。
@user = User.new(user_params)で、paramsはuserモデルのname~password_confirmationのカラムしか受け取らないようにします。

コントローラー設定が終わったので、viewsです。

new.htl.erb
<div class="text-center">
  <h1>ユーザー登録</h1>
</div>

<div class="row">
  <div class="col-sm-6 offset-sm-3">

    <%= form_with(model: @user, local: true) do |f| %>
    <%= render "layouts/error_messages", model: f.object %> <!--create失敗時フラッシュメッセージ-->

      <div class="form-group">
        <%= f.label :name, "Name" %>
        <%= f.text_field :name, class: "form-control" %>
      </div>

      <div class="form-group">
        <%= f.label :email, "Email" %>
        <%= f.email_field :email, class: "form-control" %>
      </div>

      <div class="form-group">
        <%= f.label :password, "Password" %>
        <%= f.password_field :password, class: "form-control" %>
      </div>

      <div class="form-group">
        <%= f.label :password_confirmation, "Password-Confirmation" %>
        <%= f.password_field :password_confirmation, class: 'form-control' %>
      </div>

      <%= f.submit "新規登録", class: "btn btn-primary btn-block" %>
    <% end %>
  </div>
</div>

フォームを設置しました。
form-with(model: モデル名)ですが、モデルがない場合はurlを指定します。

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

Heroku便利なコマンド大全!!

Herokuへの初回push

$ cd <レポジトリ名>
$ git init
$ heroku create <アプリ名> 
$ heroku git:remote -a <アプリ名>
# addした名前を確認出来ます
$ git remote -v
$ git add .
$ git commit -c "comment"
$ git push <アプリ名> master

Heroku app一覧の表示

heroku apps

Heroku環境にあるアプリの概要表示

これはかなり使えます!!アプリのURLとかも表示してくれるので!
Ruby on Railsのチュートリアルで、「本番環境で〇〇を確認してください」
とよくあると思いますが、このコマンド打てば本番環境のURLを一発で確認できます!

heroku info --app <アプリ名>

Heroku logの確認

heroku logs

データベースのパス取得

$ heroku config --app <アプリ名>
CLEARDB_DATABASE_URL:mysql://ユーザ名:パスワード@サーバ名(ホスト名)/データベース?reconnect =true
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails:Deviseを使用するまでの手順

動機

railsでユーザー認証機能の作成を行ったが難しかったため
メモとして残しておく

deviseのインストール

$ rails g devise:install

まずはdeviseのインストールを行う

インストールが完了すると以下が表示される

Depending on your application's configuration some manual setup may be required:

  1. Ensure you have defined default url options in your environments files. Here
     is an example of default_url_options appropriate for a development environment
     in config/environments/development.rb:

       config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }

     In production, :host should be set to the actual host of your application.

     * Required for all applications. *

  2. Ensure you have defined root_url to *something* in your config/routes.rb.
     For example:

       root to: "home#index"

     * Not required for API-only Applications *

  3. Ensure you have flash messages in app/views/layouts/application.html.erb.
     For example:

       <p class="notice"><%= notice %></p>
       <p class="alert"><%= alert %></p>

     * Not required for API-only Applications *

  4. You can copy Devise views (for customization) to your app by running:

       rails g devise:views

     * Not required *

 1.mailerの設定

1ではmailerの設定をしてと言っているので'config/environments/development.rb:'のmailer設定のところに以下のように記述

# Don't care if the mailer can't send.
  config.action_mailer.raise_delivery_errors = false

  config.action_mailer.perform_caching = false

  config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }

2.rootパスの設定

routes.rbにルーティングを記述

root to: '○○ #index'

3.flashの設定

flashの作成はこちらでは説明を割愛するが
'app/views/layouts/application.html.erb'にflashを作れと言われている

4.viewのカスタマイズ設定

deviseのviewをカスタマイズしたいならば以下を走らせろと言われているので、ターミナルで記述

rails g devise:views

MODELの作成

設定が完了したら、modelの作成を行う

$ rails g devise User

modelの作成が完了したらdb状態を更新する

$ rails db:migrate

補足

必要であれば日本語化を行う

前提

application.rbファイルに以下を記述

config.i18n.default_locale = :ja

deviseの日本語化

config/locales内に'devise.ja.yml'ファイルを作成し、以下のファイルをコピー

ja:
  activerecord:
    attributes:
      user:
        confirmation_sent_at: パスワード確認送信時刻
        confirmation_token: パスワード確認用トークン
        confirmed_at: パスワード確認時刻
        created_at: 作成日
        current_password: 現在のパスワード
        current_sign_in_at: 現在のログイン時刻
        current_sign_in_ip: 現在のログインIPアドレス
        email: Email
        encrypted_password: 暗号化パスワード
        failed_attempts: 失敗したログイン試行回数
        last_sign_in_at: 最終ログイン時刻
        last_sign_in_ip: 最終ログインIPアドレス
        locked_at: ロック時刻
        password: Password
        password_confirmation: Password(確認用)
        remember_created_at: ログイン記憶時刻
        remember_me: ログインを記憶する
        reset_password_sent_at: パスワードリセット送信時刻
        reset_password_token: パスワードリセット用トークン
        sign_in_count: ログイン回数
        unconfirmed_email: 未確認Eメール
        unlock_token: ロック解除用トークン
        updated_at: 更新日
    models:
      user: ユーザ
  devise:
    confirmations:
      confirmed: メールアドレスが確認できました。
      new:
        resend_confirmation_instructions: アカウント確認メール再送
      send_instructions: アカウントの有効化について数分以内にメールでご連絡します。
      send_paranoid_instructions: メールアドレスが登録済みの場合、本人確認用のメールが数分以内に送信されます。
    failure:
      already_authenticated: すでにログインしています。
      inactive: アカウントが有効化されていません。メールに記載された手順にしたがって、アカウントを有効化してください。
      invalid: "%{authentication_keys}またはパスワードが違います。"
      last_attempt: もう一回誤るとアカウントがロックされます。
      locked: アカウントは凍結されています。
      not_found_in_database: "%{authentication_keys}またはパスワードが違います。"
      timeout: セッションがタイムアウトしました。もう一度ログインしてください。
      unauthenticated: アカウント登録もしくはログインしてください。
      unconfirmed: メールアドレスの本人確認が必要です。
    mailer:
      confirmation_instructions:
        action: メールアドレスの確認
        greeting: "%{recipient}様"
        instruction: 以下のリンクをクリックし、メールアドレスの確認手続を完了させてください。
        subject: メールアドレス確認メール
      email_changed:
        greeting: こんにちは、%{recipient}様。
        message: あなたのメール変更(%{email})のお知らせいたします。
        subject: メール変更完了。
      password_change:
        greeting: "%{recipient}様"
        message: パスワードが再設定されたことを通知します。
        subject: パスワードの変更について
      reset_password_instructions:
        action: パスワード変更
        greeting: "%{recipient}様"
        instruction: パスワード再設定の依頼を受けたため、メールを送信しています。下のリンクからパスワードの再設定ができます。
        instruction_2: パスワード再設定の依頼をしていない場合、このメールを無視してください。
        instruction_3: パスワードの再設定は、上のリンクから新しいパスワードを登録するまで完了しません。
        subject: パスワードの再設定について
      unlock_instructions:
        action: アカウントのロック解除
        greeting: "%{recipient}様"
        instruction: アカウントのロックを解除するには下のリンクをクリックしてください。
        message: ログイン失敗が繰り返されたため、アカウントはロックされています。
        subject: アカウントの凍結解除について
    omniauth_callbacks:
      failure: "%{kind} アカウントによる認証に失敗しました。理由:(%{reason})"
      success: "%{kind} アカウントによる認証に成功しました。"
    passwords:
      edit:
        change_my_password: パスワードを変更する
        change_your_password: パスワードを変更
        confirm_new_password: 確認用新しいパスワード
        new_password: 新しいパスワード
      new:
        forgot_your_password: パスワードを忘れましたか?
        send_me_reset_password_instructions: パスワードの再設定方法を送信する
      no_token: このページにはアクセスできません。パスワード再設定メールのリンクからアクセスされた場合には、URL をご確認ください。
      send_instructions: パスワードの再設定について数分以内にメールでご連絡いたします。
      send_paranoid_instructions: メールアドレスが登録済みの場合、パスワード再設定用のメールが数分以内に送信されます。
      updated: パスワードが正しく変更されました。
      updated_not_active: パスワードが正しく変更されました。
    registrations:
      destroyed: アカウントを削除しました。またのご利用をお待ちしております。
      edit:
        are_you_sure: 本当によろしいですか?
        cancel_my_account: アカウント削除
        currently_waiting_confirmation_for_email: "%{email} の確認待ち"
        leave_blank_if_you_don_t_want_to_change_it: 空欄のままなら変更しません
        title: "%{resource}編集"
        unhappy: 気に入りません
        update: 更新
        we_need_your_current_password_to_confirm_your_changes: 変更を反映するには現在のパスワードを入力してください
      new:
        sign_up: アカウント登録
      signed_up: アカウント登録が完了しました。
      signed_up_but_inactive: ログインするためには、アカウントを有効化してください。
      signed_up_but_locked: アカウントが凍結されているためログインできません。
      signed_up_but_unconfirmed: 本人確認用のメールを送信しました。メール内のリンクからアカウントを有効化させてください。
      update_needs_confirmation: アカウント情報を変更しました。変更されたメールアドレスの本人確認のため、本人確認用メールより確認処理をおこなってください。
      updated: アカウント情報を変更しました。
      updated_but_not_signed_in: あなたのアカウントは正常に更新されましたが、パスワードが変更されたため、再度ログインしてください。
    sessions:
      already_signed_out: 既にログアウト済みです。
      new:
        sign_in: ログイン
      signed_in: ログインしました。
      signed_out: ログアウトしました。
    shared:
      links:
        back: 戻る
        didn_t_receive_confirmation_instructions: アカウント確認のメールを受け取っていませんか?
        didn_t_receive_unlock_instructions: アカウントの凍結解除方法のメールを受け取っていませんか?
        forgot_your_password: パスワードを忘れましたか?
        sign_in: ログイン
        sign_in_with_provider: "%{provider}でログイン"
        sign_up: アカウント登録
      minimum_password_length: "(%{count}字以上)"
    unlocks:
      new:
        resend_unlock_instructions: アカウントの凍結解除方法を再送する
      send_instructions: アカウントの凍結解除方法を数分以内にメールでご連絡します。
      send_paranoid_instructions: アカウントが見つかった場合、アカウントの凍結解除方法を数分以内にメールでご連絡します。
      unlocked: アカウントを凍結解除しました。
  errors:
    messages:
      already_confirmed: は既に登録済みです。ログインしてください。
      confirmation_period_expired: の期限が切れました。%{period} までに確認する必要があります。 新しくリクエストしてください。
      expired: の有効期限が切れました。新しくリクエストしてください。
      not_found: は見つかりませんでした。
      not_locked: は凍結されていません。
      not_saved:
        one: エラーが発生したため %{resource} は保存されませんでした。
        other: "%{count} 件のエラーが発生したため %{resource} は保存されませんでした。"
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

curlコマンドでrailsAPIの操作

この記事について

初心者がAPI操作に役立ったコマンドを投稿する記事です。

curlコマンドとは

https://qiita.com/buntafujikawa/items/758425773b2239feb9a7#curl%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89%E3%81%A8%E3%81%AF

curlコマンドとはサーバから、もしくはサーバへデータ転送を行うコマンドです。

curl [options] [URL]

学んだこと

#POST
curl -i -X POST -H 'Content-Type: application/json' -d '{ "カラム名": "データ" }' localhost:3000/posts

#UPDATE
curl -i -X PUT -H 'Content-Type: application/json' -d '{ "カラム名": "データ" }' localhost:3000/posts/:id

#DELTE
curl -X DELETE localhost:3000/posts/:id

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

【RailsAPI×Docker】シェルで手軽に環境構築 & Flutterでも動作確認

初心者の方でも進められるよう手順は丁寧してあります。

やること

  • RailsAPI × Docker での環境構築
  • RailsAPIを軽く実装
  • APIクライアントからAPIを叩く(PostmanやFlutterアプリから)※Flutterは任意で

スクリーンショット 2020-09-13 13.42.28.png

APIコンテナ起動のシェルだけ見たい方 → こちら
一応 RailsAPIのソースコードFlutterのソースコードも用意(※いじり倒してる可能性あり)

手順

  • API側のDockerコンテナ起動まで
    • ディレクトリ作成
    • シェルでRailsAPIコンテナビルド
    • コンテナ起動&確認
  • RailsAPI実装
    • User モデル作成&DBテーブル用意
    • User のコントローラ作成
    • ルーティング設定
  • PostmanでAPIの動作を確認
    • Postmanインストール
    • GETでDBのUserリソースを取得
    • POSTでDBのUserリソースを追加
  • FlutterでAPIを叩く
    • プロジェクト作成
    • Http通信を行えるようにする
    • main.dartを編集

前提

  • AndroidStudioでFlutterアプリケーションを起動した状態にする
    • SDKなどをダウンロードしてシミュレータを起動できる
    • ※ Flutterセットアップはこちらの記事で基本全部いけました(3~40分はかかります)
  • Docker, docker-compose のコマンドが使える($ docker-compose -vが動けばOK)

1. API側のDockerコンテナ起動まで

RailsAPIのDockerコンテナを作っていきます。

ただ、1からコマンドを打っていくのは非効率なので、基本的なところはシェルにまとめてあります。

1-1. ディレクトリ作成

app_nameにプロジェクト名を当てはめてディレクトリを用意します。

$ mkdir app_name  # APIアプリを置くディレクトリ用意
$ cd app_name

1-2. シェルでRailsAPIコンテナビルド

以下がAPIコンテナのビルドまでやってくれるシェルです。(参考にした記事)

サンプルAPI立ち上げ用のシェル
set_up_rails.sh
#!/bin/bash

#config setting#############
APP_NAME="app_name"            # ← 自由に変更してください(ディレクトリ名と一緒がいいかも)
MYSQL_PASSWORD="password"      # ← 自由に変更してください
###########################

echo "docker pull ruby2.6.6"
docker pull ruby:2.6.6

echo "docker pull mysql:5.7"
docker pull mysql:5.7

echo "docker images"
docker images

echo "make Dockerfile"

cat <<EOF > Dockerfile
FROM ruby:2.6.6
ENV LANG C.UTF-8
RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs
#yarnのセットアップ
RUN curl -o- -L https://yarnpkg.com/install.sh | bash
ENV PATH /root/.yarn/bin:/root/.config/yarn/global/node_modules/.bin:\$PATH
# 作業ディレクトリの作成、設定
RUN mkdir /${APP_NAME}
ENV APP_ROOT /${APP_NAME}
WORKDIR \$APP_ROOT
# ホスト側(ローカル)のGemfileを追加する
ADD ./Gemfile \$APP_ROOT/Gemfile
ADD ./Gemfile.lock \$APP_ROOT/Gemfile.lock
# Gemfileのbundle install
RUN bundle install
ADD . \$APP_ROOT
# gem版yarnのuninstall rails6でエラーになるため
RUN gem uninstall yarn -aIx
#webpackerの設定
#RUN rails webpacker:install
EOF

echo "make Gemfile"
cat <<EOF > Gemfile
source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
gem 'rails', '~> 6.0.3.2'
EOF

echo "make Gemfile.lock"
touch Gemfile.lock

echo "make docker-compose.yml"
cat <<EOF > docker-compose.yml
version: '3'
services:
  db:
    image: mysql:5.7
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_PASSWORD}
      MYSQL_DATABASE: root
    ports:
      - '3306:3306'
  api:
    build: .
    command: bundle exec rails s -p 3000 -b '0.0.0.0'
    volumes:
      - .:/${APP_NAME}
    ports:
      - '3000:3000'
    links:
      - db
EOF

echo "docker-compose run api rails new . --api --force --database=mysql --skip-bundle"
docker-compose run api rails new . --api --force --database=mysql --skip-bundle

echo "docker-compose run api bundle exec rails webpacker:install"
docker-compose run api bundle exec rails webpacker:install

docker-compose build

# fix config/database.yml
echo "fix config/database.yml"
cat config/database.yml | sed "s/password:$/password: ${MYSQL_PASSWORD}/" | sed "s/host: localhost/host: db/" > __tmpfile__
cat __tmpfile__ > config/database.yml
rm __tmpfile__

echo "docker-compose run api rails db:create"
docker-compose run api rails db:create

エディタからプロジェクト直下に新規ファイルとして作成&↑をコピペし、set_up_rails.shなどとして保存しましょう。「自由に変更してください」となっているapp_nameなどは合わせて編集してください。

保存が終わったら以下のようにシェルの権限を変更し、実行してみましょう(5分くらいかかります)。

$ chmod 755 set_up_rails.sh  # 権限の変更
$ ./set_up_rails.sh          # セットアップのシェル実行
docker pull ruby2.6.6
2.6.6: Pulling from library/ruby
57df1a1f1ad8: Pull complete
71e126169501: Pull complete
1af28a55c3f3: Pull complete
・
・
・

シェルの実行が終わったら、$ docker-compose psでコンテナの状態を確認してみましょう。以下のようになっていれば問題ないです。

$ docker-compose ps
     Name             Command       State       Ports
----------------------------------------------------------
app_name_db_1     docker-           Up      0.0.0.0:3306->
                  entrypoint.sh             3306/tcp,
                  mysqld                    33060/tcp

1-3. コンテナ起動 & 確認

次に、シェルでビルドしたコンテナを起動します。

$ docker-compose up
rails-api-handson_db_1 is up-to-date
Creating rails-api-handson_api_1 ... done
Attaching to rails-api-handson_db_1, rails-api-handson_api_1
db_1   | 2020-09-12 08:27:00+00:00 [Note] [Entrypoint]: Entrypoint script for MySQL Server 5.7.31-1debian10 started.
・
・
・
api_1  | * Environment: development
api_1  | * Listening on tcp://0.0.0.0:3000
api_1  | Use Ctrl-C to stop    # ← これが出たらOK!

Dockerコンテナのログが大量に出ますが、最後のUse Ctrl-C to stopが出ればコンテナが起動しているのでOKです。

コンテナの起動が確認できたので、http://localhost:3000 にアクセスし、ちゃんとRailsとしてレスポンスを返してくれるか確認しましょう。以下のようになっていればAPIコンテナの動作は大丈夫です。

スクリーンショット 2020-09-12 17.38.44.png

2. RailsAPI実装

動作確認用なので単純なユーザデータにしましょう。今回は、

User ( id, name, email )

みたいなリソースで実装してみます。

2-1. Userモデル作成 & DBテーブル用意

Userモデルとテーブルを用意します。

ここだけマイグレーションを使う方法Ridgepoleを使う方法に分けて紹介します(どちらも結果として同じモデル、スキーマが反映されます)。

Ridgepoleは、マイグレーションを使わずにスキーマを管理できるツールです。僕はこちらで進めていきますが、普段からマイグレーションを使っている方であればそちらで問題ありません。

パターン1) マイグレーションでモデル&テーブル作成

コンテナに入り、ジェネレータでUserモデルを作成。

$ docker-compose exec api bash     # コンテナに入る
# rails g model user name:string email:string
# ↑ 出来なければ以後はBundle経由でやってみましょう( # bundle exec rails .... みたいな )exit                             # コンテナから出る(以後はコンテナの出入りは明記しません。
                                   #              コマンドラインの「$」や「#」で区別します。)
$

あとはマイグレーションをかけるだけです。

# rake db:migrate

パターン2) Ridgepoleでモデル&テーブル作成

マイグレーションをスキップしてモデルを作成。

# rails g model user --skip-migration

プロジェクト直下にあるGemfilegem 'ridgepole', '>= 0.8.0'を追記します。mysql2の下とかで問題ないでしょう。

Gemfile
gem 'mysql2', '>= 0.4.4'     # 元々ある
gem 'ridgepole', '>= 0.8.0'  # これを追加

追記したgemをインストール。

# bundle install

次にdb/のなかに、Schemafile.rbというファイルを作成します(Ridgepoleではスキーマをここで一元管理する)。

db/Schemafile.rbには以下のように書き、保存します。

db/Schemafile.rb
create_table "users", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8" do |t|
  t.string "name"
  t.string "email"
  t.timestamps
end

ridgepoleのコマンドでスキーマファイルを反映。

# bundle exec ridgepole --config ./config/database.yml --file ./db/Schemafile.rb --apply
Apply `./db/Schemafile.rb`
-- create_table("users", {:options=>"ENGINE=InnoDB DEFAULT CHARSET=utf8"})
   -> 0.0716s

2-2. User のコントローラ作成

こちらの記事を参考に、URIにAPIのメジャーバージョン(v1.3なら1の部分)を含めます。ただこの記事だと、URIにapiというネームスペースを使っています。

これは僕個人の意見ですが、URIにapiと明記するよりも、サブドメインなどで示した方がいいのでは?と思います。APIを使おうとするクライアントからすればAPIだってことはわかっていますから。

example.com/api/vi/users  # URIでapiだと示す
api.example.com/vi/users  # サブドメインで示す

では本題のコントローラ作成に戻ります。

コントローラの前に、メジャーバージョンを示すv1/を作成し、その中でusersコントローラを作成します。

$ mkdir app/controllers/v1
$ rails g controller v1::users

コントローラファイルの中身は以下の感じです。

*/v1/users_controller.rb
class V1::UsersController < ApplicationController
  before_action :set_user, only: [:show, :update, :destroy]

  def index
    users = User.order(created_at: :desc)
    render json: { status: 'SUCCESS', message: 'Loaded user', data: users }
  end

  def show
    render json: { status: 'SUCCESS', message: 'Loaded the user', data: @user }
  end

  def create
    user = User.new(user_params)
    if user.save
      render json: { status: 'SUCCESS', data: user }
    else
      render json: { status: 'ERROR', data: user.errors }
    end
  end

  def destroy
    @user.destroy
    render json: { status: 'SUCCESS', message: 'Deleted the user', data: @user }
  end

  def update
    if @user.update(user_params)
      render json: { status: 'SUCCESS', message: 'Updated the user', data: @user }
    else
      render json: { status: 'SUCCESS', message: 'Not updated', data: @user.errors }
    end
  end

  private

  def set_user
    @user = User.find(params[:id])
  end

  def user_params
    params.require(:user).permit(:name, :email)
  end
end

2-3. ルーティング設定

config/routes.rbを以下のようにします。

config/routes.rb
Rails.application.routes.draw do
  namespace 'v1' do
    resources :users
  end
end

↑が終わると、rake routesコマンドでルーティングが確認できるはずです。以下のようなルーティングが反映されていればOKです。

# rake routes
  Prefix Verb    URI Pattern               Controller#Action
v1_users GET     /v1/users(.:format)       v1/users#index
         POST    /v1/users(.:format)       v1/users#create
 v1_user GET     /v1/users/:id(.:format)   v1/users#show
         PATCH   /v1/users/:id(.:format)   v1/users#update
         PUT     /v1/users/:id(.:format)   v1/users#update
         DELETE  /v1/users/:id(.:format)   v1/users#destroy

3. PostmanでAPIの動作を確認

Postmanとは、WebAPIのテストクライアントサービスのひとつです。APIの動作確認にはこれを使います。

3-1. Postmanインストール

公式サイトで、Postmanをインストールします。前にインストールしたのであまり覚えていませんが、登録して進めていけばOKだった気がします...。

Postmanインストール後、まずはコンソールで適当にUserのレコードを用意しておきましょう。以下のような感じでいいかと思います。

# rails c
Loading development environment (Rails 6.0.3.3)
irb(main):001:0> User.create(name: "hoge", email: "hoge@example.com")

3-2. GETでDBのUserリソースを取得

Postmanで以下のようにGETメソッドURIを指定し、Sendボタンを押します。

すると、画面下のBodyで返ってきたレスポンスのBodyが確認できます。ここまでできていればAPIの動作確認としては成功です!

スクリーンショット 2020-09-12 21.15.09.png

3-3. POSTでDBのUserリソースを追加

今度は以下のように、POSTメソッドを指定し、リクエストBodyの中にもデータを含めてみましょう。この時、形式がJSONになっているか確認しましょう(画像の"JSON"とオレンジになっているところ)。

成功すれば、登録されたUserのレコードがレスポンスBodyから確認出来ます。

スクリーンショット 2020-09-12 21.21.14.png

ここで先ほどのGETメソッドでのアクセスをもう一度行ってみると、POSTメソッドで追加したレコードも一緒に確認できます。

スクリーンショット 2020-09-12 21.22.33.png

4. FlutterでAPIを叩く

結果こんな感じ

  • アプリを起動したらDBにあるユーザのデータを表示
  • 右下のAddボタンでユーザデータの登録

動作確認なのでミニマムな感じです。

スクリーンショット 2020-09-12 23.28.18.png

4-1. プロジェクト作成

File > New > New Flutter Project... からFlutterプロジェクトを作成します。iPhoneやAndroidのシミュレータ(エミュレータ)などが起動できていればOKです。

4-2. Http通信を行えるようにする

デフォルトだとhttpを行うパッケージのimportでエラーを起こすので少し操作が必要になります。

まず、pubspec.yamldependencies:に以下のように追記します。

pubspec.yaml
# ~ 省略 ~
dependencies:
  http: ^0.12.0   # ← 追加する
  flutter:
    sdk: flutter
# ~ 省略 ~

すると、Android Studioのエディタの上の方に、以下のようなFlutterコマンドが出てきます。今回はPub getを選択します。

もしこの先の作業でHttp周りのエラーが出るようであれば、僕が参考にしたこちらの記事も見てみるといいかも知れません。

スクリーンショット 2020-09-12 22.35.18.png

4-3. main.dartを編集

動作確認をしたいだけであれば、lib/main.dartを以下のように書き換えれば動きます。

なお、僕はFlutterは初歩の初歩なので物申すことがあればコメントにお願いします。

lib/main.dart
import 'dart:async';
import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;

void main() => runApp(MyApp());

class MyApp extends StatefulWidget {
  MyApp({Key key}) : super(key: key);

  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  Future<GetResults> res;
  @override
  void initState() {
    super.initState();
    res = getUsers();
  }

  // postUser()でUserを登録。setState内で再度User一覧を取得
  void _postUser() {
    postUser();
    setState(() {
      res = getUsers();
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'APIをPOSTで呼び出しJSONパラメータを渡す',
      theme: ThemeData(
        primarySwatch: Colors.yellow,
      ),
      home: Scaffold(
        appBar: AppBar(
          title: Text('RailsAPI x Flutter'),
        ),
        body: Center(
          child: FutureBuilder<GetResults>(
            future: getUsers(),
            builder: (context, snapshot) {
              if (snapshot.hasData) {
                return Text(
                    snapshot.data.message.toString()
                );
              } else if (snapshot.hasError) {
                return Text("${snapshot.error}");
              }
              return CircularProgressIndicator();
            },
          ),
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: _postUser,
          tooltip: 'Increment',
          child: Icon(Icons.add),
        ),
      ),
    );
  }
}

class GetResults {
  final String message;
  GetResults({
    this.message,
  });
  factory GetResults.fromJson(Map<String, dynamic> json) {
    var datas = '';
    json['data'].forEach((item) => datas += 'id: ' + item['id'].toString() + ', name: ' + item['name'] + ', email: ' + item['email'] + '\n');
    return GetResults(
      message: datas,
    );
  }
}

class PostResults {
  final String message;
  PostResults({
    this.message,
  });
  factory PostResults.fromJson(Map<String, dynamic> json) {
    return PostResults(
      message: json['message'],
    );
  }
}

class SampleRequest {
  final String name;
  final String email;
  SampleRequest({
    this.name,
    this.email,
  });
  Map<String, dynamic> toJson() => {
    'name': name,
    'email': email,
  };
}

Future<GetResults> getUsers() async {
  var url = 'http://127.0.0.1:3000/v1/users';
  final response = await http.get(url);
  if (response.statusCode == 200) {
    return GetResults.fromJson(json.decode(response.body));
  } else {
    throw Exception('Failed');
  }
}

Future<PostResults> postUser() async {
  var url = "http://127.0.0.1:3000/v1/users";                             // APIのURI
  var request = new SampleRequest(name: 'foo', email: 'foo@example.com'); // Userのパラメータ。自由に変更してOK
  final response = await http.post(url,
      body: json.encode(request.toJson()),
      headers: {"Content-Type": "application/json"});
  if (response.statusCode == 200) {
    return PostResults.fromJson(json.decode(response.body));
  } else {
    throw Exception('Failed');
  }
}

シミュレータを起動すると、、登録されたUserがきちんと返ってきてますね!

スクリーンショット 2020-09-12 21.30.54.png

右下のAddボタンを押すと、、main.dartで指定したUserデータが登録され、取得できているのも確認できました!

スクリーンショット 2020-09-12 21.58.37.png

終わり

質問やご指摘があればコメントにお願いします。

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

Railsで使用するワンライナーの個人的なまとめ

概要

Railsで使用するワンライナーの個人的なまとめ

routes情報を出力

使用シーン
railsコマンドのroutesを使用すると4つの情報が表示される。
Prefix , Verb, URI Pattern, Controller#Action

//routesの実行
$ ./bin/rails routes

//結果の出力----
Prefix Verb   URI Pattern                                                                            Controller#Action
users POST   /v1/users(.:format)                                                                      users#create
user GET    /v1/users/:id(.:format)                                                                 users#show
...

この出力情報を加工して、稀にパス情報(URI Pattern)だけ抜き出したい場合がある。

// v1  から始まる パス文字列のみが欲しい場合
$ ./bin/rails routes |grep v1 |awk -F " " '{ print $(NF-1)}'

// 結果の出力ーーーーーーー
/v1/users(.:format)
/v1/users/:id(.:format)
...

// Verbも含んで結果を出したい場合
./bin/rails routes |grep v1 |awk -F " " '{ print $(NF-2) " " $(NF-1)}'
POST /v1/users(.:format)
GET /v1/users/:id(.:format)
...

解説
1. 結果リストをv1のみに絞っている(絞る文字は任意で変更)
2. awk -F " " で スペース(" ")をセパレータに指定している。
3. print $(NF-1) で NF(末尾)から1文字目を printしている。(末尾から指定するのがポイント。でないとズレる)

ここに、sortしたりするとざっくりのエンドポイントの一覧が得られる。

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

Rails Tutorial 第13章 S3のエラー以外は完了

2020/8/30 1.5時間

13.1.3まで進めました。

2020/8/31 0.5時間

13.16まで進めました。

2020/9/1 0.5時間

13.21まで進めました。

2020/9/2 0.5時間

13.22まで進めました。

2020/9/3 2.0時間

有給休暇で、13.29まで進めました。

2020/9/4 0.5時間

13.2.3.2まで進めました。

2020/9/5 2.5時間

休みの日で、13.3.1まで進めました。

2020/9/6 1.5時間

休みの日で、13.3.4まで進めました。

2020/9/7 0.5時間

13.55まで進めました。

2020/9/8 0.5時間

13.3.5.1まで進めました。

2020/9/9 0.5時間

13.3完了です。

2020/9/10 2.0時間

有給休暇で、13.63まで進めました。
Cloud9のディスクが不足でgemのinstallができず、ディスクを拡張しました。

2020/9/11 0.5時間

13.4.2完了です。

2020/9/12 4.0時間

休みの日で、13.4.3完了です。
ImageMagickのインストールエラーで時間をとられました。さらにAWS S3のエラーで2時間ほどはまり、この日はあきらめました。

2020/9/13 1.5時間

休みの日で進めました。S3のエラーは根本は解決しませんでしたが、そのまま進めました。

13章を完了です。
所要時間は19.0時間です。

cloud9でyumがないためImageMagickがインストールできない

ubuntu:~/environment/sample_app (user-microposts) $ sudo yum install -y ImageMagick
sudo: yum: command not found

yumがないとエラー、Ubuntuではyumではなくaptのようです。
ネットで調べ、apt-getを入力します。

ubuntu:~/environment/sample_app (user-microposts) $ sudo apt-get update
ubuntu:~/environment/sample_app (user-microposts) $ sudo apt-get install imagemagick
Reading package lists... Done
Building dependency tree       
Reading state information... Done
You might want to run 'apt --fix-broken install' to correct these.
The following packages have unmet dependencies:
 imagemagick : Depends: imagemagick-6.q16 (>= 8:6.9.2.10+dfsg-2~) but it is not going to be installed
 linux-headers-5.3.0-1034-aws : Depends: linux-aws-5.3-headers-5.3.0-1034 but it is not going to be installed
 linux-image-5.3.0-1034-aws : Depends: linux-modules-5.3.0-1034-aws but it is not going to be installed
E: Unmet dependencies. Try 'apt --fix-broken install' with no packages (or specify a solution).

エラーがでたので、メッセージにあるTryの後のコマンドを入力してみたところ、無事できました。

ubuntu:~/environment/sample_app (user-microposts) $ sudo apt --fix-broken  install 
..done
ubuntu:~/environment/sample_app (user-microposts) $ sudo apt-get install imagemagick

演習13.4.4.2 エラーがでると書いてあるが、エラーはでなかった

テキストではtestでエラーがでると書いてありますが、エラーはでませんでした。rails.pngが小さくて縮小されないのではと思い、ダウンロードしてみたところ、凄く小さいアイコンファイルでした。サイズが大きいファイルで置き換えてみましたが、エラーはでません。

リスト13.68のファイルを作ってみましたが、結果が変わらないのでこのファイルが効いているのかも分かりませんでした。

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

【Rails】content_tagを解説してみる。

忘備録的に書いていきます。間違っている点などありましたら、コメントしていただけるとありがたいです?‍♂️

 【概要】content_tagとは

  • Railsのview helperで使われる。
  • HTMLコードの要素を生成することができる。

 【何が嬉しいのか?】

HTMLコードを直に書くとセキュリティ面で脆弱性が出てしまうことがある。(XSS,CSRFであったり。)

上記のようなことを防ぐために、Railsに乗っていく(content_tagを使う)とセキュリテイ面で恩恵を受けることができます。

HTMLerbがごちゃごちゃになってしまう場合に整理して書けるというメリットもあります。

小さくパーシャル化するのにも使えるかもしれませんね。

 【基本的な使い方】

content_tag :p, 'Hello, World!' # => <p>Hello, World!</p>

第一引数にタグを指定し、第二引数にcontentとなる部分を埋め込む。

ちなみに、第二引数にはシンボル(:pみたいな)だけではなく、文字列も許容してくれる

content_tag 'p', 'Hello, World!' # => <p>Hello, World!</p>

少し、実践的な書き方をするとこんな感じ???

content_tag :p, @user.name # => <p>佐藤</p>

 「こんなこともできるよ」集

 HTMLオプションはハッシュで

content_tag :p 'Hello' class: 'hoge' # => <p class='hoge'>Hello</p>

こんな感じでclassを持たせることもできます。ちなみに、複数のclassを持たせるには下記のように書けばOK

content_tag(:p, 'Hello', class: ["hoge", "foo"])
# => <div class="hoge foo">Hello</div>

 ネストもできるよ

content_tag :div do
  content_tag :p @user.name
  content_tag :p @user.age
end

こんな感じにネストして書くこともできる。

 おいおい、#tagって知ってるか??

下記はみんな大好きDHHのissueです。

https://github.com/rails/rails/issues/25195 

content_tag :span, nil, class: 'bookmark'
# 下記は上記と同じ意味
tag.span class: 'bookmark'

すごい直感的に書けるようになりましたよね。

色々変わったのですが、個人的には下記がお気に入り。

tag.div(id: 'header') { |tag| tag.span 'hello' }
# <div id="header"><span>hello</span></div>

でも、下記も簡潔に書けますよね。

<%= tag.p do %>
 hoge
<% end %>

特に理由がなれば、content_tagではなく、tagを使うと可読性が上がるかな〜と思いました。

 参考関連記事

https://qiita.com/ren0826jam/items/0ebd451d1da90e496c2f
https://makicamel.hatenablog.com/entry/2019/03/07/224552
https://techracho.bpsinc.jp/hachi8833/2018_11_01/48191

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

Rails 6で認証認可入り掲示板APIを構築する #8 seed実装

Rails 6で認証認可入り掲示板APIを構築する #7 update, destroy実装

seedとは

開発中にテストデータが必要な時、都度rails cやらcreate actionを叩いてレコード作るのは手間です。
そこで、コマンドを叩くだけであらかじめ設定しておいたレコードを、50だろうが1000だろうが簡単に作れるseedを取り入れます。

ファイル作成とロード

rails seedだけでググると大抵db/seeds.rbに直書きばかり出てきますが、modelが多くなると管理がしづらくなります。
そこでファイルを分割し、db/seeds.rbからrequireで呼ぶ形式にします。

こうすることで追加削除時の管理がしやすくなりますし、seedはファイル指定で実行することもできるので便利です。

$ mkdir db/seeds
$ touch db/seeds/post_seeds.rb
db/seeds.rb
# frozen_string_literal: true

seed_models = %i[post]
seed_models.each do |model|
  require "./db/seeds/#{model}_seeds"
end
db/seeds/post_seeds.rb
# frozen_string_literal: true

unless Post.exists?
  20.times do
    Post.create!(subject: "hoge", body: "fuga")
  end
end

今後seed対象のmodelが増えることを予想し、seed_modelsという変数に追加していくことで外部ファイル読み込みをするようにします。
ディレクトリ走査することで都度db/seeds.rbを変更しなくても良い作りにできるのですが、postモデルの前に今後作るuserモデルのレコードが存在していないといけない等の依存関係が発生するので、手動で実行順を弄れるように書いています。

post_seeds.rbの中身は単純にPost.create!を20回実行しているだけです。
!を付けることによりバリデーションエラーで登録できなかった際に例外が投げられるので、気付いたらレコードが生成されていなかった事態を防げます。

$ rails db:reset
$ rails db:seed
$ rails c
[1] pry(main)> Post.count
   (1.1ms)  SELECT COUNT(*) FROM "posts"
=> 20

db:resetすることでテーブルを全dropし再生成。これでレコードがある時のみ動くseedが実行されます。
そしてdb:seedすることで、20レコードが追加されていることが確認できます。

Fakerの導入

レコードができたのはいいのですが全部subjectがhoge, bodyがfugaになっています。
このままだと、何かしらの原因でAPIから取得したら同一レコードが紛れていたりしても気付きづらくなります。
とはいえ完全ランダムな文字列を都度作るのも手間だし…ということで、役立つのがFakerです。

とりあえず入れてみましょう。

Gemfile
...
group :development, :test do
...

+   "faker"
end
...
$ bundle

試しに使ってみましょう。

$ rails c
[1] pry(main)> Faker::Name.unique.name
=> "Miss Porter Kovacek"
[2] pry(main)> Faker::Name.name
=> "Felicita Durgan"
[3] pry(main)> Faker::Name.name
=> "Yong Weissnat"
[4] pry(main)> Faker::Name.name
=> "Sandie Oberbrunner"

こんな感じに、実行のたびにランダムな名詞や文章を自動で返してくれるものです。
デフォルトで定義されている語句はGithubで確認すると良いです。
人名や動物、住所や電話番号等だけでなく、映画、漫画、ゲーム、ドラマ、音楽なんかもあります。ポケモン名とかONE PIECEの悪魔の実とかまでありますね。

seedsにFakerを入れる

db/seeds/post_seeds.rb
# frozen_string_literal: true

unless Post.exists?
  20.times do
-    Post.create!(subject: "hoge", body: "fuga")
+    Post.create!(subject: Faker::Lorem.word, body: Faker::Lorem.paragraph)
  end
end
$ db:reset
$ db:seed
$ rails c
[1] pry(main)> Post.all
  Post Load (0.4ms)  SELECT "posts".* FROM "posts"
=> [#<Post:0x000000000636cbe8
  id: 1,
  subject: "quos",
  body: "Earum numquam qui. Impedit autem molestias. Ipsum adipisci eos.",
  created_at: Sun, 06 Sep 2020 15:36:27 UTC +00:00,
  updated_at: Sun, 06 Sep 2020 15:36:27 UTC +00:00>,
 #<Post:0x00000000063b5be0
  id: 2,
  subject: "vero",
  body:
   "Impedit distinctio saepe. Adipisci cupiditate officiis. Vel et deleniti.",
  created_at: Sun, 06 Sep 2020 15:36:27 UTC +00:00,
  updated_at: Sun, 06 Sep 2020 15:36:27 UTC +00:00>,

ランダムなsubject, bodyのレコードができていることが確認できます。

Fakerを日本語化する

せっかく入れたのに英語だと日本語サイトを作る上で不便なので、ローカライズしましょう。
db:seedはdev環境で実行するので、dev環境の時にFakerを日本語化してみます。

config/environments/development.rb
 Rails.application.configure do
...

+  Faker::Config.locale = "ja"
 end

常に日本語化が嫌なのであれば、Faker::Config.locale = "ja"をdb/seeds/post_seeds.rbに入れてもOKです。
その後再度seedを実行します。

$ rails db:reset
$ rails db:seed
$ rails c
[1] pry(main)> Post.all
  Post Load (0.3ms)  SELECT "posts".* FROM "posts"
=> [#<Post:0x0000000006480b88
  id: 1,
  subject: "いく",
  body: "警官総括大尉。めいしぼきんかたみち。伝統徳川超〜。",
  created_at: Sun, 06 Sep 2020 15:45:27 UTC +00:00,
  updated_at: Sun, 06 Sep 2020 15:45:27 UTC +00:00>,
 #<Post:0x00000000064fb928
  id: 2,
  subject: "フランス語",
  body: "〜系けいかん先週。うえる自宅そだてる。店舗にんい高値。",
  created_at: Sun, 06 Sep 2020 15:45:27 UTC +00:00,
  updated_at: Sun, 06 Sep 2020 15:45:27 UTC +00:00>,

無事に日本語化されていますね。
なお、日本語化されているものはGithubで確認できます。逆に言えばこの中に無いものは英語のままです。

続き

Rails 6で認証認可入り掲示板APIを構築する #9 serializer導入
連載目次へ

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

Rails uninitialized constant エラーの解決へのシンプルなチェックリスト

これはなにか

Railsをそれなりにやっているにも関わらず、uninitialized constant error にハマってしまったのでチェックリストをここにまとめておこうと思う。

結論

チェックするべき項目は以下3点である。

  1. ファイル名とclass名が一致しているか?
  2. ファイルpathとmoduleの定義が一致しているか?
  3. Railsにファイルを読み込ませているか?

以下詳細をかいていく。

ファイル名とclass名が一致しているか?

以下のようなclassが存在していたら、ファイル名はqiita_user.rb でないといけない。

class QiitaUser
  def hoge
  end
end

ファイルpathとmoduleの定義が一致しているか?

以下のようなmoduleにnestされたclassが存在していたら、pathは **/v1/auth/user.rb のようになるだろう。

class V1::Auth::User
end

OR

module V1
  module Auth
    class User
    end
  end
end

Railsにファイルを読み込ませているか?

Railsのデフォルトのフォルダ以外に、フォルダを追加する場合、autoload pathに追加したフォルダへのpathを定義する必要がある。
app/lib/hoge/foo.rb を追加したとする。この場合、config/application.rb に以下の様な形で、autoload pathを追加する必要がある。

module App
  class Application < Rails::Application
    # 省略

    config.autoload_paths += Dir.glob("#{config.root}/app/lib")
  end
end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

slimの長い構文を途中で改行する

Railsアプリ開発時にslimを使ってhtmlを書いているときに、このような長い構文がある。これを改行する方法。

.card-toolbar
  a.btn.btn-primary.status-editable data-confirm="全ユーザーの編集権限を「編集可」に変更します。よろしいですか?" style="font-size: 15px; margin: 0 10px;" href="#{toggle_status_editable_admins_supplier_company_path(supplier_company_code: model.code, supplier_company_id: model.id)}" role="button" 編集可
  a.btn.btn-danger.status-editable data-confirm="全ユーザーの編集権限を「編集可」に変更します。よろしいですか?" style="font-size: 15px; margin: 0 10px;" href="#{toggle_status_not_editable_admins_supplier_company_path(supplier_company_code: model.code, supplier_company_id: model.id)}" role="button" 編集不可

こんな感じで[]をつけて改行できる。

.card-toolbar
  a[data-confirm="全ユーザーの編集権限を「編集可」に変更します。よろしいですか?" 
    style="font-size: 15px; margin: 0 10px;" 
    href="#{toggle_status_editable_admins_supplier_company_path(supplier_company_code: model.code, supplier_company_id: model.id)}" 
    role="button"]
    .btn.btn-primary.status-editable
     | 編集可
  a[data-confirm="全ユーザーの編集権限を「編集不可」に変更します。よろしいですか?" 
    style="font-size: 15px; margin: 0 10px;" 
    href="#{toggle_status_not_editable_admins_supplier_company_path(supplier_company_code: model.code, supplier_company_id: model.id)}" 
    role="button"]
    .btn.btn-danger.status-editable 
     | 編集不可
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Rails]外部キー制限かかっているときにdestoryできないエラーを発生させたい時の対処

はじめに

レコード削除時に関連づけられたレコードが存在するときに、例外やエラーを発生させたい。
外部キーで使用されていると親要素が消されては困るというシチュエーションです。

削除しようとするとRubyのエラー画面が出てしまう。

原因

Modelに関係づけされたモデルに対する挙動を定義するが無いからでした。
マイグレーションファイルのforeign key: Trueだけでは不十分でした。

対策

Modeldependentオプションを記述することで解決しました。
該当のモデルに以下の記述をします。

#contractというモデル
class Contract < ApplicationRecord
  has_many :...., dependent: :restrict_with_error

end

アソシエーション記述の後にdependent: :restrict_with_errorと記述することでdestoryできないときのエラーを表示することができた。
以下の条件分が成り立ち解決しました!。

#contractというモデルのコントローラー
def destroy
    contract = Contract.find(params[:id])
    if contract.destroy
      redirect_to contract_path, notice: "選んだ契約を削除しました"
    else
      redirect_to contract_path, alert: "使用中のため削除できません"
    end
end

参照

https://dorarep.page/articles/rails-dependent#dependent_restrict_with_exception_restrict_with_error

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

[Rails]errorsメソッドでメッセージ表示させるとビューが崩れる時の対処

はじめに

formで入力に誤り(例えば無入力)があるとエラーメッセージを表示させるためにerrorsメソッドを用いて記述し、動きを確認するとビューが崩れるという問題が発生しました。その時の自分の解決した方法です。

原因

崩れたビューを確認するとfield_with_errorsというdivクラスが付与させていていることを確認した。これが原因である。

対処

今回はこのfield_with_errorsというdivクラスが付与されないようにした。

config/application.rbに以下の記述をした。

module #モデル名
  class Application < Rails::Application

    config.load_defaults 6.0
    config.i18n.default_locale = :ja

    config.action_view.field_error_proc = Proc.new { |html_tag, instance| html_tag }
    #↑この記述をプラスした

  end
end

こうすることで余計なクラスが付与されずにできました!

参考

https://yukimasablog.com/rails-field-with-errors

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

[Rails]errorsメソッドでエラーメッセージが出せないときの対処

はじめに

例えばresult.saveのように変数resultの保存をしようとした時に、失敗するとresultにエラーメッセージが格納される。
これを利用してエラーメッセージを表示させたいがRubyのエラー画面となる。

言い換えると思い通りのエラーメッセージが出ない。

エラー時にコンソールで確認の以下のようになる

result.errors.any? => False

保存できてないのにエラー出てないことになってる。

今回は無入力で登録しようとするとエラーを出したい。

原因

Modelにバリデーション記述が無かったことが原因。
テーブル作成時にはカラムに制限(無入力制限のため null: falseとした)がそれだけではエラーメッセージがでない。

対策

Modelに以下のような記述をした。

class Result < ApplicationRecord
  belongs_to ...
  has_many ...
  validates :name, presence: true

  ...
end

validates :name, presence: trueと記述することで

result.errors.any? => True

となり

result.errors.full_messages

で、エラーメッセージ全件の内容を取得しeachを用いて取り出せました!

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