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

mac環境でrailsを起動する(mysql)

今回はrailsを用いたポートフォリオ作成の為に今までcloud9で開発していたのをローカル環境で行う。その備忘とアウトプットです。
今後も掲載していくので何か気付きがありましたら報告よろしくお願いします!:bow_tone1:

rails ruby などなどはすでにインストールを以前済ませた為mysqlの起動から

mysqldのターミナル上での起動
mysql.server start

mysqldのターミナル上での起動ステータスの確認
mysql.server status

mysqlが起動していなかった場合
ERROR! MySQL is not running

mysqlが起動していた場合
Starting MySQL
.. SUCCESS!

となるmysqlが起動されステータスokであれば
rails sでrailsサーバーを起動するtと以下のログが時発生するので

image.png
https://gyazo.com/35a5d81be14535389ca6fce42a2b62e3

最終行の://localhost:3000をコピーしhttp://localhost:3000
ネットワークで検索をかける

827992458a77dd1af90c00ee8da3ddc4.jpg

見覚えのあるこちらの画面が出ればローカル環境でのrails起動が完了する。

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

Railsチュートリアル テストについて

はじめに

Railsチュートリアルを学習すると、第3章にいかに「自動化テスト」が重要であるか書いてあります。テストの書き方については色々議論の余地があるそうですが、本記事ではテストについて自分なりに抑えていたほうが良いと思われることをまとめました。

テストフレームワークについて

テストフレームワークにもいくつか種類があり、それぞれコード書き方等の特徴があります、ここでは主だった2種類のフレームワークを紹介します。

参考にしました →Railsの人気テストフレームワーク6選!
         RSpecとMinitest、使うならどっち?

 Minitest

 ・Railsのデフォルトのフレームワーク。
 ・学習コストは比較的低い。
 ・Railsチュートリアルでも使用されている。
 ・処理速度は比較的早いといわれている。
 ・デフォルトの機能は必要最小限。
 ・コード量は少なく、シンプル

 Rspec

 ・自然言語で近い方たちで書かれている。
 ・利用率が高い。
 ・慣れるまでが時間がかかる。
 ・Minitestに比べると処理速度は遅いといわれている。
 ・デフォルトの機能は豊富。
 ・コード量は多くて、複雑。

どちらも一長一短みたいですね。初心者はMinitestから入って、その後Rspecも学ぶのが一番ですかね。

テストの種類について

参考する文献によって2種類だったり、それ以上だったりしますが今回は2種類のテストについて紹介します。

参考にしました →Rails テスティングガイド
         ソフトウェアテスト -wilki

 単体テスト

 関数やメソッドなど小さな単位で行うテスト。モデルやビューヘルパー単体の動作をチェックします。

例:

test/helpers/application_helper_test
  test "full title helper" do
    assert_equal full_title,         "Ruby on Rails Tutorial Sample App"
  end

assertとはオブジェクトまたは式を評価して、期待された結果(true)が得られるかどうかをチェックするコードです。
上記はassert_equalがfull_titleが"Ruby on Rails Tutorial Sample App"と完全一致しているかどうか検証しています。

test/models/user_test.rb
  test "name should not be too long" do
    @user.name = "a" * 51
    assert_not @user.valid?
  end

assert_notとは、assertとは逆にfalseが得られるかどうかをチェックするコードです。
上記はユーザーの名前が51文字の場合、ユーザーが有効でないことを検証しています。

 統合テスト

 プログラムを組み合わせて行うテスト。個々の機能が正しく連動しているか、ユーザーの実際の操作を想定してチェックします。

例:

test/integration/users_login_test
  test "login with valid email/invalid password" do
    get login_path
    assert_template 'sessions/new'
    post login_path,params: {session: { email: @user.email,
                          password: "invalid"}}
    assert_not is_logged_in?
    assert_template 'sessions/new'
    assert_not flash.empty?
    get root_path
    assert flash.empty?
  end

上記は、有効でないパスワードを入力したユーザーに対するテストを行っています。

1. getメソッドでログインページ(login_path)を生成する。
  →get login_path

2. ログインページのテンプレートが選択されているか検証する。
  →assert_template 'sessions/new'

3. login_pathへ有効なメールアドレスと有効でないパスワードを投稿する。
  →post login_path,params: {session: { email: @user.email,password: "invalid"}}

4. ユーザーがログインしていないことを検証する。
  →assert_not is_logged_in?

 is_logged_in?メソッドは下記により定義されています。

test/test_helper.rb
  #テストユーザーがログイン中の場合にtrueを返す
  def is_logged_in?
    !session[:user_id].nil?
  end

5. ログインページのテンプレートが選択されているか検証する。
  →assert_template 'sessions/new'

6. エラーメッセージが表示されていることを検証する。
  →assert_not flash.empty?

7. getメソッドでホームページ(root_path)を生成する。
  →get root_path

8. エラーメッセージが表示されていないことを検証する。
  →assert flash.empty?

という流れになっています。

まとめ

正直Railsチュートリアル終えても自力でテストをかけるか自信がないです。また、テストはどこまで厳密にやるかセンスが問われそうですね。数をこなしてなれていくのが大事だと思いました。

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

Rails 6で認証認可入り掲示板APIを構築する #1 環境構築

はじめに

Rails APIに関して、チュートリアルがあまり見当たらなかったため作成しました。
なお、筆者自身もRails API触り始めて半年程度なので、誤っている点などあればご指摘ください。

Rails APIモードを使い、掲示板APIを構築してみようと思います。
なお、フロントエンドは本チュートリアルに含みません。
API完成後、ご自由なものを選択してください。

対象読者

  • Ruby on Railsチュートリアルを1周し、Railsアプリケーションの実装方法や用語がなんとなく分かっている方
  • モノリシックなRailsではなく、RailsのAPIサーバを構築したい方

RailsのCRUD操作に関する最低限の知識はないと、追いつくのは難しいかもしれません。

最終的な環境・Gem

AWS Cloud9
PostgreSQL 9.5.15
Ruby 2.7.1
Ruby on Rails 6.0.3.2

Gem
シリアライザ:active_model_serializers
認証:devise_token_auth
認可:pundit
ダミーデータ:Faker
静的コード解析:rubocop
テスト:RSpec, FactoryBot
(記事執筆中なので、今後増減の可能性あり)

Cloud9環境の構築

まずはAWSアカウントをお持ちでない場合、アカウントを取得しましょう。
本記事ではアカウントを所持していること前提として解説をします。

AWSマネジメントコンソールでCloud9を探します。
s1.png

Create environmentを押します。
s2.png

分かりやすい名前と説明を入れ作成します。
s3.png
2ページ目は基本的に全てデフォルトで問題ないでしょう。

構築に数分かかるので放置します。
完了したらこんなIDE画面が表示されます。

s4.png

Rubyのバージョンアップ

最初からRubyもRailsも入っていますが、バージョンが古いですね。

$ ruby -v
ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-linux]
$ rails -v
Rails 5.0.0

s5.png

記事執筆当時の安定バージョンは2.7.1なので上げましょう。

$ rvm install 2.7.1
...
$ rvm use 2.7.1
$ ruby -v
ruby 2.7.1p83 (2020-03-31 revision a0c7c23c9c) [x86_64-linux]

無事上がりました。

参考:【AWS】Cloud9でRubyのバージョンをアップデートする方法(rvm使用)

Railsのバージョンを上げる

こちらも、記事執筆当時最新が6.0.3に対し、インストールされているのが5.0.0とだいぶ古いので上げましょう。

$ gem install rails
...
$ rails -v
Rails 6.0.3.2

簡単ですね。

参考:Cloud9でRailsのバージョンを変更する

postgreSQLを入れる

なぜpostgresをこのタイミングで入れるか?

Railsのデフォルト開発DBはSQLiteですが、Amazon LinuxのCloud9環境だとSQLiteのバージョンが低くアプリケーションが作れません。バージョン上げがなかなかしんどいので、今回はPostgreSQLを使います。

また、SQLiteはその名の通り機能的にもliteなので、他のRDBMSに比べてできないことも多く、無駄に詰まることがあるので、それを回避するのも要因の一つです。

まずインストールします。

$ sudo yum install postgresql95-devel postgresql95-server postgresql95-contrib
$ psql --version
psql (PostgreSQL) 9.5.15

次に初期化と起動、ユーザー作成。Permission deniedは無視でOKです。

$ sudo service postgresql95 initdb
Initializing database: sudo service postgresql95 start     [  OK  ]
$ sudo service postgresql95 start
Starting postgresql95 service:                             [  OK  ]
$ sudo -u postgres createuser -s ec2-user
could not change directory to "/home/ec2-user/environment": Permission denied

参考:[Rails6]AWS Cloud9(Amazon Linux)で動かしてみる

APIモードでrails newする

$ rails new bbs -d postgresql --api

--apiと付けることでRails APIモードとなります。
APIに不要なファイルが生成されない状態で作られます。

Railsのテストサーバを立ち上げる

New Terminalから新しいターミナルを立ち上げます。

s6.png

ローカル等ではrails sだけでテストサーバが立ち上がるのですが、Cloud9はオプションが必要です。

$ cd bbs/
$ rails s -b $IP -p $PORT
...
* Listening on tcp://127.0.0.1:8080
Use Ctrl-C to stop

これでサーバが立ち上がります。
なお、書いてあるとおりCtrl+Cで止めることができます。
port8080で動いていることが分かりますね。

先程のターミナルに戻り、ちゃんと立ち上がったかcurlで確認してみましょう。
テストサーバを立ち上げたターミナルウィンドウは、止めずに放置してください。

$ cd bbs/
$ curl localhost:8080                                                                                                                  
{"status":500,"error":"Internal Server Error","exception":"#\u003cActiveRecord::NoDatabaseError: FATAL:  database \"bbs_development\" does not exist

どうやらDBが無くて怒られてます。
railsでDBの初期化をしましょう。

$ rails db:create
...
Created database 'bbs_development'
Created database 'bbs_test'
$ curl localhost:8080                                                                                                                  

なんかhtmlっぽい大量の文字列が出てきたら、とりあえず500系エラーは起きていないので大丈夫です。

IDEの初期設定をする

インデントはspace2にしたいので、画面右端にある歯車マークから設定画面へ。
Code Editor (Ace)を選択し、

Soft Tabsを4から2に偏向
On Save, Strip Whitespace

して閉じます。

edit.png

ホストを許可する

Rails6から、ホストを許可しないとエラーでページが表示されません。
Cloud9で実行しているので、AWSのhostsを追加します。
保存したらテストサーバを一度Ctrl+Cで止め、再起動してください。そうしないとconfigが反映されません。

config/application.rb
...
module Bbs
  class Application < Rails::Application
...

+    config.hosts << '.amazonaws.com'

...
  end
end

Welcome画面を表示する

画面上部からPreview Running Applicationを押すと、小さくアプリケーションウィンドウが立ち上がります。

s7.png

ですがこの小さいウィンドウでは正常に実行されないため、Pop Out Into New Windowを押して別ウィンドウを立ち上げます。

s8.png

するとついに、正常にWelcome画面が表示されます。

welcome.png

続き

Rails 6で認証認可入り掲示板APIを構築する #2 gitとrubocop導入

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

can't find gem bundler (>= 0.a) with executable bundleの対処法

既存のRailsプロジェクトをcloneし、% bundle installをした際にこのエラーが発生。

% bundle install

Traceback (most recent call last):
       2: from /Users/trilingual/.rbenv/versions/2.5.1/bin/bundle:23:in `<main>'
       1: from /Users/trilingual/.rbenv/versions/2.5.1/lib/ruby/2.5.0/rubygems.rb:308:in `activate_bin_path'
/Users/triringual/.rbenv/versions/2.5.1/lib/ruby/2.5.0/rubygems.rb:289:in `find_spec_for_exe': can't find gem bundler (>= 0.a) with executable bundle (Gem::GemNotFoundException)

原因

Gemfile.lock最下部に記載のBUNDLED WITH2.0.2の記述と自身がインストールしたbundlerのバージョンと異なってしまっていることが原因。

Gemfile.lock
<省略>
BUNDLED WITH
2.1.4

これと

% bundler -v
Bundler version 1.17.1

これが違うということです。

解決策

①アプリケーションのGemfile.lock最下部に記載のBUNDLED WITH○○の記載を確認

Gemfile.lock
<省略>
BUNDLED WITH
2.1.4

②Gemfile.lockに記載のバージョンをインストールする(今回は2.1.4)

% gem install bundler -v 2.1.4

まとめ

アプリケーションで想定している環境自身のPCの環境
を合わせてあげるようなイメージです。
・PCを買い替えてアプリを開いたとき
・アプリをgit cloneしたとき
・Rubyのバージョンを変更したとき
のようなパターンでこのエラーが頻発しているように感じます。

同じエラーが発生した方へ届きますように!

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

[rails]ネストした関係でどうやって親情報を表示させるか

やりたいこと

前提・Itemの情報がないとOrderの情報は成り立たない関係
(Orderにitem_idカラムがある(どのitem情報を指しているのか?))
(親)itemテーブル>(子)orderテーブルになる。

orderコントローラでindexアクションを起動させ(index.html.erb)に
(親)のitem情報を表示させたかったらどうやって記述するの?
※例えば
itemテーブル     
id:1
ぬいぐるみ4000円

orderテーブル
item_id:1
user_id:3
ぬいぐるみ4000円とuser_idが結びつくんだな・・・

手順

config/routes.rbの記述内容
Rails.application.routes.draw do
   devise_for :users
  root  'items#index'
  resources :items do
    resources :orders, only: [:create, :index]
  end
end

1.親items>子ordersの関係をroutes.rbで記述する

resources :items do
    resources :orders, only: [:create, :index]

これで(親)item>(子)orderの関係を指定することができる。ネストすることができる

2.ordersコントローラーで@item = Item.find(params[item_id])と記述する

def index
     @item = Item.find(params[:item_id])
  end

@itemの箱の中にitemテーブルの情報を入れる。
どのitemテーブルの情報を指定するか[:item_id]で指定できる
ユーザーが選択したitem情報を@itemに代入する

3.@itemをorder#indexのビューファイルに記述する

orderコントローラーのindex.html.erbに記述
<%= @item.item_name %>

これで(親)itemテーブルからデーターを抜き出して
(子)orderコントローラーのindex.html.erbのビューに表示することができる。

まとめ

分かりにくいまとめですが
要は親テーブルの情報を抜き出して、子のビューに表示させたかったら
ネストを使って関係を示し、子コントローラーでparams[:親_id]と記述すれば
親から情報を抜き出して子のビュー表示することができるという内容でした。

うまくまとめることができないのは私の力不足です・・・。
具体的な画像とテーブル情報が添付していると分かりやすくなったかも。

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

[rails]ネストした関係で親情報を子ビューに表示する方法

やりたいこと

前提・Itemの情報がないとOrderの情報は成り立たない関係
(Orderにitem_idカラムがある(どのitem情報を指しているのか?))
(親)itemテーブル>(子)orderテーブルになる。

orderコントローラでindexアクションを起動させ(index.html.erb)に
(親)のitem情報を表示させたかったらどうやって記述するの?
※例えば
itemテーブル     
id:1
ぬいぐるみ4000円

orderテーブル
item_id:1
user_id:3
ぬいぐるみ4000円とuser_idが結びつくんだな・・・

手順

config/routes.rbの記述内容
Rails.application.routes.draw do
   devise_for :users
  root  'items#index'
  resources :items do
    resources :orders, only: [:create, :index]
  end
end

1.親items>子ordersの関係をroutes.rbで記述する

resources :items do
    resources :orders, only: [:create, :index]

これで(親)item>(子)orderの関係を指定することができる。ネストすることができる

2.ordersコントローラーで@item = Item.find(params[item_id])と記述する

def index
     @item = Item.find(params[:item_id])
  end

@itemの箱の中にitemテーブルの情報を入れる。
どのitemテーブルの情報を指定するか[:item_id]で指定できる
ユーザーが選択したitem情報を@itemに代入する

3.@itemをorder#indexのビューファイルに記述する

orderコントローラーのindex.html.erbに記述
<%= @item.item_name %>

これで(親)itemテーブルからデーターを抜き出して
(子)orderコントローラーのindex.html.erbのビューに表示することができる。

まとめ

分かりにくいまとめですが
要は親テーブルの情報を抜き出して、子のビューに表示させたかったら
ネストを使って関係を示し、子コントローラーでparams[:親_id]と記述すれば
親から情報を抜き出して子のビュー表示することができるという内容でした。

うまくまとめることができないのは私の力不足です・・・。
具体的な画像とテーブル情報が添付していると分かりやすくなったかも。

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

railsのsessionデータ更新でzero-length delimited identifier at or near """"のエラー

開発環境である時見慣れぬエラーが出て、もしも本番で発生したらどうすればいいのか途方に暮れそうなので、復旧手順を試行錯誤したメモです。同じエラーが出た場合にヒントになればと思うのですが、同じ状況とは限らないのでご注意。

環境 rails 5.2.4.3 postgresql 12.2 pg 1.2.2 activerecord-session_store 1.1.3

状況
 ログインするサイトで、ブラウザからアクセスした際に以下のエラーが出て、サイトをまったく利用できない。

development.log
ActiveRecord::StatementInvalid (PG::SyntaxError: ERROR:  zero-length delimited identifier at or near """"
LINE 1: ... SET "data" = $1, "updated_at" = $2 WHERE "sessions"."" = $3
: UPDATE "sessions" SET "data" = $1, "updated_at" = $2 WHERE "sessions"."" = $3)
:

activerecord (5.2.4.3) lib/active_record/connection_adapters/postgresql_adapter.rb:611:in `exec_params'
activerecord (5.2.4.3) lib/active_record/connection_adapters/postgresql_adapter.rb:611:in `block (2 levels) in exec_no_cache'
...

ログに羅列されているファイル名には自分が書いたファイルはなく心当たりなし。昨日何やったっけ。とりあえずこの状態のDBをダンプして保存。

セッションの情報はactiverecord-session_storeでactiverecordに記録していて、ユーザがアクセスするとこの情報を更新する。その際にこのエラーが出ている。
正常に動いている本番データと比べると、whereのところは本来は
WHERE "sessions"."id" = $3 のはずであることがわかった。

activerecord-5.2.4.3/lib/active_record/connection_adapters/abstract/database_statements.rb
までは追っかけて、発行するsqlを生成する関数の入力値arelでname="id"であるところが、エラーが出るパターンではname=""になっていたところまではわかったが、そこから遡るのは断念。

pg_dumpしてpsqlでloadしても同じエラー。そこで、pg_dumpをデータだけにして、dbは一度初期化、migrateしてデータを流し込んだら復旧できた。復旧後改めてpg_dumpして比較すると、

ALTER TABLE ONLY public.sessions
    ADD CONSTRAINT sessions_pkey PRIMARY KEY (id);

が欠けていた。どこで/何をしてこれが消えたのか、心当たりなし。

sessionsのテーブルだけであれば、邪道っぽいけど、以下のようにactiverecord-session_store適用でやったのと同じmigrationを一時的に用意し、

class ResetSessionTable < ActiveRecord::Migration[5.2]
  def up
    drop_table :sessions
    create_table :sessions do |t|
      t.string :session_id, :null => false
      t.text :data
      t.timestamps
    end

    add_index :sessions, :session_id, :unique => true
    add_index :sessions, :updated_at
  end
  def down
  end
end
# pg_dump -b DB名 --data-only --table sessions >sessions_bak.sql
# ここで上記をdb:migrateしてすぐにdb:rollbackしてファイルを削除
# cat sessions_bak.sql  |/usr/local/pgsql/bin/psql -e DB名

こんな感じで停止もそこそこで復旧できる見通しはついた。もっと良い対応があるかもしれない。

結局発生の原因はわかっていないが、開発環境は初期化とかデータロードを頻繁に行っているため、そこで何らかエラーがあった可能性が高いけど、pgのバージョンの制約とか、他のgemとかの影響の可能性もなくはないので、情報共有で投稿してみました。

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

Rails+mecab+Herokuでデプロイエラーが発生した場合の対策

事象

  • Rails(6.0.0)
  • Heroku
  • Heroku Buildpacksにmecab(https://github.com/diasks2/heroku-buildpack-mecab.git)を追加

この条件で、Herokuでデプロイを行った場合、エラーが発生するケースがあると思います。

調査

デプロイエラーの内容は下記のようです。

 !     To update to the latest version installed on your system, run `bundle update --bundler`.
 !     To install the missing version, run `gem install bundler:2.0.2`

確かに、Gemfile.lockに下記のようにbundlerのバージョン指定をしています。

BUNDLED WITH
   2.0.2

そこで、Gemfile.lockのBUNDLED WITH指定を手作業で削除し、再度Herokuでデプロイします。
 ⇒正常にデプロイ完了します。

しかし、Heroku Buildpacksからmecabを外し、BUNDLED WITH 2.0.2指定を復活させ、再度Herokuでデプロイしてみたところ…
 ⇒正常にデプロイ完了します。解せません。

つまり、下記のような状態です。

- mecabあり mecabなし
BUNDLED WITH 2.0.2 デプロイNG デプロイOK
BUNDLED WITH なし デプロイOK デプロイOK

mecabとbundler v2.0.2は相性が悪いように読み取れます。

根本解決になっておらず恐縮ですが、
Rails+mecab+Herokuという(マニアックな)組み合わせでアプリケーションを作りたい場合、この事象に遭遇した際は、
手作業でGemfile.lockのBUNDLED WITH指定を削除すると、この事象を突破できると思います。

備忘

Config versにMECAB_PATH=/app/vendor/mecab/lib/libmecab.soの設定をお忘れなく!

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

文字コードの指定ミスによる、マイグレーションファイルが実行できないエラーについて。

エラーが発生いたしました。

An error has occurred

マイグレーションファイルの実行をした時です。

rails db:migrate
:Error: Specified key was too long; max key length is 767 bytes

キーの長さが最大767バイトを超えてしまったとエラー文です。

直前の自身の操作を思い返してみます。

t.string :name,               null: false

キーについてマイグレーションファイルに記述を行なっていました。

では、このキー(:name)が怪しいです。
名前の文字数に関連する何かであると考えられます。

エラー文を検索すると以下の記事に詳細が書かれていました。
https://qiita.com/terufumi1122/items/9ea764618eba01144e09

RailsやCakePHPのデフォルトで生成される255文字のVARCHARでは、3バイトのutf8では255×3=765でほぼぴったり収まります。一方、utf8mb4ではオーバーしてしまうので、エラーとなりました。

ここでVARCHARとは、データベースのフィールド定義などで用いられるデータ型の一つで、可変長の文字列を意味するデータ型のことです。
http://e-words.jp/w/VARCHAR.html#:~:text=VARCHAR%E3%81%A8%E3%81%AF%E3%80%81%E3%83%87%E3%83%BC%E3%82%BF%E3%83%99%E3%83%BC%E3%82%B9%E3%81%AE,%E3%82%92%E6%84%8F%E5%91%B3%E3%81%99%E3%82%8B%E3%83%87%E3%83%BC%E3%82%BF%E5%9E%8B%E3%80%82

つまり推測になりますが、カラムの型と、そのキーを記述してマイグレーションファイルを実行するということは、
そのキー(テーブルにおけるカラム)について767バイト分のデータ容量を確保するといことになるのでしょうか。
可変長ということなので、レコードごとにその容量は異なりますが、767バイトは超えてはいけなく、かつnull: falseによって0バイトでもいけないということでしょう。

ここでもう一度、上記の引用です。
RailsやCakePHPのデフォルトで生成される255文字のVARCHARでは、3バイトのutf8では255×3=765でほぼぴったり収まります。一方、utf8mb4ではオーバーしてしまうので、エラーとなりました。

Railsにおいて、255文字分のVARCHARは自動生成され、
そして、一文字ごとのデータの大きさは文字コードによって異なるみたいです。

文字コードutf8は255文字生成されたとしてもそのデータの大きさは765バイト、可変長容量内です。

よって今回、文字コードの指定がutf8mb4となっていると考えられます。

アプリケーションの開発において、
文字コードの記述があるパスは、
config/database.ymlです。

行ってみると

encoding: utf8mb4

と記述がされていました。

ecoding: utf8

に記述を書き直し。

再度マイグレーションファイルの実行をします。

:Error: Table 'users' already exists

不安を覚えましたので
Sequel Proを確認しにいきます、
文字コードに関連しているだろう記述がある部分を見つけました。

10e5c8a893f7e8c6452c104aa1e9bb54.png

utf8mb4のままの部分がございます。

データベースのリセットを一度行います。

rails db:reset

その上でマイグレーションファイルを実行いたします。

rails db:migrate

今回はエラー文が表示されませんでした。

再度、確認しに行きますと
しっかりとutf8となっています。

935e08cdb5323459dc9b03759ee1426e.png

以上でエラー解決とさせていただきます。

最後まで、お読み頂きありがとうございます。
誤った認識をしている箇所、認識が不足している部分について、ご指摘して頂けますと幸いに存じます。

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

simple_formを使ってみる/子モデルまで編集する

概要

simple_formは高機能なフォームを自動作成してくれるgemです。https://github.com/heartcombo/simple_form
フォームを作ってくれるだけでなく、エラーメッセージや項目名なども自動で表示します。

今回はテーブルが2つのシンプルなrailsプロジェクトで、
simple_formを使ってみました

※bootstrapやCSSは書いていません

image.png

実装

まずは一番シンプルな実装userモデルのみ実装します

gemfile
gem 'simple_form'
user.rb
class User < ApplicationRecord
  validates :name, presence: true
  validates :email, presence: true
  validates :age, presence: true
end
routes.rb
  resources :users
users_controller.rb
class UsersController < ApplicationController
  def new
    @user = User.new
  end

  def create
    @user = User.new(user_params)
    if @user.save
      redirect_to user_path(@user)
    else
      render 'users/new'
    end
  end

  private

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

下記migrationファイルを実行します。rails db:migrate

db/migrate/20200906_create_users.rb
class CreateUsers < ActiveRecord::Migration[5.1]
  def change
    create_table :users do |t|
      t.string :name
      t.string :email
      t.integer :age
      t.datetime :date
      t.string :url

      t.timestamps
    end
  end
end

viewファイル(new)にsimple_formを書きます。ほぼreadmeのコピペです

app/views/users/new.html.erb
<%= simple_form_for user do |f| %>
  <%= f.input :name %>
  <%= f.input :email %>
  <%= f.input :age, collection: 18..60 %>
  <%= f.input :date %>
  <%= f.input :url %>
  <%= f.button :submit %>  
<% end %>

これでフォームは表示されます。localhost:3000/users/newにアクセスした結果が以下です。
image.png

presenceのバリデーションを設定している項目にはcan't be blank と自動で表示されています。

また、emailとurlはカラム名からsimple_formが判断して、各々最適なバリデーションをしてくれます。
image.png

image.png

吹き出しのデザインも勝手にsimple_formが生成してくれたものです。

また、dateとageの項目を見れば分かるように、型に応じて入力形式を勝手に変えてくれます

ここに書いたのは一例で、
その他 colorやcountryなど、様々な入力パターンが用意されています。(reademeに書いてあります)
simple_formの使い方はこんな感じです。

子モデルを一緒に編集したい場合

userに紐ついている子モデルを、userモデルから編集することができます。
userモデルが主であるようなページで同時に編集したい場合に使います。

今回は user_propertyという子モデルを用意します。
user_properties_controller のcreateアクションではなく、
users_controllerのcreate で編集をできるようにします。(updateも同様です

またそれによってsubmitボタンを一つにできます。

下に先ほどから変化のあっるファイルを示します。

user_property.rb
class UserProperty < ApplicationRecord
  belongs_to :user

  validates :nickname, presence: true
  validates :hobby, presence: true
end
user.rb
class User < ApplicationRecord
  validates :name, presence: true
  validates :email, presence: true
  validates :age, presence: true

 has_one :user_property
  accepts_nested_attributes_for :user_property
end
users_controller.rb
class UsersController < ApplicationController
  def new
    @user = User.new
  @user.create_user_property(nickname: "takashi", hobby: "yamamoto")
  end

  private

  def user_params
    params.require(:user).permit(
      :name,
      :email,
      :age,
      :date,
       user_property_attributes: %i[
        nickname
        hobby
      ]
    )
  end

下記migrationファイルを実行します。rails db:migrate

db/migrate/20200906_create_user_property.rb
class CreateUserProperties < ActiveRecord::Migration[5.1]
  def change
    create_table :user_properties do |t|
      t.integer :user_id
      t.string :nickname
      t.string :hobby

      t.timestamps
    end
  end
end

simple_formは以下のようにかきます。
----user_proiperty-----以下が追加した項目です。
simple_field_forを使います。

app/views/users/new.html.erb
<%= simple_form_for user do |f| %>
  <%= f.input :name %>
  <%= f.input :email %>
  <%= f.input :age, collection: 18..60 %>
  <%= f.input :date %>
  <%= f.input :url %>

-------user_property----------  

  <%= f.simple_fields_for :user_property, user.user_property do |ff| %>
    <%= ff.input :nickname %>
    <%= ff.input :hobby %>
  <% end %>

  <%= f.button :submit %>  
<% end %>

これでできたのが以下です
user_propertyの項目が二つ追加されています。これで完了です。
image.png

userと同様に、バリデーションエラーが表示されます
image.png

以上です

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

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

前提

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

基本方針

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

第4章です。この章は冗長な内容だった記憶が…。つべこべ言わずやっていきます。
本日のBGMはこちら。
matryoshka "zatracenie[full album]"

 

【4.1.2 カスタムヘルパー メモ】

・新しく作ったメソッド=カスタムヘルパー
全てのページで使うヘルパー:app/helpers/application_helper.rb に設置
特定のコントローラだけが使うヘルパー:app/helpers/(該当のコントローラ名).rb に設置

 

【4.2.2 文字列 メモと演習】

・putsメソッドは戻り値にnilを返す。改行文字である\nが出力の末尾に追加される。
・printメメソッドは改行文字を追加しない。
・シングルクォート内では式展開できない。入力した文字をエスケープせずに、そのまま保持するときに便利

1. city変数に適当な市区町村を、prefecture変数に適当な都道府県を代入してください。
→ city = "sapporo" prefecture = "hokkaido"(今コンサドーレ札幌の試合観ながらやってるので)

2. 先ほど作った変数と式展開を使って、「東京都 新宿区」のような住所の文字列を作ってみましょう。出力にはputsを使ってください。
→ 下記

>> puts prefecture +  " " + city          
hokkaido sapporo
=> nil

3. 上記の文字列の間にある半角スペースをタブに置き換えてみてください。(ヒント: 改行文字と同じで、タブも特殊文字です)
→ 下記

>> puts prefecture +  "\t" + city         
hokkaido        sapporo
=> nil

4. タブに置き換えた文字列を、ダブルクォートからシングルクォートに置き換えてみるとどうなるでしょうか?
→ 下記

>> puts prefecture +  '\t' + city         
hokkaido\tsapporo
=> nil

 

【4.2.3 オブジェクトとメッセージ受け渡し 演習】

1. "racecar" の文字列の長さはいくつですか? lengthメソッドを使って調べてみてください。
→ 下記

>> s = "racecar"
=> "racecar"
>> s.length
=> 7

2. reverseメソッドを使って、"racecar"の文字列を逆から読むとどうなるか調べてみてください。
→ 下記

>> s.reverse
=> "racecar"

3. 変数sに "racecar" を代入してください。その後、比較演算子 (==) を使って変数sとs.reverseの値が同じであるかどうか、調べてみてください。
→ もう入れとったわ。下記

>> s == s.reverse
=> true

4. リスト 4.9を実行すると、どんな結果になるでしょうか? 変数sに "onomatopoeia" という文字列を代入するとどうなるでしょうか? ヒント: 上矢印 (またはCtrl-Pコマンド) を使って以前に使ったコマンドを再利用すると一からコマンドを全部打ち込む必要がなくて便利ですよ。)
→ 下記

>> puts "It's a palindrome!" if s == s.reverse
It's a palindrome!
=> nil
>> s = "onomatopoeia"
=> "onomatopoeia"
>> puts "It's a palindrome!" if s == s.reverse
=> nil

 

【4.2.4 メソッドの定義 演習】

1. リスト 4.10のFILL_INの部分を適切なコードに置き換え、回文かどうかをチェックするメソッドを定義してみてください。ヒント: リスト 4.9の比較方法を参考にしてください。
→ 下記

>> def palindrome_tester(s)
>>   if s == s.reverse
>>     puts "It's a palindrome!"
>>   else
>>     puts "It's not a palindrome."
>>   end
>> end
=> :palindrome_tester

2. 上で定義したメソッドを使って “racecar” と “onomatopoeia” が回文かどうかを確かめてみてください。1つ目は回文である、2つ目は回文でない、という結果になれば成功です。
→ 下記

>> palindrome_tester("racecar")
It's a palindrome!
=> nil
>> palindrome_tester("onomatopoeia")
It's not a palindrome.
=> nil

3. palindrome_tester("racecar")に対してnil?メソッドを呼び出し、戻り値がnilであるかどうかを確認してみてください (つまりnil?を呼び出した結果がtrueであることを確認してください)。このメソッドチェーンは、nil?メソッドがリスト 4.10の戻り値を受け取り、その結果を返しているという意味になります。
→ 下記

>> palindrome_tester("racecar").nil?
It's a palindrome!
=> true

 

【4.3.1 配列と範囲演算子 演習】

1. 文字列「A man, a plan, a canal, Panama」を ", " で分割して配列にし、変数aに代入してみてください。
→ 下記

>> a = "A man, a plan, a canal, Panama".split(',')
=> ["A man", " a plan", " a canal", " Panama"]

 
2. 今度は、変数aの要素を連結した結果 (文字列) を、変数sに代入してみてください。
→ 下記

>> s = a.join
=> "A man a plan a canal Panama"

3. 変数sを半角スペースで分割した後、もう一度連結して文字列にしてください (ヒント: メソッドチェーンを使うと1行でもできます)。リスト 4.10で使った回文をチェックするメソッドを使って、(現状ではまだ) 変数sが回文ではないことを確認してください。downcaseメソッドを使って、s.downcaseは回文であることを確認してください。
→ ひとまとめにするとこうなる(一回コンソール落としてたからメソッドの再定義めんどくさかったやん!)

>> palindrome_tester(s.split(' ').join.downcase)
It's a palindrome!
=> nil

4. aからzまでの範囲オブジェクトを作成し、7番目の要素を取り出してみてください。同様にして、後ろから7番目の要素を取り出してみてください。(ヒント: 範囲オブジェクトを配列に変換するのを忘れないでください)
→ 最初は0から始まるから6ですね。

>> A = ("a".."z").to_a
=> ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"]
>> A[6]
=> "g"
>> A[-7]
=> "t"

 

【4.3.2 ブロック 演習】

1. 範囲オブジェクト0..16を使って、各要素の2乗を出力してください。
→ 下記

>> (0..16).each { |i| puts i**2 }
0
1
4
9
16
25
36
49
64
81
100
121
144
169
196
225
256
=> 0..16

 
2. yeller (大声で叫ぶ) というメソッドを定義してください。このメソッドは、文字列の要素で構成された配列を受け取り、各要素を連結した後、大文字にして結果を返します。例えばyeller(['o', 'l', 'd'])と実行したとき、"OLD"という結果が返ってくれば成功です。ヒント: mapとupcaseとjoinメソッドを使ってみましょう。
→ 下記。これもっと賢いやり方ないかな。引数に配列じゃなくて文字列入れるだけでいいようにするとか。

>> def yeller(s)
>>   s.map(&:upcase).join
>> end
=> :yeller
>> yeller(['o','l','d'])
=> "OLD"

3. random_subdomainというメソッドを定義してください。このメソッドはランダムな8文字を生成し、文字列として返します。ヒント: サブドメインを作るときに使ったRubyコードをメソッド化したものです。
→ 下記

>> def random_subdomain
>>   ("a".."z").to_a.shuffle[0..7].join
>> end
=> :random_subdomain
>> random_subdomain
=> "unwdemsp"

4. リスト 4.12の「?」の部分を、それぞれ適切なメソッドに置き換えてみてください。ヒント:split、shuffle、joinメソッドを組み合わせると、メソッドに渡された文字列 (引数) をシャッフルさせることができます。
→ 下記

>> def string_shuffle(s)
>>   s.split('').shuffle.join
>> end
=> :string_shuffle
>> string_shuffle("foobar")
=> "arbfoo"

 

【4.3.3 ハッシュとシンボル 演習】

1. キーが'one'、'two'、'three'となっていて、それぞれの値が'uno'、'dos'、'tres'となっているハッシュを作ってみてください。その後、ハッシュの各要素をみて、それぞれのキーと値を"'#{key}'のスペイン語は'#{value}'"といった形で出力してみてください。
→ 下記

>> n = { one: 'uno', two: 'dos', three: 'tres' }
=> {:one=>"uno", :two=>"dos", :three=>"tres"}
>> n.each do |key, value|                 
?>   puts "#{key}のスペイン語は#{value}"
>> end
oneのスペイン語はuno
twoのスペイン語はdos
threeのスペイン語はtres
=> {:one=>"uno", :two=>"dos", :three=>"tres"}

 
2. person1、person2、person3という3つのハッシュを作成し、それぞれのハッシュに:firstと:lastキーを追加し、適当な値 (名前など) を入力してください。その後、次のようなparamsというハッシュのハッシュを作ってみてください。1.) キーparams[:father]の値にperson1を代入、2). キーparams[:mother]の値にperson2を代入、3). キーparams[:child]の値にperson3を代入。最後に、ハッシュのハッシュを調べていき、正しい値になっているか確かめてみてください。(例えばparams[:father][:first]がperson1[:first]と一致しているか確かめてみてください)
→ 下記。それぞれの名前とキーの意味が分かるアナタはガンバサポーター。(2020シーズン第14節のスタメンより)

>> person1 = { first: "Yuki", last: "Yamamoto" }                                    
=> {:first=>"Yuki", :last=>"Yamamoto"}
>> person2 = { first: "Yosuke", last: "Ideguchi" }
=> {:first=>"Yosuke", :last=>"Ideguchi"}
>> person3 = { first: "Shu", last: "Kurata" }
=> {:first=>"Shu", :last=>"Kurata"}
>> params = { anchor: person1, rih: person2, lih: person3 }
=> {:anchor=>{:first=>"Yuki", :last=>"Yamamoto"}, :rih=>{:first=>"Yosuke", :last=>"Ideguchi"}, :lih=>{:first=>"Shu", :last=>"Kurata"}}
>> params[:anchor][:first] == person1[:first]
=> true

 
3. userというハッシュを定義してみてください。このハッシュは3つのキー:name、:email、:password_digestを持っていて、それぞれの値にあなたの名前、あなたのメールアドレス、そして16文字からなるランダムな文字列が代入されています。
→ 下記

>> user = { name: "tk", email: "tk@mail.com", password_digest: ("a".."z").to_a.shuffle[0..15].join }
=> {:name=>"tk", :email=>"tk@mail.com", :password_digest=>"socxlgerjatyinbw"}

 
4. Ruby API (訳注: もしくはるりまサーチ) を使って、Hashクラスのmergeメソッドについて調べてみてください。次のコードを実行せずに、どのような結果が返ってくるか推測できますか? 推測できたら、実際にコードを実行して推測があっていたか確認してみましょう。

{ "a" => 100, "b" => 200 }.merge({ "b" => 300 })

→ 正直、るりまサーチで調べてもよく分かんないんですよね。日本語が日本語じゃない。なので普通にグーグル先生に頼ると、mergeメソッドは複数のハッシュを結合させるメソッドとのこと。そして、merge「前」のハッシュに、merge「後」のハッシュを結合するのですが、重複するハッシュがある場合は、「後」のハッシュが上書きされます。ということは、上の結果はbが300になるはず。実際の結果は下記。合ってますね。

>> { "a" => 100, "b" => 200 }.merge({ "b" => 300 })
=> {"a"=>100, "b"=>300}

 

【4.4.1 コンストラクタ 演習】

1. 1から10の範囲オブジェクトを生成するリテラルコンストラクタは何でしたか? (復習です)
→ 下記

>> r = 1..10
=> 1..10

 
2. 今度はRangeクラスとnewメソッドを使って、1から10の範囲オブジェクトを作ってみてください。ヒント: newメソッドに2つの引数を渡す必要があります。
→ 下記

>> r2 = Range.new(1,10)
=> 1..10

 
3. 比較演算子==を使って、上記2つの課題で作ったそれぞれのオブジェクトが同じであることを確認してみてください。
→ 下記

>> r == r2
=> true

 

【4.4.2 クラス継承 演習】

1. Rangeクラスの継承階層を調べてみてください。同様にして、HashとSymbolクラスの継承階層も調べてみてください。
→ まずはRangeから。

>> r = Range.new(1,3)
=> 1..3
>> r.class
=> Range
>> r.class.superclass
=> Object
>> r.class.superclass.superclass
=> BasicObject
>> r.class.superclass.superclass.superclass
=> nil

 次にHash

>> h = {}
=> {}
>> h.class
=> Hash
>> h.class.superclass
=> Object
>> h.class.superclass.superclass
=> BasicObject
>> h.class.superclass.superclass.superclass
=> nil

 最後にSymbol

>> s = :symbol
=> :symbol
>> s.class
=> Symbol
>> s.class.superclass
=> Object
>> s.class.superclass.superclass
=> BasicObject
>> s.class.superclass.superclass.superclass
=> nil

 
2. リスト 4.15にあるself.reverseのselfを省略し、reverseと書いてもうまく動くことを確認してみてください。
→ 下記

>> class Word < String
>>   def palindrome?
>>     self == reverse
>>   end
>> end
=> :palindrome?
>> s = Word.new("level")
=> "level"
>> s.palindrome?
=> true

 

【4.4.3 組み込みクラスの変更 演習】

1. palindrome?メソッドを使って、“racecar”が回文であり、“onomatopoeia”が回文でないことを確認してみてください。南インドの言葉「Malayalam」は回文でしょうか? ヒント: downcaseメソッドで小文字にすることを忘れないで。
→ 4.4.2の演習の流れでいきましょう。

s = Word.new("racecar")
=> "racecar"
>> s.palindrome?
=> true
>> s = Word.new("onomatopoeia")
=> "onomatopoeia"
>> s.palindrome?
=> false
>> s.downcase.palindrome?
=> true

 
2. リスト 4.16を参考に、Stringクラスにshuffleメソッドを追加してみてください。ヒント: リスト 4.12も参考になります。

3. 比較演算子==を使って、上記2つの課題で作ったそれぞれのオブジェクトが同じであることを確認してみてください。
→ まとめて下記

>>   def shuffle
>>     self.split('').shuffle.join
>>   end
>> end
=> :shuffle
>> "foobar".shuffle
=> "fobaro"
>> class String
>>   def shuffle
>>     split('').shuffle.join
>>   end
>> end
=> :shuffle
>> "foobar".shuffle
=> "boroaf"

 

【4.4.4 コントローラクラス 演習】

1. 第2章で作ったToyアプリケーションのディレクトリでRailsコンソールを開き、User.newと実行することでuserオブジェクトが生成できることを確認してみましょう。
→ toy_app消してますやん…ということで、git cloneを実行。また一つ賢くなりました。usernameとpasswordを聞かれましたが、Githubのアカウント名とログインパスワードを入力すれば実行できました。

$ git clone 該当のリモートリポジトリのhttps

 そして本題に入ろうとしたら、各種エラーが…。budle install --without productionして、rails db:migrateして解決。無事userオブジェクトを作成できました。

>> user = User.new
=> #<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil>

 
2. 生成したuserオブジェクトのクラスの継承階層を調べてみてください。
→ 下記

>> user.class
=> User(id: integer, name: string, email: string, created_at: datetime, updated_at: datetime)
>> user.class.superclass
=> ApplicationRecord(abstract)
>> user.class.superclass.superclass
=> ActiveRecord::Base
>> user.class.superclass.superclass.superclass
=> Object
>> user.class.superclass.superclass.superclass.superclass
=> BasicObject
>> user.class.superclass.superclass.superclass.superclass.superclass
=> nil

 

【4.4.5 ユーザークラス 演習】

1. Userクラスで定義されているname属性を修正して、first_name属性とlast_name属性に分割してみましょう。また、それらの属性を使って "Michael Hartl" といった文字列を返すfull_nameメソッドを定義してみてください。最後に、formatted_emailメソッドのnameの部分を、full_nameに置き換えてみましょう (元々の結果と同じになっていれば成功です)
→ 下記

example_user.rb
class User
  attr_accessor :first_name, :last_name, :email

  def initialize(attributes = {})
    @first_name = attributes[:first_name]
    @last_name = attributes[:last_name]
    @email = attributes[:email]
  end

  def full_name
    "#{@first_name} #{@last_name}"
  end

  def formatted_email
    "#{self.full_name} <#{@email}>"
  end
end

 コンソール上で、

>> require './example_user'
=> true
user = User.new(first_name: "t" ,last_name: "k", email: "tk@mail.com")
=> #<User:0x00000000030715e0 @first_name="t", @last_name="k", @email="tk@mail.com">
>> user.formatted_email=> "t k <tk@mail.com>"

 
2. "Hartl, Michael" といったフォーマット (苗字と名前がカンマ+半角スペースで区切られている文字列) で返すalphabetical_nameメソッドを定義してみましょう。
→ 下記

  def full_name
    "#{@first_name} #{@last_name}"
  end

3. full_name.splitとalphabetical_name.split(', ').reverseの結果を比較し、同じ結果になるかどうか確認してみましょう。
→ 上の演習2を記入してるのでコンソールに読み込み直し。上の入力値も短すぎるので入れ直します。(ヤットさんのaが抜けてる、、、)

>> require './example_user'=> true
>> user = User.new(first_name: "Ysuhito", last_name: "Endo", email: "ye@ye.com")
=> #<User:0x0000000003059968 @first_name="Ysuhito", @last_name="Endo", @email="ye@ye.com">
>> user.full_name.split=> ["Ysuhito", "Endo"]
>> user.alphabetical_name.split(', ').reverse
=> ["Ysuhito", "Endo"]
>> user.full_name.split == user.alphabetical_name.split(', ').reverse
=> true

 

第4章まとめ

・全体で使うのか、特定のコントローラで使うのか、それらによってヘルパーの定義ファイルも使い分ける。
・オブジェクト指向を感覚に刷り込んでいけ。すべてがオブジェクトだ。
・クラスは継承できる。必ず大元のクラスがある。継承しているから色々な機能が使える。
・クラスに定義するのがクラスメソッド、インスタンスで定義するのがインスタンスメソッド。
・( )とか{ }省略できる。引数の最後のブロックの{ }など。
・:nameはシンボル。ハッシュ記法は ~~: "mojiretu" の書き方が一般的。
・ぼちぼち分からない単語があったので、用語集にまとめています。

 
この章は作業多かったな〜。でもコードリーディングには省略記法とかの知識が大事かも。次の5章から、再びアプリ開発に戻ります!

 

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

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

・組み込み関数
 プログラミング言語などの仕様にあらかじめ用意され、標準で使用できる関数のこと。これに対し、プログラマがコード上で定義・実装した関数を「ユーザー定義関数」(user-defined function)という。

・API(Application Programming Interface)
 あるコンピュータプログラム(ソフトウェア)の機能や管理するデータなどを、外部の他のプログラムから呼び出して利用するための手順やデータ形式などを定めた規約のこと。APIの利用には、ソフトウェア開発の効率化・セキュリティの向上・最新情報を簡単に取得可能 といったメリットがある。

・エスケープ(エスケープ文字・処理)
 意味を持つ文字列をただの文字列にしたり、その逆で意味を与えたりすること、その働きを持つ文字(\など)のこと。

・リテラル (literal)
 ソースコードに書いた文字とか数字のこと。

・ネスト(nest)
 あるものの中に、それと同じ形や種類の(一回り小さい)ものが入っている状態や構造のこと。

・アクセサー(accessir)
 オブジェクト指向プログラミングで、オブジェクト内部のメンバ変数(属性、プロパティ)に外部からアクセスするために用意されたメソッド。メンバ変数をオブジェクト内部に隠蔽し、外部から直接参照させないようにするために用意される。

・メンバ変数
 インスタンス変数のこと。

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

GitHubActionsでRuby on RailsのRSpecテストを実行する

Ruby on RailsのRSpecテストをGitHubActionsで実行しようとしたら、そこそこ大変だったのでメモに残しておきます。

GitHubActionsとは

GitHub上で動作するサーバレスな実行環境です。
リポジトリ内のファイルに設定を記述することで、GitHubの各種操作にトリガして任意のアクションを実行できる仕組みです。
パブリックリポジトリであれば無料で使えます。

今回はPull Requestの作成に紐づけてRSpecを実行することで、Rails用のCIにしようと思います。

結論

Railsを実行できるDockerイメージをDockerHubにアップロードして、DockerPullしてテストを実行するのが良さそうです。

環境

  • Ubuntu 16.04 LTS
  • Ruby on Rails 6.0
  • Ruby 2.6.6

OS以外は違っても簡単に対応できます。

Dockerイメージを作ってDockerHubにアップロードする

Dockerfile

ベーシックな環境です。
必要なライブラリがあれば追記してください。

tools/ci/Dockerfile
FROM ubuntu:16.04

SHELL ["/bin/bash", "-c"]

ENV RUBY_VERSION="2.6.6"
ENV BUNDLER_VERSION="2.1.4"
ENV DEBIAN_FRONTEND="noninteractive"
ENV PATH=/root/.rbenv/bin:/root/.rbenv/shims:$PATH

WORKDIR /app

COPY Gemfile .
COPY Gemfile.lock .

RUN set -x \
  && apt update \
  && apt install -y \
    build-essential \
    curl \
    git \
    libssl-dev \
    libreadline-dev \
    libmysqlclient-dev \
    mysql-client \
    mysql-server \
    tzdata \
    zlib1g-dev \
  # Install rbenv and ruby
  && git clone https://github.com/sstephenson/rbenv.git ~/.rbenv \
  && git clone https://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build \
  && CONFIGURE_OPTS='--disable-install-rdoc' /root/.rbenv/bin/rbenv install ${RUBY_VERSION} -s \
  && /root/.rbenv/bin/rbenv global ${RUBY_VERSION} \
  # Install bundler
  && echo 'gem: --no-rdoc --no-ri' > /root/.gemrc \
  && /root/.rbenv/shims/gem install bundler -v ${BUNDLER_VERSION} \
  # bundle install
  && /root/.rbenv/shims/bundle config set without development \
  && /root/.rbenv/shims/bundle install \
  # delete unused file
  && rm -rf /var/lib/apt/lists/* \
  && rm Gemfile Gemfile.lock

RUNを複数書いてDockerのレイヤを重ねるとMySQL周りで実行時にエラーが出るので1つのコマンドで全環境を構築します。

Dockerのビルド

docker build -t utyosu/build-rails:latest . -f tools/ci/Dockerfile

Dockerイメージのアップロード

DockerHubのアカウントが必要になります。
はじめてのDockerHubリポジトリ登録あたりが参考になりそうです。

docker push utyosu/build-rails:latest

GitHubActionsのトリガを設定する

プロジェクトのリポジトリ内にGitHubAction用のファイルを作成します。

.github\workflows\build.yml
name: build

# Pull Requestをトリガにする
on: [pull_request]

jobs:
  build:
    # Ubuntuの最新版をベースにする
    runs-on: ubuntu-latest

    # DockerHubからイメージをPullする
    container:
      image: utyosu/build-rails:latest

    # このブランチをcheckoutする
    steps:
    - uses: actions/checkout@v1

    # test.shを実行する
    - name: run test
      run: . tools/ci/test.sh

テストスクリプト

テストしたいことを記述します。
このスクリプトが失敗するとビルド失敗になります。

tools/ci/test.sh
#!/bin/bash

# プロセスが何も起動してないので、まずはMySQLを立ち上げておく
service mysql start

# MySQLユーザの作成と権限の付与
mysql -e 'create user "user_name";'
mysql -e 'grant all on *.* to "user_name";'

export RAILS_ENV=test
bundle config set without development

# Gemをインストールする。
# 何もない状態から
bundle install

# Capistranoが動いているか確認する
# (バージョンの不一致で動かないときがあるので念の為チェックしている)
bundle exec cap -T

# Rubocopによる静的解析
bundle exec rubocop

# railsで定義されているデータベースを作成する
bundle exec rails db:create

# ridgepoleによるデータベースのスキーマ作成
# ridgepoleを使っていない人は代わりに bundle exec rails db:schema:load を実行する
bundle exec ridgepole -c config/database.yml --apply -f db/schema -E test

# RSpecの実行
bundle exec rspec

実際にやってみる

PullRequestを作成すると Some checks haven't completed yet と表示されます。

1.png

DetailsをクリックするとCIの進捗状況を確認できます。

3.png

少し待っているとコンソール出力が表示されます。

4.png

しばらくすると完了します。

5.png

PullRequestに戻ると All checks have passed と表示されて、問題なくテストが通ったことが分かります。

6.png

経緯

GitHub標準のRuby実行環境が用意されているので、最初はそれをベースにして試していました。
しかし、RailsやGemのインストールで30分以上かかってしまうので、軽めのプロジェクトにおけるCIには向いてないと思いました。
DockerHubからイメージをPullして実行する方法であれば、40秒くらいでテストの実行が開始できることが分かったのでこの方法を用いています。

メモ

別コンテナにMySQLとか用意してコンテナ同士でやり取りする方法もあるようですが仕組みが難しすぎて実現できませんでした。
Rails環境で上手く実現できたらいいなーって思ってます。

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

Rails(ActiveSupport)の*_since、*_agoはダサい!

結論

  • *_since*_ago は使うな
  • 代わりに since/ago/after/before +/- を適切に使おう

はじめに

Rails便利ですね。
個人的にRailsの中ではActiveRecordが最高のツールですが、ActiveSupportも変態拡張が多くて大好きです。
current_user.authorized_at <= 1.day.ago # 1.day.before(Time.now) とか気持ちいいですね。

あるPR

user.expires_on = Date.today.months_since(6)

これを見たときにすっと思考に入ってきますか?
僕はこれが未来を指すのか過去を指すのか即座に判断できません。

英語の例文を考えてみましょう。

「ユーザの権限は6ヶ月後に期限切れとなる」
The user's permission will expire on 6 months after.

どこにもsinceは現れません。

sinceを使った例文「ユーザの権限は1ヶ月前から切れている」
The user's permission has expired since 1 month ago.

なので私は、PRで months_since を見ると

user.expires_on = 6.months.after
# 他にもこんな書き方ができるよ!
# user.expires_on = Date.today + 6.months
# user.expires_on = 6.months.after(Date.today) # 丁寧な英語っぽく
# user.expires_on = 6.months.from_now.to_date

という指摘をすることがあります。

何にこだわっているのか

コードを書くときには読み手に負担が少ないことが大事です。
Railsガイドにも *_since(ago) の記載があり、Rails開発者の中ではメジャーな存在となっていると思いますが、読み心地が極端に悪いと僕は考えています。
すべてのコードが英語としてリーダブルであることを求める必要はありませんが、別の書き方で読みやすくなるならそちらで記述した方がいいでしょう。

sinceのもたらす語感

英語のsinceは、起点を示してそこから何かが継続しているイメージを想起させます。

image.png
引用元: https://www.english-speaking.jp/difference-between-from-and-since/

しかしmonths_since(6)と出てくるとその感覚がバカになるのです。1

念のために原点を確認してみた

古いCHANGELOGを見ると、すでにその姿を見つけられました。1.0.0のころの記述ですね。

* Added Time::Calculations to ask for things like Time.now.tomorrow, Time.now.yesterday, Time.now.months_ago(4) #580 [DP|Flurin]. Examples:

    "Later today"         => now.in(3.hours),
    "Tomorrow morning"    => now.tomorrow.change(:hour => 9),
    "Tomorrow afternoon"  => now.tomorrow.change(:hour => 14),
    "In a couple of days" => now.tomorrow.tomorrow.change(:hour => 9),
    "Next monday"         => now.next_week.change(:hour => 9),
    "In a month"          => now.next_month.change(:hour => 9),
    "In 6 months"         => now.months_since(6).change(:hour => 9),
    "In a year"           => now.in(1.year).change(:hour => 9)

正直なところ、sinceの使い方に関して6.months.since(user.created_at)とかの例が出てくるかなと思ってましたが拍子抜けです。
非ネイティブだからこその無駄なこだわりかもしれません。

since/agoについて

プレフィクスを伴わないsince/agoは、加算(減算)のシンタックスシュガーです。

どこかに起点を置いてそこからの経過を取得するという場合には有用なメソッドです。
ただ、やっぱりTime拡張のsince/agoは使いどころが見つかりません。

Duration#since/agoは使い勝手がいいので使い方を間違わずに使っていきたいと考えます。
また、それぞれ after/from_now/ before/until というエイリアスを持っているので、文脈に合わせてメソッドを選びたいですね。

結局*_sinceを使うか?

先述のように1.0当初から*_sinceがあり現在の使い方を想定されていたとはいえ、私はやはり可読性を損なう記述に関しては書き換えを推奨していきます。

6.months.after # 6ヶ月後
1.month.after(Time.new(2020,1,31)) # 2020/1/31の1ヶ月後: ちゃんと2020/2/29が作れる
Time.new(2020,1,31) + 1.month # 2020/1/31に1ヶ月足す: ちゃんと2020/2/29が作れる
2.weeks.since(user.created_at) # ユーザの作成日から2週間: 無料期間とか作るときこんな形だとその間というのがわかりやすい

などが結果を損なわず、文意もわかりやすいものだと考えます。

逆に推奨しない書き方

Time.now.months_since(6) # 本稿の主題。months_sinceは語感からずれる
Time.now.months_ago(6) # sinceがagoに変わっただけで本質は同じ。
Time.now.since(6.month) # 同様にsinceにNumericを持ってきて加算するのは気持ち悪い。
Time.now.ago(6.month) # これもagoの語感と語順が気持ち悪い
Time.now.since(user.created_at) # 機能的にはこんなことができるけどもはや意味不明

  1. 念のために社内の英語ネイティブスピーカー(非エンジニア)にも確認してみたところ、同様の感覚で答えてくれました。 

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

ローカル環境からherokuにデプロイしたRailsアプリを更新したいとき

やりたいこと

ローカル環境からherokuにデプロイしたRailsアプリの内容を更新する。

具体的には、変更したデータベースを反映したい。

(忘備録に残しておきます)

更新方法

ターミナルにて、更新したいアプリがあるディレクトリに移動し、
$git add -A
$git commit -m "Update application"
$git push heroku master

からの、

$heroku run rails db:migrate

これでいけました。

git push heroku master時にエラーが発生する場合

gemfileをいじったとき、最後にbundle installをし忘れている可能性があります。

ということで、再びターミナルにて

$budnle install

$git add -A
$git commit -m "Update application"
$git push heroku master

$heroku run rails db:migrate

これでherokuにデプロイしていたアプリの内容を更新できました!

参考にした記事

以下の記事が参考になりました。

https://qiita.com/kazukimatsumoto/items/a0daa7281a3948701c39

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

[rails]binding.pryで処理内容を確認する

やりたいこと

@itemに入っている中身valueを知りたい

items#show
  def show
    @item = Item.find(params[:id])
  end
#show.html.erbの記述
<%= link_to '購入画面に進む',item_orders_path(@item.id)%>

処理内容
"購入画面に進む"ボタンを押すとorders#index(/items/:item_id/orders(.:format))に画面遷移する。
購入ボタンを押すと@itemの内容を参照するようになるがどのような情報を抜き出しているのか見たい

処理内容の確認方法

1.gem 'pry-rails'をGemfileに記述しインストールする
2.items#showにbinding.pryと記述する

  def show
    @item = Item.find(params[:id])
    binding.pry
  end

binding.pryによってshowアクションが実行された時処理を止めている

3.実際にブラウザを開きshow.hrml.erbのページを開くshowアクションを実行する。

4.処理が止まるのでコンソールのサーバーを見る

app/controllers/items_controller.rb:41 ItemsController#show:
    40: def show
 => 41:   binding.pry
    42: end

[1] pry(#<ItemsController>)>

showアクションで処理が止まっていることがわかる

5[1] pry(#)>に@itemと記述する

[1] pry(#<ItemsController>)> @item
=> #<Item:0x00007f9ac9e01300
 id: 8,
 item_name: "ラーメン",
 info: "いい味出てる!",
 category_id: 3,
 status_id: 3,
 shipping_id: 3,
 area_id: 5,
 schedule_id: 4,
 price: 1000,
 user_id: 1,
 created_at: Fri, 04 Sep 2020 01:29:20 UTC +00:00,
 updated_at: Fri, 04 Sep 2020 01:29:20 UTC +00:00>

これで@itemに中身valueがわかった!

補足

[1] pry(#)>に@item.idとうつと

[2] pry(#<ItemsController>)> @item.id
=> 8

@item.id の内容が”8”ということがわかった

まとめ

<%= link_to '購入画面に進む',item_orders_path(@item.id)%>
item_orders_pathに画面遷移したかったらitem_idが指定できないと遷移できない。

item_orders  /items/:item_id/orders(.:format)  orders#index
<%= link_to '購入画面に進む',item_orders_path(@item.id)%>

(@item.id)と指定することでの
:item_idに必要な情報を抜き出すことができた!

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

VSCodeでファイル保存時に rubocopのauto-correct

掲題の通り。設定だけならそれほど大したことはなかったが、一点謎の不具合があってそこそこハマった。

プロジェクトにrubocopを導入。

この辺は公式のReadme等読みつつ雑に実施。

gemfileにrubocopを入れて、.rubocop.ymlを用意するだけ。
参考: https://github.com/rubocop-hq/rubocop

元ファイルはRailsのRubocopをベースに作成。
rubocop-railsやrubocop-performanceを使っている様子なので、こちらもGemfile等に入れておく。
参考: https://github.com/rails/rails/blob/master/.rubocop.yml

rubocopの対象外にしたいファイルは除外する。
参考: https://qiita.com/necojackarc/items/8bc16092bbc69f17a16d#%E5%AF%BE%E8%B1%A1%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB

あとはbundle exec rubocopでrubocopが想定通り動作すればOK

VSCode 上でrubocop の auto-correct

Prettierみたいなことがしたいだけだったが苦労した。なぜか。。。

基本設定

基本的には以下をVSCodeのsetting.jsonに入れるだけでおおよそ実現する

  "ruby.lint": {
    "rubocop": true
  },
  "[ruby]": {
    "editor.formatOnSave": false
  },
  "ruby.format": "rubocop"

参考: https://qiita.com/yumikokh/items/98be01df144c41d60e1e#formatting

上の参考サイトでは "editor.formatOnSaveTimeout": 5000が必要とされていたが、私の環境では必要なかった。

ハマり箇所

ただ、私の環境だとこの設定をした際、日本語が何故かユニコードに変換されてしまうという現象が起きた。コマンドラインからrubocop auto-correctを実施した際は全く問題無いが、VSCodeの自動整形でのみ発生する。

例えば、
context '未ログインの場合' do

context "\u672A\u30ED\u30B0\u30A4\u30F3\u306E\u5834\u5408" do
こうなる。

ファイルはutf8になっており、文字コード周りの設定は問題ないとは思うのだが、、、
この辺りの話が関連しているのだろうか?わからん。
https://techracho.bpsinc.jp/hachi8833/2016_10_13/26969

またこちらも理由は全くわからないが .rubocop.ymlの設定を以下のように変えたら上手くいった。

Style/StringLiterals:
  Enabled: true
  EnforcedStyle: double_quotes

Style/StringLiterals:
  Enabled: true
  EnforcedStyle: single_quotes

doubleをsingleに変えただけ。謎だ。。。

formatterとしてrubocop以外を利用した場合も日本語周りで問題が起きる。
おそらく何かしら問題があるだろうと思う。

実際に私もやってみた結果、同じ結果に遭遇し、同じ対応で乗り越えることができた。
が、encoding: utf8 と書くのがなんとなく引っかかり、上記の対応を取ることにした。

最後に

double_quotesが強制の環境だとキツイことになるな...どうしようか?
またもっと良い解決策をご存知の方がいましたら教えてください m(_ _)m
(しかしそもそもこれ再現性あるのだろうか。。。?)

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

ECSでデプロイしたEC2のコンテナインスタンスが落ち続けたら

前提

  • ECSとECRを使用してデプロイ
  • nginxとpumaをソケット通信
  • ELBのルールを修正してHTTPSへリダイレクト
  • ヘルスチェックの結果は、health checks failed with these codes: [301]
  • URLを開くとnginxがBad gatewayやらService temporary unavailableとなる。

原因

ELBのヘルスチェックにリダイレクトを適応させていないことが原因でした。

解決方法

  1. [LOAD BALANCING] で [ターゲットグループ] を選択する。

  2. ターゲットグループを選択。

  3. [ヘルスチェック] タブで、[編集] を選択。

  4. [ターゲットグループの編集] ページで、必要に応じて設定を変更して終わり。

まとめ

HTTPからHTTPSへリダイレクトする設定をした場合、ヘルスチェックもHTTPS、ポート443へ変更しなければヘルスチェックは失敗してしまうようです。

ECSは設定が多くて扱うのが難しいですね。次の課題はCloudWatchでログを読み取れるようにすることです。

記事を読んでいただきありがとうございました。

参考にした記事

ヘルスチェックが失敗しましたが、トラブルシューティングと解決方法を教えてください。
https://aws.amazon.com/jp/premiumsupport/knowledge-center/troubleshoot-classic-health-checks/

Health checks for your target groups
https://docs.aws.amazon.com/ja_jp/elasticloadbalancing/latest/application/target-group-health-checks.html

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

google vision apiをhocalhostでリファラーの制限を設定する

google vision api を開発環境で実行すると↓のエラーになります。
```ruby

'"message"=>Requests from referer are blocked.' '"status"=>"PERMISSION_DENIED",
```

APIのリファラーの制限をしている
開発環境でリファラーの制限しても動作させる方法

header にRefererを設定するとhocalhostでも動作する

https://qiita.com/kasajei/items/78f934b943ebdff7ef08
headers = {
'Content-Type': 'application/json',
'Referer': 'your domain here'
}

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

bundle execが必要な理由

なぜbundle execが必要なのか

$bundle exec rails g controller user

のようにすると、Gemfile.lockに記載されているものがrequireされる。

簡単に言うと、bundle exec をつけないで開発を行い、アップデートを行うという場合、動かなくなることがある。だから、bundle execを付けて開発を行うのが安牌なのかなという理解で、とりあえずは良いのかなと思いました。

参照
https://qiita.com/dawn_628/items/1821d4eef22b9f45eea8
この記事とかとても詳しく書かれていました。

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

Rails Tutorial 第13章 パーシャルのパスはカレントでも省略するとエラーになる

リスト13.43は演習で変更していたようで、テキストと違った

new.html.erbには修正すべきerror_messagesの行がありませんでした。

views/users/new.html.erb
    <%= render 'form' %>

formに移してあったので、修正しました。

views/users/_form.html.erb
 <%= form_for(@user, url: yield(:url_path) ) do |f|%>
    <%#= render 'shared/error_messages', object: @user %>
    <%= render 'shared/error_messages', object: f.object %>

    <%= f.label :name %>
    <%= f.text_field :name, class: 'form-control' %>   

おそらく過去に演習で変更していたと思われます。ネットで同じ方がいたため。

パーシャルのパスに、カレントを想定して省略するとエラーになった

パーシャルが見つからないとエラーになりました。
同じフォルダにあるので、フォルダ名を省略してファイル名を書いていました。

ActionView::Template::Error (Missing partial microposts/_userandmicropost, application/_userandmicropost with {:locale=>[:en], :formats=>[:html], :variants=>[], :handlers=>[:raw, :erb, :html, :builder, :ruby, :coffee, :jbuilder]}. Searched in:
  * "/home/ubuntu/environment/sample_app/app/views"
):
    1: 
    2: <% if logged_in? %>
    3:     <%= render 'userandmicropost' %>

パーシャルのパスに、static_pages/xxxと省略せずに書いたところ直りました。
1回目の呼び出しではエラーにならず、Postボタンを押したときにエラーになります。
理由は分かりませんが、ネットの他の方もstatic_pages/xxxxと書いていました。

views/static_pages/home.html.erb
<% if logged_in? %>
    <%= render 'static_pages/userandmicropost' %>

演習13.3.3.2でエラー、再実行したら正常終了

1回目はfalseでエラーです。

>> Micropost.where("user_id =  ?", user.id) == user.microposts
  Micropost Load (1.6ms)  SELECT "microposts".* FROM "microposts" WHERE "microposts"."user_id" = ? ORDER BY "microposts"."created_at" DESC  [["user_id", 1]]
  Micropost Load (0.2ms)  SELECT "microposts".* FROM "microposts" WHERE (user_id =  1) ORDER BY "microposts"."created_at" DESC
=> true
>> Micropost.where("user_id =  ?", user.id) == user.feed
=> false

consoleをいったん終了してやり直してみると、2回目はtrueで正常終了に変わりました。原因不明です。

>> Micropost.where("user_id = ?", user.id) == user.feed
=> true
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む