Postモデルと関連するpostsテーブルを作成できましたので、Railsのコンソール(REPL)を使ってモデル操作をしていきましょう。
REPLとは対話式の実行環境のことです。
作成した機能が正しく動作しているか確認したり、メソッドを実行した際に発行されるSQLを確認したりすることができる環境です。学習や開発時に積極的に活用しましょう。
以下コマンドを実行し、Railsコンソールを立ち上げてください。
$ rails console
以下のようにconsoleを小文字の「c」と省略することもできます。
$ rails c
以下のようにコンソールが起動すればOKです。
$ rails c
Loading development environment (Rails 7.0.4)
irb(main):001:0>
「Loading development environment (Rails 7.0.4)」 は開発環境でコンソールが実行されていることを意味します。railsのバージョンも確認できます。
irbとは「Interactive Ruby」の略で、対話形式でプログラムを実行できる環境であることを意味します。Railsコンソールのプロンプトになりますので、現実行環境がターミナルシェル(プロンプトは$)かコンソールなのかを判断することができます。
irb(main):の次の数字は実行行数です。
Railsコンソールを終了するにはexitまたはquitコマンドを使ってください。
irb(main):001:0> exit
xxxx/board $
プロンプトが「$」になりましたらRailsコンソールを抜けBashターミナルのコマンド入力待ちになっている状態です。
式が途中の場合は改行しても式の終了とはなりません。(例:Post.find(1)の場合)
irb> Post.find(
irb* 1)>
=> #<Post id: 1, content: "最初の投稿です。", created_at: "20xx-mm-dd HH:MM:SS", updated_at: "20xx-mm-dd HH:MM:SS">
クォーテーションなども同様です。
irb> Post.find_by(content: "
irb" hoge")>
Post Load (0.4ms) SELECT `posts`.* FROM `posts` WHERE `posts`.`content` = '\nhoge' LIMIT 1
=> nil
「irb”」や「irb*」表記になっていていつもと違うことに注目してください。
では早速モデルを操作していきましょう。
始めに学習するメソッドは「new」です。このメソッドはモデルのクラスメソッドとして定義済みのメソッドです。
モデルクラスにこのメソッドを使うとモデルの新規インスタンスを返します。
ではrailsコンソールを立ち上げて以前のLessonで作成したPostクラスにこのメソッドを使ってみましょう。結果を変数「newpost」に代入します。(クラスは大文字から書き始めるルールでしたね。)
$ rails c
irb> newpost = Post.new
=> #<Post:ハッシュ値 id: nil, content: nil, created_at: nil, updated_at: nil>
以下のような結果が返ってきていると思います。
=> # <Post id: nil, content: nil, created_at: nil, updated_at: nil>
こちらがPostモデルの新規インスタンスの実態です。
作成したカラムと同じ属性と属性値をハッシュ形式のようなデータで持つオブジェクトだという事がわかりますね。
どんなテーブルを作成したか忘れてしまった人はPostgreSQLクライアントを立ち上げて確認しましょう。
$ psql -d board_development
board_development-> \d posts
Table "public.posts"
Column | Type | Collation | Nullable | Default
------------+--------------------------------+-----------+----------+-----------------------------------
id | bigint | | not null | nextval('posts_id_seq'::regclass)
content | character varying | | |
created_at | timestamp(6) without time zone | | not null |
updated_at | timestamp(6) without time zone | | not null |
Indexes:
"posts_pkey" PRIMARY KEY, btree (id)
それぞれの属性にアクセスするには、そのカラム名と同じ名前のインスタンスメソッドが使えるようになっています。以下操作を行ってみてください。
irb> newpost.content= "最初の投稿です。"
=> "最初の投稿です。"
Postモデルの新規インスタンスが入った変数newpostのcontentにアクセスし、「最初の投稿です」という値をセットしました。確認してみましょう。
irb> newpost
=> #<Post id: nil, content: "最初の投稿です。", created_at: nil, updated_at: nil>
contentに「最初の投稿です。」という文字が入ったのが確認できたと思います。
newメソッドは新規インスタンスを作成するメソッドになります。
また、モデルインスタンスははデータベースのカラム名と同じ属性を持ち、カラム名と同じ名前のインスタンスメソッドでアクセスできるという事を確認しておきましょう。
次のチャプターではsaveメソッドを学習します。irbは終了せずそのまま進んでください。
次に学習するのはモデルのインスタンスメソッド「save」です。saveメソッドはモデルをデータベースに保存するためのメソッドになります。
早速newpostを保存してみましょう。
irb> newpost.save
TRANSACTION (0.2ms) BEGIN
Post Create (2.0ms) INSERT INTO "posts" ("content", "created_at", "updated_at") VALUES ($1, $2, $3) RETURNING "id" [["content", "最初の投稿です。"], ["created_at", "20yy-mm-dd xxxxxx"], ["updated_at", "20yy-mm-dd xxxxxx"]]
TRANSACTION (2.1ms) COMMIT
=> true
緑字の部分が発行されたSQLです。最後に=> trueが返ってきているのが確認できましたらsaveが成功したことになります。
saveが成功した後のnewpost変数を確認してみましょう。
irb>newpost
=> #<Post id: 1, content: "最初の投稿です。", created_at: "20xx-xx-xx xx:xx:xx", updated_at: "20xx-xx-xx xx:xx:xx">
先ほどnilだったid、created_at、updated_atに値が入っていますね。
これはsaveメソッド実行時にRailsアプリが自動的に値を入れてくれるようになっているためです。
idカラムはPrimaryKeyとして一意の(被らない)連番がint型で自動挿入されます。初期値は1です。
created_atは作成日が自動的に入ります。updated_atは更新日が自動的に入りますが、新規インスタンスを保存した時は作成日と同じ日付が入ることになります。
では本当に保存されたかPostgreSQLクライアントに接続して確認しましょう。
$ psql -d board_development
board_development-> select * from posts;
id | content | created_at | updated_at
----+------------------+----------------------------+----------------------------
1 | 最初の投稿です。 | 20xx-mm-dd HH:MM:SS | 20xx-mm-dd HH:MM:SS
(1 row)
さらにもう一つ投稿を作ってみましょう。
irb> post = Post.new(content: "二つ目の投稿です。")
irb> post.save
TRANSACTION (0.2ms) BEGIN
Post Create (0.6ms) INSERT INTO "posts" ("content", "created_at", "updated_at") VALUES ($1, $2, $3) RETURNING "id" [["content", "二つ目の投稿です。"], ["created_at", "2022-12-08 01:46:20.915489"], ["updated_at", "2022-12-08 01:46:20.915489"]]
TRANSACTION (6.4ms) COMMIT
=> true
「true」が表示されましたら成功です。PostgreSQLクライアントでもレコードが追加されているか確認してみてください。
createメソッドはsaveとよく似たメソッドになりますが、saveメソッドはインスタンスメソッドに対し、createはモデルクラスのメソッドになります。
そのため当然インスタンスには使えません。モデルクラスに対して使いましょう。
では以下のようにしてレコードを増やしましょう。
irb> post3 = Post.create(content: "hello3回目の投稿")
TRANSACTION (0.2ms) BEGIN
Post Create (0.7ms) INSERT INTO "posts" ("content", "created_at", "updated_at") VALUES ($1, $2, $3) RETURNING "id" [["content", "hello3回目の投稿"], ["created_at", "20xx-mm-dd HH:MM:SS"], ["updated_at", "20xx-mm-dd HH:MM:SS"]]
TRANSACTION (2.0ms) COMMIT
=>
#
createメソッドの戻り値は作成したレコードのインスタンスです。
irb> post3
=>
#<Post
id: 3,
content: "hello3回目の投稿", created_at: w, dd M 20xx xxxxxx UTC +00:00, updated_at: w, dd M 20xx xxxxxx UTC +00:00>
このようにcreateメソッドはモデルクラスから新規インスタンスを作成してレコードを保存するメソッドになります。saveメソッドとの違いをしっかり確認しておきましょう。
正しく保存されたかPostgreSQLクライアントでも確認しておいてください。
allはモデルクラスのメソッドです。
レコード全てに対応するインスタンスを配列で返します。この配列のことをインスタンスの集合「collection」と呼びます。
早速使ってみましょう。(結果を変数postsに代入します。)
irb> posts = Post.all
=>
Post Load (0.5ms) SELECT "posts".* FROM "posts"=>
[#>Post:0x00007f5596927500
...
postsテーブルのレコードを取得するSQLが発行され、Post:ハッシュ値というオブジェクトが生成されました。このクラスを確認してみましょう。
irb> posts = posts.class
=> Post::ActiveRecord_Relation
ActiveRecord_Relationのオブジェクトであることがわかりました。このオブジェクトはPostクラスのインスタンスを配列のような形で格納したオブジェクトです。内容を確認してみましょう。
posts
=>
[#<Post:0x00007f55971130c0
id: 1,
content: "最初の投稿です。",
created_at: Thu, 08 Dec 2022 01:41:50.649391000 UTC +00:00,
updated_at: Thu, 08 Dec 2022 01:41:50.649391000 UTC +00:00>,
#<Post:0x00007f5597112f30
id: 2,
content: "二つ目の投稿です。",
created_at: Thu, 08 Dec 2022 01:46:20.915489000 UTC +00:00,
updated_at: Thu, 08 Dec 2022 01:46:20.915489000 UTC +00:00>,
#<Post:0x00007f5597112dc8
id: 3,
content: "hello3回目の投稿",
created_at: Thu, 08 Dec 2022 01:50:22.185307000 UTC +00:00,
updated_at: Thu, 08 Dec 2022 01:50:22.185307000 UTC +00:00>,
[ <id:1のインスタンス>, <id:2のインスタンス>, <id:3のインスタンス> ]といった配列のような形になっています。配列のように扱えるので「posts[0]」のように添字でn番目の要素にアクセスしたり、eachを使って順にアクセスしたりすることも可能です。
変数に代入すると、メモリ上にデータが展開され、次からは展開されたメモリ上のデータを参照するようになります。
変数に代入せずにPost.allを複数回実行すると、毎回データベースに問い合わせをすることになりますが、データベースにアクセスするよりメモリ上のデータを参照する方がパフォーマンスに優れています。そのため変数に代入しておき後からアクセスするための準備をしているのです。
find はidで検索するメソッドです。モデルインスタンスのコレクションにもモデルクラスにも使えます。見つからなかった場合は例外(エラー)を送出します。
ではActiveRecord_Relationに使ってみましょう。
irb> posts = Post.all
Post Load (0.6ms) SELECT "posts".* FROM "posts"
=>
[#<Post:0x00007f5597284e18
...
irb> posts.find(1)
Post Load (0.3ms) SELECT "posts".* FROM "posts" WHERE "posts"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
=>
#<Post:0x00007f5598b5fbb8
id: 1,
content: "最初の投稿です。",
created_at: w, dd MM 20yy xxxxxx UTC +00:00,
updated_at: w, dd MM 20yy xxxxxx UTC +00:00>
idが1のPostモデルのインスタンスが返ってきましたね。
発行されているSQLにも注目しておきましょう。
SELECT `posts`.* FROM `posts` WHERE `posts`.`id` = 1 LIMIT 1
「LIMIT 1」となっています。つまり、最初の1件だけ返ってくるという事です。
ではクラスにも使ってみましょう。
irb> Post.find(2)
Post Load (0.3ms) SELECT "posts".* FROM "posts" WHERE "posts"."id" = $1 LIMIT $2 [["id", 2], ["LIMIT", 1]]
=>
#<Post:0x00007f5598a83ca8
id: 2,
content: "二つ目の投稿です。",
created_at: w, dd MM 20yy xxxxxx UTC +00:00,
updated_at: w, dd MM 20yy xxxxxx UTC +00:00>
idが2のインスタンスが返ってきましたね。
では存在しないidだった場合はどうでしょう。
irb> Post.find(100)
Post Load (0.4ms) SELECT "posts".* FROM "posts" WHERE "posts"."id" = $1 LIMIT $2 [["id", 100], ["LIMIT", 1]]
activerecord-7.0.4/lib/active_record/core.rb:284:in `find': Couldn't find Post with 'id'=100 (ActiveRecord::RecordNotFound)
以下のような例外が送出されています。各自翻訳するなどしてエラーの概要を掴んでおきましょう。
ActiveRecord::RecordNotFound (Couldn't find Post with 'id'=100)
「idが100のレコードが見つからなかった」ということですね。このようにエラーが出ることをぜひ覚えておいてください。口述するfind_byメソッドでは見つからなかった場合はエラーが出ません。
また、以下のようにidの引数を渡さなかった場合、nilが渡された場合も確認しておきましょう。未定義のインスタンス変数はNilクラスのオブジェクトとなり、nilが返ります。
irb> testpost = Post.find()
activerecord-7.0.4/lib/active_record/relation/finder_methods.rb:455:in `find_with_ids': Couldn't find Post without an ID (ActiveRecord::RecordNotFound)
irb> testpost
=> nil
ActiveRecord::RecordNotFound)という例外が送出されましたね。
findはよく使うことになるメソッドです。見つからなかった場合はエラーとなることを理解しておきましょう。
findに似たメソッド「find_by」を学習していきましょう。
find_byメソッドは条件検索メソッドです。条件にある最初の1件を取得してインスタンスを返します。検索結果が無い場合はnilを返します。
早速操作していきましょう。
irb> posts = Post.all Post Load (4.9ms) SELECT "posts".* FROM "posts"=> [#>Post:0x00007f559724c630...
find_byは条件指定になりますので、任意の属性(カラム)で検索することが可能です。例えばcontentが「最初の投稿です。」というものを検索してみます。文字列(string)を検索する場合はクォーテーションで括るのを忘れないようにしましょう。
irb> posts.find_by(content: "最初の投稿です。") Post Load (1.2ms) SELECT "posts".* FROM "posts" WHERE "posts"."content" = $1 LIMIT $2 [["content", "最初の投稿です。"], ["LIMIT", 1]]=> #
無事一致するものが検索できましたね。ここでぜひ覚えておいてほしいのが、find_byの検索条件は完全一致だということです。
一文字でも違ってはいけません。「最初の投稿です」と最後の読点を無くすと条件に一致する門が無くてnilが返ってくることを確認しておきましょう。
irb> posts.find_by(content: "最初の投稿です")
Post Load (0.3ms) SELECT "posts".* FROM "posts" WHERE "posts"."content" = $1 LIMIT $2 [["content", "最初の投稿です"], ["LIMIT", 1]]
=> nil
モデルクラスに使う事もできます。
irb> Post.find_by(content: "最初の投稿です。")
Post Load (0.5ms) SELECT "posts".* FROM "posts" WHERE "posts"."content" = $1 LIMIT $2 [["content", "最初の投稿です。"], ["LIMIT", 1]]
=>
#>Post:0x00007f55969ad470
id: 1,
content: "最初の投稿です。",
created_at: w, dd MM 20yy xxxxxx UTC +00:00,
updated_at: w, dd MM 20yy xxxxxx UTC +00:00>
メソッド | 概要 |
---|---|
new | 新規オブジェクトの作成 |
save | インスタンスの保存 |
create | 新規インスタンスを作成して保存 |
all | 全てのレコードのインスタンスの取得 |
find | idで検索し、最初の1件のインスタンスを返す。検索結果が無い場合は「RecordNotFound エラー」を送出 |
find_by | 条件検索し、最初の1件のインスタンスを返す。検索結果が無い場合はnilを返す。 |
where | 条件検索し、インスタンスの配列で返す。 |