【Rails】パンくずリストを作るbreadcrumbs_on_railsを使って構造化データ対応する
Ruby on Railsで「パンくずリスト」を簡単に実現してくれるGembreadcrumbs_on_rails
を使って「構造化データ」を実現する方法を紹介します。
構造化データとは!?
構造化データとはGoogle公式の情報の記載を引用します。
Google 検索では、ページのコンテンツを理解するよう取り組んでいます。ページに構造化データを含めて、ページの内容についての明白な判断材料を提供すると、Google でそのページをより正確に理解できるようになります。構造化データとは、ページに関する情報を提供し、ページ コンテンツ(たとえばレシピのページでは、材料、加熱時間と加熱温度、カロリーなど)を分類するための標準化されたデータ形式です。
そのままですが、ページ情報を正しくGoogleに伝えるための仕組みです。
すごく多くの種類があるのですが、例えば以下のようなものです。
- 記事
- 書籍
- パンくずリスト
- カルーセル
- イベント
- ライブ配信
今回は、この中の「パンくずリスト」を表示させるためにどうすれば良いか!?をRails使って実現したいと思います。
各種類のイメージや使い方はこちらから参照してください。 https://developers.google.com/search/docs/guides/search-gallery?hl=ja
breadcrumbs_on_railsの使い方
Gem breadcrumbs_on_rails
の使い方は以下の記事でまとめてありますので、こちらを参照してください。
実装方法
パンくずリストの構造化データを実現するには以下のような記載が必要です。
<ol itemscope itemtype="https://schema.org/BreadcrumbList"> <li itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem"> <!-- Method 1 (preferred) --> <a itemprop="item" href="https://example.com/books"> <span itemprop="name">Books</span></a> <meta itemprop="position" content="1" /> </li> <ol>
見慣れないプロパティがありますが、これが構造化データですよ!と伝える為の記述になります。
詳細な内容は、こちらのGoogle公式のページに記載されているのでチェックして見てください。
https://developers.google.com/search/docs/data-types/breadcrumb?hl=ja
今回は、これをrails
x gem breadcrumbs_on_rails
を使って実現していきます。
Viewの記述
# haml .breadcrumbs{itemscope: '', itemtype: 'http://schema.org/BreadcrumbList'} %ul - @breadcrumbs.each.with_index(1) do |breadcrumb, i| %li{itemprop: 'itemListElement', itemscope: '', itemtype: 'http://schema.org/ListItem'} - if breadcrumb.path.present? %a{href: breadcrumb.path, itemprop: 'item'} %span{:itemprop => 'name'}= breadcrumb.name %meta{:itemprop => 'position', content: i} - else %span{:itemprop => 'name'}= breadcrumb.name %meta{:itemprop => 'position', content: i}
↓
# html <div class="breadcrumbs" itemscope="" itemtype="http://schema.org/BreadcrumbList"> <ul> <li itemprop="itemListElement" itemscope="" itemtype="http://schema.org/ListItem"> <a href="root_url" itemprop="item"> <span itemprop="name">HOME</span> </a> <meta content="1" itemprop="position"> </li> <li itemprop="itemListElement" itemscope="" itemtype="http://schema.org/ListItem"> <span itemprop="name">登録ユーザーの一覧</span> <meta content="2" itemprop="position"> </li> </ul> </div>
構造化データが正しく実装できているかは、構造化データ テストツールから確認できます!
失敗しているとエラーでお知らせしてくれます。
【Rails】パンくずリストを簡単に作る Gem breadcrumbs_on_rails
Ruby on Railsで「パンくずリスト」を生成するGembreadcrumbs_on_rails
の紹介です。
導入方法
Gemfileにbreadcrumbs_on_rails
を追加してbundle install
します。
# Gemfile gem 'breadcrumbs_on_rails'
パンくずリストを登録する(Controller)
パンくずリストを表示させたいContollerのアクション内でパンくずリストを登録します。
# new_users_controller.rb def index @users = NewUser.all add_breadcrumb 'HOME', :root_url add_breadcrumb '登録ユーザーの一覧' end
使い方はadd_breadcrumb 表示する文字列, パス
です。第二引数は省略すると文字列だけ表示されます。
また、階層順になるように設定してください。上記の場合はHOME > 登録ユーザーの一覧
の並びになります。
上記の例ではアクションの中で定義していますが、アプリ全体で定義したい場合はApplicationController
で。コントローラー全体で定義したい場合はアクションの外に定義できます。なので、今回の場合は以下のように記述した方が良いかもしれないです。
class ApplicationController < ActionController::Base add_breadcrumb 'HOME', :root_url end class NewUsersController < ApplicationController add_breadcrumb 'ユーザー一覧', :new_users_path def index end def show add_breadcrumb @user.name end end
こうすることで、アクション毎に記述する必要が無くなります。この例だと、HOME > ユーザー一覧 > opiyo
みたいな感じで表示されます。
パンくずリストを表示する(View)
パンくずリストの表示方法も簡単で、以下のように記述するだけです。
.breadcrumbs %ul <li> = render_breadcrumbs separator: '</li><li>' </li>
ただ、画像のように何も設定しないと縦並びになってしまうのでcssで見た目を整えます。
.breadcrumbs %ul <li> = render_breadcrumbs separator: '</li><li>' </li> :sass .breadcrumbs text-align: center padding: 10px 0 ul li display: inline-block .breadcrumbs ul > li + li:before content: "\f105" font-family: FontAwesome padding: 0 20px display: inline-block
【Rails】Active Recordで作ったオブジェクトを検索しよう!(find/where)
Ruby on Railsでデータベースを操作するのに使うActive Record。
今回は、find
やwhere
を使って実現するオブジェクトの検索方法を紹介します。
今回利用するテーブル情報はこちらです。
基本的な使い方
検索するメソッドはいっぱいあるのですが、よく使う項目は以下のようなのがあります。
- find
- find_by
- find_by!
- first
- last
- where
1件だけ取得したり、条件に応じて複数データ取得できたりします。
これらの違いを一つずつ実行しながら見ていきたいと思います。
条件に合致したデータを1件取得する
これらのメソッドは条件に合致したデータを1件取得するメソッドです。
- first
- last
- find
- find_by
- find_by!
- take
idが一番最初のデータを取得する(first)
> NewUser.first NewUser Load (0.4ms) SELECT "new_users".* FROM "new_users" ORDER BY "new_users"."id" ASC LIMIT $1 [["LIMIT", 1]] => #<NewUser:0x00007f8f237f3758 id: 1, name: "opiyo", age: nil, gender: nil, birthday: nil, email: nil, created_at: Mon, 07 Oct 2019 21:09:45 JST +09:00, updated_at: Mon, 07 Oct 2019 21:09:45 JST +09:00, token: nil>
idが一番最後のデータを取得する(last)
> NewUser.last NewUser Load (0.4ms) SELECT "new_users".* FROM "new_users" ORDER BY "new_users"."id" DESC LIMIT $1 [["LIMIT", 1]] => #<NewUser:0x00007f8f08033420 id: 10, name: "opiyo", age: nil, gender: nil, birthday: nil, email: nil, created_at: Thu, 10 Oct 2019 19:26:05 JST +09:00, updated_at: Thu, 10 Oct 2019 19:26:05 JST +09:00, token: "PXFWyWaE5KfYUtjMxE8DNSDF">
指定したidのデータを取得する(find)
> NewUser.find 1 NewUser Load (0.5ms) SELECT "new_users".* FROM "new_users" WHERE "new_users"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]] => #<NewUser:0x00007f8f2478bd28 id: 1, name: "opiyo", age: nil, gender: nil, birthday: nil, email: nil, created_at: Mon, 07 Oct 2019 21:09:45 JST +09:00, updated_at: Mon, 07 Oct 2019 21:09:45 JST +09:00, token: nil>
条件に合致したデータを取得する(find_by)
# 仮に複数件マッチした場合でも1件のみ取得します > NewUser.find_by(name: 'opiyo') NewUser Load (0.4ms) SELECT "new_users".* FROM "new_users" WHERE "new_users"."name" = $1 LIMIT $2 [["name", "opiyo"], ["LIMIT", 1]] => #<NewUser:0x00007f8f09483498 id: 1, name: "opiyo", age: nil, gender: nil, birthday: nil, email: nil, created_at: Mon, 07 Oct 2019 21:09:45 JST +09:00, updated_at: Mon, 07 Oct 2019 21:09:45 JST +09:00, token: nil> > NewUser.find_by(name: 'opiyoopiyoopiyo') NewUser Load (0.4ms) SELECT "new_users".* FROM "new_users" WHERE "new_users"."name" = $1 LIMIT $2 [["name", "opiyoopiyoopiyo"], ["LIMIT", 1]] => nil
find_byと同じだが取得できない場合は例外が発生(find_by!)
> NewUser.find_by!(name: 'opiyoopiyoopiyo') NewUser Load (0.5ms) SELECT "new_users".* FROM "new_users" WHERE "new_users"."name" = $1 LIMIT $2 [["name", "opiyoopiyoopiyo"], ["LIMIT", 1]] ActiveRecord::RecordNotFound: Couldn't find NewUser from /Users/tnakano/rails/iemiru/vendor/bundle/ruby/2.5.0/gems/activerecord-5.2.3/lib/active_record/core.rb:217:in `find_by!'
条件に合致したデータを複数取得する(where)
where
を使うと条件に応じたデータを複数件取得することができます。
> NewUser.where(name: 'opiyo') NewUser Load (0.5ms) SELECT "new_users".* FROM "new_users" WHERE "new_users"."name" = $1 [["name", "opiyo"]] => [#<NewUser:0x00007f8f26d165e8 id: 1, name: "opiyo", age: nil, gender: nil, birthday: nil, email: nil, created_at: Mon, 07 Oct 2019 21:09:45 JST +09:00, updated_at: Mon, 07 Oct 2019 21:09:45 JST +09:00, token: nil>, #<NewUser:0x00007f8f26d16458 id: 4, name: "opiyo", age: nil, gender: nil, birthday: nil, email: nil, created_at: Mon, 07 Oct 2019 22:03:40 JST +09:00, updated_at: Mon, 07 Oct 2019 22:03:40 JST +09:00, token: nil>, #<NewUser:0x00007f8f26d162c8 id: 7, name: "opiyo", age: 2, gender: nil, birthday: nil, email: nil, created_at: Mon, 07 Oct 2019 22:29:20 JST +09:00, updated_at: Mon, 07 Oct 2019 22:32:12 JST +09:00, token: nil> > NewUser.where(name: 'opiyo', age: 2) NewUser Load (2.0ms) SELECT "new_users".* FROM "new_users" WHERE "new_users"."name" = $1 AND "new_users"."age" = $2 [["name", "opiyo"], ["age", 2]] => [#<NewUser:0x00007f8f093894e8 id: 7, name: "opiyo", age: 2, gender: nil, birthday: nil, email: nil, created_at: Mon, 07 Oct 2019 22:29:20 JST +09:00, updated_at: Mon, 07 Oct 2019 22:32:12 JST +09:00, token: nil>]
また、条件関係なく全データ取得したい場合は、all
を使います。
> NewUser.all NewUser Load (0.5ms) SELECT "new_users".* FROM "new_users" => [#<NewUser:0x00007f8f26f57fa0 id: 1, name: "opiyo", age: nil, gender: nil, birthday: nil, email: nil, created_at: Mon, 07 Oct 2019 21:09:45 JST +09:00, updated_at: Mon, 07 Oct 2019 21:09:45 JST +09:00, token: nil>, #<NewUser:0x00007f8f26f57e10 id: 2, name: nil, age: nil, gender: nil, birthday: nil, email: nil, created_at: Mon, 07 Oct 2019 21:41:35 JST +09:00, updated_at: Mon, 07 Oct 2019 21:41:35 JST +09:00, token: nil>, #<NewUser:0x00007f8f26f57c80 id: 3, name: nil, age: nil, gender: nil, birthday: nil, email: nil, created_at: Mon, 07 Oct 2019 21:42:31 JST +09:00, updated_at: Mon, 07 Oct 2019 21:42:31 JST +09:00, token: nil>, #<NewUser:0x00007f8f26f57af0 id: 4, name: "opiyo", age: nil, gender: nil, birthday: nil, email: nil, created_at: Mon, 07 Oct 2019 22:03:40 JST +09:00, updated_at: Mon, 07 Oct 2019 22:03:40 JST +09:00, token: nil>, #<NewUser:0x00007f8f26f57960 id: 5, name: nil, age: 1, gender: nil, birthday: nil, email: nil, created_at: Mon, 07 Oct 2019 22:22:18 JST +09:00, updated_at: Mon, 07 Oct 2019 22:22:18 JST +09:00, token: nil>, #<NewUser:0x00007f8f26f577d0 id: 6, name: nil, age: 2, gender: nil, birthday: nil, email: nil, created_at: Mon, 07 Oct 2019 22:23:25 JST +09:00, updated_at: Mon, 07 Oct 2019 22:23:25 JST +09:00, token: nil>, #<NewUser:0x00007f8f26f57640 id: 7, name: "opiyo", age: 2, gender: nil, birthday: nil, email: nil, created_at: Mon, 07 Oct 2019 22:29:20 JST +09:00, updated_at: Mon, 07 Oct 2019 22:32:12 JST +09:00, token: nil>, #<NewUser:0x00007f8f26f574b0 id: 8, name: "not_token", age: nil, gender: nil, birthday: nil, email: nil, created_at: Thu, 10 Oct 2019 19:25:07 JST +09:00, updated_at: Thu, 10 Oct 2019 19:25:07 JST +09:00, token: nil>]
whereで取得した以外のデータを取得する(where.not)
> NewUser.where.not(name: 'opiyo') NewUser Load (0.4ms) SELECT "new_users".* FROM "new_users" WHERE "new_users"."name" != $1 [["name", "opiyo"]] => [#<NewUser:0x00007f8f08858268 id: 8, name: "not_token", age: nil, gender: nil, birthday: nil, email: nil, created_at: Thu, 10 Oct 2019 19:25:07 JST +09:00, updated_at: Thu, 10 Oct 2019 19:25:07 JST +09:00, token: nil>]
グルーピング(group)
# 人毎の年齢合計 > NewUser.group(:name).sum(:age) (2.5ms) SELECT SUM("new_users"."age") AS sum_age, "new_users"."name" AS new_users_name FROM "new_users" GROUP BY "new_users"."name" => {nil=>3, "opiyo"=>2, "not_token"=>0}
テーブルの結合(joins)
> users = NewUser.select('new_users.name, posts.title').joins(:posts).where("posts.title = '1'") NewUser Load (0.6ms) SELECT new_users.name, posts.title FROM "new_users" INNER JOIN "posts" ON "posts"."new_user_id" = "new_users"."id" WHERE (posts.title = '1') => [#<NewUser:0x00007f8f09b63e20 id: nil, name: "opiyo">] > users.each do |user| * puts user.name * puts user.title * end opiyo 1
件数を取得する(count)
> NewUser.where(name: 'opiyo').count (0.5ms) SELECT COUNT(*) FROM "new_users" WHERE "new_users"."name" = $1 [["name", "opiyo"]] => 3
重複を削除(distinct)
> NewUser.select(:name).distinct NewUser Load (0.4ms) SELECT DISTINCT "new_users"."name" FROM "new_users" => [#<NewUser:0x00007f8f1e892ee8 id: nil, name: nil>, #<NewUser:0x00007f8f1e88a108 id: nil, name: "opiyo">, #<NewUser:0x00007f8f1e870c08 id: nil, name: "not_token">]
並び順の指定(order)
# 昇順 > NewUser.where(name: 'opiyo').order(:id) NewUser Load (0.5ms) SELECT "new_users".* FROM "new_users" WHERE "new_users"."name" = $1 ORDER BY "new_users"."id" ASC [["name", "opiyo"]] => [#<NewUser:0x00007f8f0844a6d0 id: 1, name: "opiyo", age: nil, gender: nil, birthday: nil, email: nil, created_at: Mon, 07 Oct 2019 21:09:45 JST +09:00, updated_at: Mon, 07 Oct 2019 21:09:45 JST +09:00, token: nil>, #<NewUser:0x00007f8f0844a540 id: 4, name: "opiyo", age: nil, gender: nil, birthday: nil, email: nil, created_at: Mon, 07 Oct 2019 22:03:40 JST +09:00, updated_at: Mon, 07 Oct 2019 22:03:40 JST +09:00, token: nil>, #<NewUser:0x00007f8f0844a3b0 id: 7, name: "opiyo", age: 2, gender: nil, birthday: nil, email: nil, created_at: Mon, 07 Oct 2019 22:29:20 JST +09:00, updated_at: Mon, 07 Oct 2019 22:32:12 JST +09:00, token: nil>] # 降順 > NewUser.where(name: 'opiyo').order(id: :desc) NewUser Load (0.4ms) SELECT "new_users".* FROM "new_users" WHERE "new_users"."name" = $1 ORDER BY "new_users"."id" DESC [["name", "opiyo"]] => [#<NewUser:0x00007f8f092892a0 id: 7, name: "opiyo", age: 2, gender: nil, birthday: nil, email: nil, created_at: Mon, 07 Oct 2019 22:29:20 JST +09:00, updated_at: Mon, 07 Oct 2019 22:32:12 JST +09:00, token: nil>, #<NewUser:0x00007f8f09289110 id: 4, name: "opiyo", age: nil, gender: nil, birthday: nil, email: nil, created_at: Mon, 07 Oct 2019 22:03:40 JST +09:00, updated_at: Mon, 07 Oct 2019 22:03:40 JST +09:00, token: nil>, #<NewUser:0x00007f8f09288f80 id: 1, name: "opiyo", age: nil, gender: nil, birthday: nil, email: nil, created_at: Mon, 07 Oct 2019 21:09:45 JST +09:00, updated_at: Mon, 07 Oct 2019 21:09:45 JST +09:00, token: nil>] # 複数指定する場合は文字列で囲み、カンマ区切りで複数記述します > NewUser.where(name: 'opiyo').order('id desc, created_at') NewUser Load (0.5ms) SELECT "new_users".* FROM "new_users" WHERE "new_users"."name" = $1 ORDER BY id desc, created_at [["name", "opiyo"]] => [#<NewUser:0x00007f8f09032290 id: 7, name: "opiyo", age: 2, gender: nil, birthday: nil, email: nil, created_at: Mon, 07 Oct 2019 22:29:20 JST +09:00, updated_at: Mon, 07 Oct 2019 22:32:12 JST +09:00, token: nil>, #<NewUser:0x00007f8f09032100 id: 4, name: "opiyo", age: nil, gender: nil, birthday: nil, email: nil, created_at: Mon, 07 Oct 2019 22:03:40 JST +09:00, updated_at: Mon, 07 Oct 2019 22:03:40 JST +09:00, token: nil>, #<NewUser:0x00007f8f09031f70 id: 1, name: "opiyo", age: nil, gender: nil, birthday: nil, email: nil, created_at: Mon, 07 Oct 2019 21:09:45 JST +09:00, updated_at: Mon, 07 Oct 2019 21:09:45 JST +09:00, token: nil>]
【Rails】PostgreSQLをローカル環境(Mac)にインストールする
Ruby on Railsでよく使われるPostgreSQL
をローカル(Mac)環境にインストールする方法です。
PostgreSQLのインストール
$ brew install postgresql
PostgreSQLのバージョンチェック
$ psql --version psql (PostgreSQL) 9.6.2
PostgreSQLサーバの起動
$ postgres -D /usr/local/var/postgres or $ brew services start postgresql # なんか色々な方法があるみたいです。こっちの方が直感的でわかりやすいですね。 $ brew services list # 起動状況の確認ができる。初めて実行した時だけ`homebrew/services`がインストールされた。 ==> Tapping homebrew/services Cloning into '/usr/local/Homebrew/Library/Taps/homebrew/homebrew-services'... remote: Counting objects: 10, done. remote: Compressing objects: 100% (7/7), done. remote: Total 10 (delta 0), reused 5 (delta 0), pack-reused 0 Unpacking objects: 100% (10/10), done. Tapped 0 formulae (37 files, 50.7KB) Name Status User Plist postgresql started taku /Users/taku/Library/LaunchAgents/homebrew.mxcl.postgresql.plist
※http://qiita.com/takuya0301/items/c0720753de98572703b8
PostgreSQL のデータベース一覧を表示する
$ psql -l
ロールの登録
$ psql postgres postgres=# ¥du #登録されているロール情報の表示 postgres=# create role projectname with createdb login password 'password'; # createdbができる権限の持ったロールを作成する postgres=# ¥q #終了
PostgreSQL のデータベースを登録する
$ createdb hoge
PostgreSQL のデータベースを削除する
$ dropdb hoge
その他
Railsを動かす場合は、Railsプロジェクトのconfig/database.yml
に記載されている設定に合わせてロールとDBを作る
development: adapter: postgresql encoding: unicode database: project_database username: projectname host: 127.0.0.1
【Rails】ActiveRecordじゃなくて生のSQLを実行する方法(find_by_sql)
Ruby on Railsで生のSQLを実行する方法です。
ちょっと複雑なデータを取得したい時に、Active Recordだと逆に面倒だったりして生のSQLを描きたい場合があります。
その場合はfind_by_sql
を使います。
基本的な使い方
使い方はシンプルでModel.find_by_sql(sql)
です。
> sql = 'select * from new_users;' => "select * from new_users;" > NewUser.find_by_sql(sql) NewUser Load (5.3ms) select * from new_users; => [#<NewUser:0x00007fd740f61e30 id: 1, name: "opiyo", age: nil, gender: nil, birthday: nil, email: nil, created_at: Mon, 07 Oct 2019 21:09:45 JST +09:00, updated_at: Mon, 07 Oct 2019 21:09:45 JST +09:00>, #<NewUser:0x00007fd73d2380b8 id: 2, name: nil, age: nil, gender: nil, birthday: nil, email: nil, created_at: Mon, 07 Oct 2019 21:41:35 JST +09:00, updated_at: Mon, 07 Oct 2019 21:41:35 JST +09:00>, #<NewUser:0x00007fd741ee7f08 id: 3, name: nil, age: nil, gender: nil, birthday: nil, email: nil, created_at: Mon, 07 Oct 2019 21:42:31 JST +09:00, updated_at: Mon, 07 Oct 2019 21:42:31 JST +09:00>, #<NewUser:0x00007fd741ee7d78 id: 4, name: "opiyo", age: nil, gender: nil, birthday: nil, email: nil, created_at: Mon, 07 Oct 2019 22:03:40 JST +09:00, updated_at: Mon, 07 Oct 2019 22:03:40 JST +09:00>, #<NewUser:0x00007fd741ee7be8 id: 5, name: nil, age: 1, gender: nil, birthday: nil, email: nil, created_at: Mon, 07 Oct 2019 22:22:18 JST +09:00, updated_at: Mon, 07 Oct 2019 22:22:18 JST +09:00>, #<NewUser:0x00007fd741ee7a58 id: 6, name: nil, age: 2, gender: nil, birthday: nil, email: nil, created_at: Mon, 07 Oct 2019 22:23:25 JST +09:00, updated_at: Mon, 07 Oct 2019 22:23:25 JST +09:00>, #<NewUser:0x00007fd741ee78c8 id: 7, name: "opiyo", age: 2, gender: nil, birthday: nil, email: nil, created_at: Mon, 07 Oct 2019 22:29:20 JST +09:00, updated_at: Mon, 07 Oct 2019 22:32:12 JST +09:00>]
動的なパラメーターを渡す
whereなどに動的に値を渡したい場合は、値を設定したい場所を?
にし、配列で渡します。
> sql = 'select * from new_users where name = ?;' => "select * from new_users where name = ?;" > NewUser.find_by_sql([sql, 'opiyo']) NewUser Load (0.5ms) select * from new_users where name = 'opiyo'; => [#<NewUser:0x00007fd742cf1798 id: 1, name: "opiyo", age: nil, gender: nil, birthday: nil, email: nil, created_at: Mon, 07 Oct 2019 21:09:45 JST +09:00, updated_at: Mon, 07 Oct 2019 21:09:45 JST +09:00>, #<NewUser:0x00007fd742cf1608 id: 4, name: "opiyo", age: nil, gender: nil, birthday: nil, email: nil, created_at: Mon, 07 Oct 2019 22:03:40 JST +09:00, updated_at: Mon, 07 Oct 2019 22:03:40 JST +09:00>, #<NewUser:0x00007fd742cf1478 id: 7, name: "opiyo", age: 2, gender: nil, birthday: nil, email: nil, created_at: Mon, 07 Oct 2019 22:29:20 JST +09:00, updated_at: Mon, 07 Oct 2019 22:32:12 JST +09:00>]
また、シンボル:name
を使ってハッシュ値で渡すことも可能です。
> sql = 'select * from new_users where name = :name;' => "select * from new_users where name = :name;" > NewUser.find_by_sql([sql, { name: 'opiyo' }]) NewUser Load (1.9ms) select * from new_users where name = 'opiyo'; => [#<NewUser:0x00007fd726052c88 id: 1, name: "opiyo", age: nil, gender: nil, birthday: nil, email: nil, created_at: Mon, 07 Oct 2019 21:09:45 JST +09:00, updated_at: Mon, 07 Oct 2019 21:09:45 JST +09:00>, #<NewUser:0x00007fd726052af8 id: 4, name: "opiyo", age: nil, gender: nil, birthday: nil, email: nil, created_at: Mon, 07 Oct 2019 22:03:40 JST +09:00, updated_at: Mon, 07 Oct 2019 22:03:40 JST +09:00>, #<NewUser:0x00007fd726052968 id: 7, name: "opiyo", age: 2, gender: nil, birthday: nil, email: nil, created_at: Mon, 07 Oct 2019 22:29:20 JST +09:00, updated_at: Mon, 07 Oct 2019 22:32:12 JST +09:00>]
複数ある場合も同様に?
やシンボルを使えばokです。
> name = 'opiyo' => "opiyo" > from = Date.current.beginning_of_month => Tue, 01 Oct 2019 > to = Date.current.end_of_month => Thu, 31 Oct 2019 > sql = "select * from new_users where name = :name and created_at between :from AND :to;" => "select * from new_users where name = :name and created_at between :from AND :to;" [88] pry(main)> NewUser.find_by_sql([sql, { name: name, from: from.to_s, to: to.to_s }]) NewUser Load (0.5ms) select * from new_users where name = 'opiyo' and created_at between '2019/10/01' AND '2019/10/31'; => [#<NewUser:0x00007fd741af0c88 id: 1, name: "opiyo", age: nil, gender: nil, birthday: nil, email: nil, created_at: Mon, 07 Oct 2019 21:09:45 JST +09:00, updated_at: Mon, 07 Oct 2019 21:09:45 JST +09:00>, #<NewUser:0x00007fd741af0878 id: 4, name: "opiyo", age: nil, gender: nil, birthday: nil, email: nil, created_at: Mon, 07 Oct 2019 22:03:40 JST +09:00, updated_at: Mon, 07 Oct 2019 22:03:40 JST +09:00>, #<NewUser:0x00007fd741af05a8 id: 7, name: "opiyo", age: 2, gender: nil, birthday: nil, email: nil, created_at: Mon, 07 Oct 2019 22:29:20 JST +09:00, updated_at: Mon, 07 Oct 2019 22:32:12 JST +09:00>]
テーブルを結合するような場合も普通に出来ます。
> sql => "select new_users.name, posts.title from new_users join posts on new_users.id = posts.id" > new_user_posts = NewUser.find_by_sql(sql) NewUser Load (0.5ms) select new_users.name, posts.title from new_users join posts on new_users.id = posts.id => [#<NewUser:0x00007fd7421a2bf8 id: nil, name: "opiyo">, #<NewUser:0x00007fd7421a29a0 id: nil, name: nil>, #<NewUser:0x00007fd7421a2658 id: nil, name: nil>, #<NewUser:0x00007fd7421a22e8 id: nil, name: "opiyo">, #<NewUser:0x00007fd7421a20e0 id: nil, name: nil>] > new_user_posts.first => #<NewUser:0x00007fd7421a2bf8 id: nil, name: "opiyo"> > new_user_posts.first.attributes => {"id"=>nil, "name"=>"opiyo", "title"=>"一番目"} > new_user_posts.first.name => "opiyo" > new_user_posts.first.title => "一番目"
select
指定するとパッと見取得できないような感じですが、attributes
で確認するとバッチリ取得できてます。
あとは、いつもと同じようにuser.name
とか使えるのでいかように!
【Rails保存版】Active Recordで作ったオブジェクトを保存しよう!(save/update)
Ruby on Railsでデータベースを操作するのに使うActive Record。
今回は、save
やupdate
を使って実現するオブジェクトの保存方法紹介します。
今回利用するテーブル情報はこちらです。
基本的な使い方
保存する役割を果たすメソッドは以下の通りいっぱいあります。
- save
- save!
- update
- update!
- update_attribute
- update_attributes
- update_attributes!
- update_column
- update_columns
- update_all
それぞれの違いは、「戻り値の違い」と「コールバックの有無」です。
使い所や実行結果などをそれぞれ見ていきたいと思います。
save
- よく使う処理: コントローラーのcreate
- 戻り値: true/false
基本的に利用するのは、create
アクションです。戻り値がtrue/falseになるので保存が成功したか失敗したかで処理を分けるように記述します。
# new_users_controller.rb def create @user = NewUser.new(new_user_params) if @user.save redirect_to :new_users_path, notice: 'ユーザーを登録しました。' else render :new end end
# 成功する場合 > user.name = 'opiyo' => "opiyo" > user.save (0.3ms) BEGIN NewUser Create (0.9ms) INSERT INTO "new_users" ("name", "created_at", "updated_at") VALUES ($1, $2, $3) RETURNING "id" [["name", "opiyo"], ["created_at", "2019-10-07 12:09:45.517006"], ["updated_at", "2019-10-07 12:09:45.517006"]] (0.6ms) COMMIT => true # 失敗する場合 > user.save (0.3ms) BEGIN (4.8ms) ROLLBACK => false
save!
- よく使う処理: モデル、rakeタスク
- 戻り値: 例外(ActiveRecord::RecordInvalid)
基本的に利用するのは、モデルやrakeタスクなどで利用します。合わせて例外処理を書く場面が多いと思います。
self.transaction do self.name = 'opiyo' self.save! end begin user.name = 'opiyo' user.save! rescue => e puts e end
update
- よく使う処理: コントローラーのupdate
- 戻り値: true/false
基本的に利用するのは、updateアクションです。
def update if @user.update new_user_params redirect_to new_users_path(@magazine), notice: 'ユーザーを保存しました' else render 'edit' end end
> user.update(name: 'opiyo') (0.3ms) BEGIN NewUser Create (5.8ms) INSERT INTO "new_users" ("name", "created_at", "updated_at") VALUES ($1, $2, $3) RETURNING "id" [["name", "opiyo"], ["created_at", "2019-10-07 13:03:40.364928"], ["updated_at", "2019-10-07 13:03:40.364928"]] (1.3ms) COMMIT => true
update!
- よく使う処理: モデル
- 戻り値: 例外(ActiveRecord::RecordInvalid)
基本的に利用するのは、モデルやrakeタスクなどで利用します。合わせて例外処理を書く場面が多いと思います。
self.transaction do self.save!(name: 'opiyo') end begin self.save!(name: 'opiyo') rescue => e puts e end
update_attribute
- よく使う処理: モデルやrakeタスク
- 戻り値: true/false
基本的に利用するのは、モデルやrakeタスクです。
update
との違いは、保存できるのは1つのカラムでvalidationを無視します。
# validationの設定 class NewUser < ApplicationRecord validates :name, presence: true end # 実行結果 > user.update(age: 1) (0.3ms) BEGIN (0.3ms) ROLLBACK => false > user.update_attribute(:age, 1) (0.3ms) BEGIN NewUser Create (0.4ms) INSERT INTO "new_users" ("age", "created_at", "updated_at") VALUES ($1, $2, $3) RETURNING "id" [["age", 1], ["created_at", "2019-10-07 13:29:20.417777"], ["updated_at", "2019-10-07 13:29:20.417777"]] (2.0ms) COMMIT => true
update_attributes
- よく使う処理: モデルやrakeタスク
- 戻り値: true/false
基本的に利用するのは、モデルやrakeタスクです。
update_attribute
との違いは、複数保存ができ、validationは実行されます。なので処理の内容としてはupdate
と全く同じです。
> user.update_attributes(age: 2) (0.3ms) BEGIN (0.2ms) ROLLBACK => false > user.update_attributes(name: 'opiyo', age: 2) (0.3ms) BEGIN NewUser Update (0.6ms) UPDATE "new_users" SET "name" = $1, "age" = $2, "updated_at" = $3 WHERE "new_users"."id" = $4 [["name", "opiyo"], ["age", 2], ["updated_at", "2019-10-07 13:32:12.006308"], ["id", 7]] (2.9ms) COMMIT => true
update_attributes!
- よく使う処理: モデルやrakeタスク
- 戻り値: 例外(ActiveRecord::RecordInvalid)
基本的に利用するのは、モデルやrakeタスクです。
update_attributes
との違いは、例外が発生します。なので処理の内容としてはupdate!
と全く同じです。
update_column
- よく使う処理: ajax使ってデータ更新するときやモデルやrakeタスク
- 戻り値: 例外(ActiveRecord::RecordInvalid)
基本的に利用するのは、モデルやrakeタスクです。
update
やupdate_attribute
との違いは、「validationを無視」「コールバック無視」「update_at無視」です。つまり保存するだけ。
update_columns
- よく使う処理: ajax使ってデータ更新するときやモデルやrakeタスク
- 戻り値: 例外(ActiveRecord::RecordInvalid)
基本的に利用するのは、モデルやrakeタスクです。
update_attributes
と同じで複数カラムを同時に更新するときに使います。内容はupdate_column
と同じで「validationを無視」「コールバック無視」「update_at無視」です。
まとめ
- create: save!
- update: update
- モデルやrakeタスク: save/update_attributes
- ただ保存したい: update_columns
- 例外: !
各メソッドの実行結果や、どうゆう時に何を使うかまとめてきました。
改めて見直すと、各メソッドの微妙な違いを意識して処理できてたか曖昧な部分もあるので見直したいなーと思いました。
【Rails】Active Recordで作ったオブジェクトを削除しよう!(destroy/delete)
Ruby on Railsでデータベースを操作するのに使うActive Record。
今回は、destroy
やdelete
を使って実現するオブジェクトの削除方法紹介します。
今回利用するテーブル情報はこちらです。
基本的なこと
オブジェクトを削除するメソッドはdestroy
とdelete
です。
# deleteはDELETE文が実行される > user.posts.last => #<Post:0x00007fb4d78bb800 id: 7, user_id: nil, title: "5", body: nil, created_at: Thu, 03 Oct 2019 22:47:03 JST +09:00, updated_at: Thu, 03 Oct 2019 22:47:03 JST +09:00, new_user_id: 1> > user.posts.last.delete Post Destroy (2.2ms) DELETE FROM "posts" WHERE "posts"."id" = $1 [["id", 7]] => #<Post:0x00007fb4d78bb800 id: 7, user_id: nil, title: "5", body: nil, created_at: Thu, 03 Oct 2019 22:47:03 JST +09:00, updated_at: Thu, 03 Oct 2019 22:47:03 JST +09:00, new_user_id: 1> # ActiveRecordのdestroyが実行される > user.posts.last => #<Post:0x00007fb4d78bb800 id: 7, user_id: nil, title: "5", body: nil, created_at: Thu, 03 Oct 2019 22:47:03 JST +09:00, updated_at: Thu, 03 Oct 2019 22:47:03 JST +09:00, new_user_id: 1> > user.posts.last.destroy (0.3ms) BEGIN (0.2ms) COMMIT => #<Post:0x00007fb4d78bb800 id: 7, user_id: nil, title: "5", body: nil, created_at: Thu, 03 Oct 2019 22:47:03 JST +09:00, updated_at: Thu, 03 Oct 2019 22:47:03 JST +09:00, new_user_id: 1>
destroy
destroy
を実行した場合は関連データを考慮してくれます。
NewUserオブジェクトを削除した時に、紐づくPostオブジェクトも同時に削除してくれます。
# new_user.rb class NewUser < ApplicationRecord has_many :posts, dependent: :destroy end
これを実現しているのがdependent
という設定になるのですが、大きく2種類あります。
destory
: 関連データを削除nullify
: 関連データを削除せずに、nullに更新する
dependent: :destroy
実行結果を見るとわかりますが、先ず始めにPostオブジェクトにDELETE文が走ります。その後にNewUserオブジェクトにDELETE文が実行され関連するデータが全て削除されました。
> Post.all Post Load (0.4ms) SELECT "posts".* FROM "posts" => [#<Post:0x00007fed68ebe4e0 id: 14, user_id: nil, title: "1", body: nil, created_at: Thu, 03 Oct 2019 23:20:16 JST +09:00, updated_at: Thu, 03 Oct 2019 23:20:16 JST +09:00, new_user_id: 4>, #<Post:0x00007fed68ebe350 id: 15, user_id: nil, title: "2", body: nil, created_at: Thu, 03 Oct 2019 23:20:17 JST +09:00, updated_at: Thu, 03 Oct 2019 23:20:17 JST +09:00, new_user_id: 4>, #<Post:0x00007fed68ebe1c0 id: 16, user_id: nil, title: "3", body: nil, created_at: Thu, 03 Oct 2019 23:20:21 JST +09:00, updated_at: Thu, 03 Oct 2019 23:20:21 JST +09:00, new_user_id: 4>] > NewUser.last.destroy NewUser Load (0.5ms) SELECT "new_users".* FROM "new_users" ORDER BY "new_users"."id" DESC LIMIT $1 [["LIMIT", 1]] (0.2ms) BEGIN Post Load (0.3ms) SELECT "posts".* FROM "posts" WHERE "posts"."new_user_id" = $1 [["new_user_id", 4]] Post Destroy (0.3ms) DELETE FROM "posts" WHERE "posts"."id" = $1 [["id", 14]] Post Destroy (0.2ms) DELETE FROM "posts" WHERE "posts"."id" = $1 [["id", 15]] Post Destroy (0.3ms) DELETE FROM "posts" WHERE "posts"."id" = $1 [["id", 16]] NewUser Destroy (0.4ms) DELETE FROM "new_users" WHERE "new_users"."id" = $1 [["id", 4]] (0.5ms) COMMIT => #<NewUser:0x00007fed4b098ab8 id: 4, name: "opiyo", age: nil, gender: nil, birthday: nil, email: nil, created_at: Thu, 03 Oct 2019 23:20:02 JST +09:00, updated_at: Thu, 03 Oct 2019 23:20:02 JST +09:00> > Post.all Post Load (0.5ms) SELECT "posts".* FROM "posts" => []
dependent: :nullfy
実行結果を見るとわかりますが、Postオブジェクトは削除されずにnew_user_id
がnullにupdateされています。
そのため、Postデータを明示的に残しておきたい場合はdependent: :nullify
を設定します。
> Post.all Post Load (0.7ms) SELECT "posts".* FROM "posts" => [#<Post:0x00007fbf14ebfb10 id: 8, user_id: nil, title: "1", body: nil, created_at: Thu, 03 Oct 2019 23:14:38 JST +09:00, updated_at: Thu, 03 Oct 2019 23:14:38 JST +09:00, new_user_id: 2>, #<Post:0x00007fbf14ebf958 id: 9, user_id: nil, title: "2", body: nil, created_at: Thu, 03 Oct 2019 23:14:40 JST +09:00, updated_at: Thu, 03 Oct 2019 23:14:40 JST +09:00, new_user_id: 2>, #<Post:0x00007fbf14ebf7c8 id: 10, user_id: nil, title: "3", body: nil, created_at: Thu, 03 Oct 2019 23:14:42 JST +09:00, updated_at: Thu, 03 Oct 2019 23:14:42 JST +09:00, new_user_id: 2>] > NewUser.last.destroy NewUser Load (0.4ms) SELECT "new_users".* FROM "new_users" ORDER BY "new_users"."id" DESC LIMIT $1 [["LIMIT", 1]] (0.2ms) BEGIN Post Update All (0.4ms) UPDATE "posts" SET "new_user_id" = NULL WHERE "posts"."new_user_id" = $1 [["new_user_id", 2]] NewUser Destroy (0.4ms) DELETE FROM "new_users" WHERE "new_users"."id" = $1 [["id", 2]] (1.9ms) COMMIT => #<NewUser:0x00007fbef78b27f8 id: 2, name: "opiyo", age: nil, gender: nil, birthday: nil, email: nil, created_at: Thu, 03 Oct 2019 23:14:35 JST +09:00, updated_at: Thu, 03 Oct 2019 23:14:35 JST +09:00> > Post.all Post Load (0.4ms) SELECT "posts".* FROM "posts" => [#<Post:0x00007fbef7927b70 id: 8, user_id: nil, title: "1", body: nil, created_at: Thu, 03 Oct 2019 23:14:38 JST +09:00, updated_at: Thu, 03 Oct 2019 23:14:38 JST +09:00, new_user_id: nil>, #<Post:0x00007fbef79279e0 id: 9, user_id: nil, title: "2", body: nil, created_at: Thu, 03 Oct 2019 23:14:40 JST +09:00, updated_at: Thu, 03 Oct 2019 23:14:40 JST +09:00, new_user_id: nil>, #<Post:0x00007fbef7927850 id: 10, user_id: nil, title: "3", body: nil, created_at: Thu, 03 Oct 2019 23:14:42 JST +09:00, updated_at: Thu, 03 Oct 2019 23:14:42 JST +09:00, new_user_id: nil>] [12] pry(main)> NewUser.all NewUser Load (0.4ms) SELECT "new_users".* FROM "new_users" => []
delete
delete
はイメージ的にSQLのDELETE
文を実行するような感じで対象のデータを1件削除します。そのためdestroy
よりも処理は速いです。
が、アプリケーションの処理の中ではほとんど使われないのではないでしょうか?
関連データがあった場合にdelete
しちゃうと整合性が取れず壊れちゃうし、関連データがない場合でもdestroy
で削除することは可能なので。
全件削除する(destroy_all/delete_all)
1件だけじゃなくて全てのデータを削除したい場合はdestroy_all
とdelete_all
があります。
# destroy_allの実行例 > NewUser.all NewUser Load (0.5ms) SELECT "new_users".* FROM "new_users" => [#<NewUser:0x00007fed66dc46b8 id: 5, name: "opiyo", age: nil, gender: nil, birthday: nil, email: nil, created_at: Thu, 03 Oct 2019 23:25:58 JST +09:00, updated_at: Thu, 03 Oct 2019 23:25:58 JST +09:00>, #<NewUser:0x00007fed66dc4528 id: 6, name: "fuuzo", age: nil, gender: nil, birthday: nil, email: nil, created_at: Thu, 03 Oct 2019 23:26:26 JST +09:00, updated_at: Thu, 03 Oct 2019 23:26:26 JST +09:00>] > Post.all Post Load (0.4ms) SELECT "posts".* FROM "posts" => [#<Post:0x00007fed68bac798 id: 17, user_id: nil, title: "1", body: nil, created_at: Thu, 03 Oct 2019 23:26:03 JST +09:00, updated_at: Thu, 03 Oct 2019 23:26:03 JST +09:00, new_user_id: 5>, #<Post:0x00007fed68bac608 id: 18, user_id: nil, title: "2", body: nil, created_at: Thu, 03 Oct 2019 23:26:04 JST +09:00, updated_at: Thu, 03 Oct 2019 23:26:04 JST +09:00, new_user_id: 5>, #<Post:0x00007fed68bac478 id: 19, user_id: nil, title: "3", body: nil, created_at: Thu, 03 Oct 2019 23:26:10 JST +09:00, updated_at: Thu, 03 Oct 2019 23:26:10 JST +09:00, new_user_id: 5>, #<Post:0x00007fed68bac2e8 id: 20, user_id: nil, title: "11", body: nil, created_at: Thu, 03 Oct 2019 23:26:32 JST +09:00, updated_at: Thu, 03 Oct 2019 23:26:32 JST +09:00, new_user_id: 6>, #<Post:0x00007fed68bac158 id: 21, user_id: nil, title: "12", body: nil, created_at: Thu, 03 Oct 2019 23:26:34 JST +09:00, updated_at: Thu, 03 Oct 2019 23:26:34 JST +09:00, new_user_id: 6>, #<Post:0x00007fed68ba7f68 id: 22, user_id: nil, title: "13", body: nil, created_at: Thu, 03 Oct 2019 23:26:35 JST +09:00, updated_at: Thu, 03 Oct 2019 23:26:35 JST +09:00, new_user_id: 6>] > NewUser.destroy_all NewUser Load (0.4ms) SELECT "new_users".* FROM "new_users" (0.2ms) BEGIN Post Load (0.3ms) SELECT "posts".* FROM "posts" WHERE "posts"."new_user_id" = $1 [["new_user_id", 5]] Post Destroy (0.3ms) DELETE FROM "posts" WHERE "posts"."id" = $1 [["id", 17]] Post Destroy (0.3ms) DELETE FROM "posts" WHERE "posts"."id" = $1 [["id", 18]] Post Destroy (0.3ms) DELETE FROM "posts" WHERE "posts"."id" = $1 [["id", 19]] NewUser Destroy (0.4ms) DELETE FROM "new_users" WHERE "new_users"."id" = $1 [["id", 5]] (1.7ms) COMMIT (0.2ms) BEGIN Post Load (0.3ms) SELECT "posts".* FROM "posts" WHERE "posts"."new_user_id" = $1 [["new_user_id", 6]] Post Destroy (0.3ms) DELETE FROM "posts" WHERE "posts"."id" = $1 [["id", 20]] Post Destroy (0.3ms) DELETE FROM "posts" WHERE "posts"."id" = $1 [["id", 21]] Post Destroy (0.3ms) DELETE FROM "posts" WHERE "posts"."id" = $1 [["id", 22]] NewUser Destroy (0.4ms) DELETE FROM "new_users" WHERE "new_users"."id" = $1 [["id", 6]] (0.4ms) COMMIT => [#<NewUser:0x00007fed66d75cc0 id: 5, name: "opiyo", age: nil, gender: nil, birthday: nil, email: nil, created_at: Thu, 03 Oct 2019 23:25:58 JST +09:00, updated_at: Thu, 03 Oct 2019 23:25:58 JST +09:00>, #<NewUser:0x00007fed66d75b30 id: 6, name: "fuuzo", age: nil, gender: nil, birthday: nil, email: nil, created_at: Thu, 03 Oct 2019 23:26:26 JST +09:00, updated_at: Thu, 03 Oct 2019 23:26:26 JST +09:00>] > NewUser.all NewUser Load (0.4ms) SELECT "new_users".* FROM "new_users" => [] > Post.all Post Load (0.4ms) SELECT "posts".* FROM "posts" => []
実践的に使う
destroy / destroy!
save
とsave!
と同じように!
を付けると例外を発生させることが可能です。
基本的には全てdestroy!
で問題ないと思います。削除できなかったってことは何か問題が発生したってことなので例外を発生させて気付けるようにしたいので。
条件に応じて削除しないようにする
とはいえ、条件に応じて処理は続行させたい場合もあると思います。
今回の例の場合だと、1件でもPostデータがあればUserデータは削除させないようにするにはbefore_destroy
を使う。
# post.rb class Post < ApplicationRecord belongs_to :new_user before_destroy :do_not_destroy_posted private def do_not_destroy_posted throw :abort end end
> user = NewUser.last NewUser Load (0.7ms) SELECT "new_users".* FROM "new_users" ORDER BY "new_users"."id" DESC LIMIT $1 [["LIMIT", 1]] => #<NewUser:0x00007fab7fe19150 id: 7, name: "opiyo", age: nil, gender: nil, birthday: nil, email: nil, created_at: Thu, 03 Oct 2019 23:43:42 JST +09:00, updated_at: Thu, 03 Oct 2019 23:43:42 JST +09:00> > user.destroy (0.3ms) BEGIN Post Load (0.8ms) SELECT "posts".* FROM "posts" WHERE "posts"."new_user_id" = $1 [["new_user_id", 7]] (0.8ms) ROLLBACK => false
まとめ
削除処理はデータをぶっ壊す可能性がありますので、ちゃんと考えて実装する必要があると思います。
しっかりと関連データまで意識した処理を書いていきたいですね。
- 基本的には
destroy!
dependent
で関連データを扱いを定義する- 条件がある場合は
before_destroy
を使ってバリデーションをかける - なーんも関係なく削除したい場合は
delete
の方が早い
【Rails】Active Recordでオブジェクトを作ろう!(new/build)
Ruby on Railsでデータベースを操作するのに使うActive Record
。
今回は、new
やbuild
を使って実現するオブジェクトの作り方を紹介します。
基本的なこと
new
やbuild
は基本的に出来ることは一緒です。
厳密には違うのかもしれませんが、「new build 違い」でググってもあまりヒットしませんでした。(知っている方、ぜひ教えて下さい!)
ですが、使う場面が違ったりするのでそちらを以下で紹介できればと思います。
今回利用するテーブルはこちらです。
# users Table "public.users" Column | Type | Collation | Nullable | Default ------------------------+-----------------------------+-----------+----------+----------------------------------- id | integer | | not null | nextval('users_id_seq'::regclass) email | character varying | | not null | ''::character varying name | character varying | | | tel | character varying | | | postal_code | character varying | | | address | character varying | | | nickname | character varying | | | gender | character varying | | | birthday | date | | |
# posts Table "public.posts" Column | Type | Collation | Nullable | Default ------------+-----------------------------+-----------+----------+----------------------------------- id | bigint | | not null | nextval('posts_id_seq'::regclass) user_id | bigint | | | title | character varying | | | body | text | | | created_at | timestamp without time zone | | not null | updated_at | timestamp without time zone | | not null | Indexes: "posts_pkey" PRIMARY KEY, btree (id) "index_posts_on_user_id" btree (user_id) Foreign-key constraints: "fk_rails_5b5ddfd518" FOREIGN KEY (user_id) REFERENCES users(id)
# user_condition Table "public.user_conditions" Column | Type | Collation | Nullable | Default ---------------+-----------------------------+-----------+----------+--------------------------------------------- id | bigint | | not null | nextval('user_conditions_id_seq'::regclass) user_id | bigint | | | sign_in_count | integer | | not null | 0 created_at | timestamp without time zone | | not null | updated_at | timestamp without time zone | | not null | Indexes: "user_conditions_pkey" PRIMARY KEY, btree (id) "index_user_conditions_on_user_id" btree (user_id) Foreign-key constraints: "fk_rails_47c01ca983" FOREIGN KEY (user_id) REFERENCES users(id)
newを使う時
new
を使う時は、そのモデル単体で作成したい場合に使います。
Model.new
だけ実行すると空っぽの状態で、引数にカラムと設定したい値を付けると値が反映された状態で作成されます。
# 引数なし > user = User.new => #<User id: nil, email: "", created_at: nil, updated_at: nil, name: nil, phonetic: nil, tel: nil, postal_code: nil, address: nil, nickname: nil, gender: nil, birthday: nil> # 引数あり > User.new(name: 'opiyo', email: 'kosmo.waizu0804@gmail.com') => #<User id: nil, email: "kosmo.waizu0804@gmail.com", created_at: nil, updated_at: nil, name: "opiyo", .....>
実際の処理で使う場面としては、new
やcreate
アクションで使うと思います。
class UsersController < ApplicationController def new @user = User.new end def create @user = User.new user_params if @user.save redirect_to users_path else render :new end end private def user_params params.require(:user).permit(:name, :postal_code, :address, :email, :tel) end end
buildを使う時
build
を使う時は、1 : Nの関係がある時にN側のモデルを作成したい場合に使います。
# user.rb(1) class User < ApplicationRecord has_many :posts, dependent: :destroy end
# post.rb(N) class Post < ApplicationRecord belongs_to :user end
new
と同じで引数がないと空っぽの状態で、引数があるとその値が反映された状態で作成されます。
# 引数なし > user = User.last => #<User id: 1, email: "kosmo.waizu0804@gmail.com", created_at: "2019-04-04 09:26:02", updated_at: "2019-04-16 12:08:08", name: "opiyo" > user.posts.build => #<Post:0x00007faeee38dfe8 id: nil, user_id: 200, title: nil, body: nil, created_at: nil, updated_at: nil> # newでも同じことが出来ます > user.posts.new => #<Post:0x00007faeef94e350 id: nil, user_id: 200, title: nil, body: nil, created_at: nil, updated_at: nil>
実際の処理で使う場面としては、new
やcreate
アクションで使うのは同じですがログインが前提の場合や親になるモデルが取得できているような場面で利用します。
class ApplicationController < ActionController::Base def current_user @current_user = User.find_by(id: session[:user_id]) end end class PostsController < ApplicationController def new @post = @current_user.posts.build end def create @post = @current_user.posts.build post_params if @post.save redirect_to users_posts_path else render :new end end private def post_params params.require(:post).permit(:title, :body) end end
Post.new
してpost.user_id = @current_user.id
みたいに書くことも可能ですが、上記のように書くことで最初からuser_id
が設定された状態でオブジェクトが作られますのでスマートに書けます。
new/build以外の作り方
1 : 1の関係でオブジェクトを作りたい時はbuild_テーブル名
というメソッドを使います。
# user.rb(1) class User < ApplicationRecord has_one :user_condition, dependent: :destroy end
# user_condition.rb(1) class UserCondition < ApplicationRecord belongs_to :user end
> user = User.last => #<User id: 200, email: "kosmo.waizu0804@gmail.com", created_at: "2019-04-04 09:26:02", updated_at: "2019-04-16 12:08:08", name: "opiyo"...> > user.user_condition UserCondition Load (0.5ms) SELECT "user_conditions".* FROM "user_conditions" WHERE "user_conditions"."user_id" = $1 LIMIT $2 [["user_id", 200], ["LIMIT", 1]] => nil # newだとエラー > user.user_condition.new NoMethodError: undefined method `new' for nil:NilClass from (pry):6:in `<main>' # buildだとエラー > user.user_condition.build NoMethodError: undefined method `build' for nil:NilClass from (pry):7:in `<main>' > user.build_user_condition => #<UserCondition:0x00007feedc2c26d0 id: nil, user_id: 200, sign_in_count: 0, created_at: nil, updated_at: nil>
まとめ
最後に改めてざっとまとめてみると、こんな感じでしょうか。
- 単体でオブジェクトを作る時は
Model.new
- 1 : Nの関係の時は
parent.childs.build
- 1 : 1の関係の時は
model.build_テーブル名
では。
【Rails】オブジェクトを作成したり検索したり更新したり保存したり
Ruby on Railsではデータベースとの様々なやりとりをActice Record
という仕組みを使って行いますが、よく使われる方法をざっとご紹介です。
今回利用するテーブル情報はこちらです。
Table "public.hoges" Column | Type | Collation | Nullable | Default ------------+-----------------------------+-----------+----------+----------------------------------- id | bigint | | not null | nextval('hoges_id_seq'::regclass) name | character varying | | | age | integer | | | gender | character varying | | | birthday | date | | | email | character varying | | | created_at | timestamp without time zone | | not null | updated_at | timestamp without time zone | | not null | Indexes: "hoges_pkey" PRIMARY KEY, btree (id)
オブジェクトを作成する
オブジェクトを作成するときは、new
とbuild
を使います。
> Hoge.new => #<Hoge:0x00007fe9b5bbce10 id: nil, name: nil, age: nil, gender: nil, birthday: nil, email: nil, created_at: nil, updated_at: nil>
# hogeに紐づく記事データを作成する(1 : Nの関係) def new hoge = Hoge.find params[:id] @hoge = hoge.articles.build(strong_parameter) end # hogeに紐づく家族構成データを作成する(1 : 1の関係) def new hoge = Hoge.find params[:id] @hoge = hoge.build_family_structure(strong_parameter) end
new
は単体のモデルを生成する時、build
はテーブルの関係が1 : N(has_many)
の場面で使います。
ログインが前提になるようなサービスだとnew
を使う場面は、あんまり無いと思います。
build_モデル名
はテーブルの関係が1 : 1(has_one)
の場面で利用します。
【20191002追記】
より詳細な情報・書き方まとめてみました!
オブジェクトを検索する
オブジェクトを検索する = SQLで言うwhere
文を実行する処理です。
- find
- find_by
- find_by!
- first
- last
- where
# idが1のものを1件取得 > Hoge.find 1 Hoge Load (0.5ms) SELECT "hoges".* FROM "hoges" WHERE "hoges"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]] => #<Hoge:0x00007fe9b645f4a0 id: 1, name: "opiyo", age: 30, gender: nil, birthday: Fri, 04 Aug 1989, email: nil, created_at: Mon, 30 Sep 2019 20:47:43 JST +09:00, updated_at: Mon, 30 Sep 2019 20:47:43 JST +09:00> # nameが'opiyo'のものを1件取得 > Hoge.find_by(name: 'opiyo') Hoge Load (0.4ms) SELECT "hoges".* FROM "hoges" WHERE "hoges"."name" = $1 LIMIT $2 [["name", "opiyo"], ["LIMIT", 1]] => #<Hoge:0x00007fe9d4d21e30 id: 1, name: "opiyo", age: 30, gender: nil, birthday: Fri, 04 Aug 1989, email: nil, created_at: Mon, 30 Sep 2019 20:47:43 JST +09:00, updated_at: Mon, 30 Sep 2019 20:47:43 JST +09:00> # 一番最初に登録されたものを取得 > Hoge.first Hoge Load (0.3ms) SELECT "hoges".* FROM "hoges" ORDER BY "hoges"."id" ASC LIMIT $1 [["LIMIT", 1]] => #<Hoge:0x00007fe9b6242e60 id: 1, name: "opiyo", age: 30, gender: nil, birthday: Fri, 04 Aug 1989, email: nil, created_at: Mon, 30 Sep 2019 20:47:43 JST +09:00, updated_at: Mon, 30 Sep 2019 20:47:43 JST +09:00> # 一番最後に登録されたものを取得 > Hoge.last Hoge Load (0.4ms) SELECT "hoges".* FROM "hoges" ORDER BY "hoges"."id" DESC LIMIT $1 [["LIMIT", 1]] => #<Hoge:0x00007fe9b61b97c8 id: 2, name: "tako", age: 20, gender: nil, birthday: Sat, 23 Oct 1999, email: nil, created_at: Tue, 01 Oct 2019 21:50:26 JST +09:00, updated_at: Tue, 01 Oct 2019 21:50:26 JST +09:00> # 条件に一致するものを複数件取得 > Hoge.where(gender: nil) Hoge Load (0.4ms) SELECT "hoges".* FROM "hoges" WHERE "hoges"."gender" IS NULL => [#<Hoge:0x00007fe9b59b6ff8 id: 1, name: "opiyo", age: 30, gender: nil, birthday: Fri, 04 Aug 1989, email: nil, created_at: Mon, 30 Sep 2019 20:47:43 JST +09:00, updated_at: Mon, 30 Sep 2019 20:47:43 JST +09:00>, #<Hoge:0x00007fe9b59b6e40 id: 2, name: "tako", age: 20, gender: nil, birthday: Sat, 23 Oct 1999, email: nil, created_at: Tue, 01 Oct 2019 21:50:26 JST +09:00, updated_at: Tue, 01 Oct 2019 21:50:26 JST +09:00>] # データが見つからなかった時 > Hoge.find_by!(name: 'opi') Hoge Load (0.4ms) SELECT "hoges".* FROM "hoges" WHERE "hoges"."name" = $1 LIMIT $2 [["name", "opi"], ["LIMIT", 1]] ActiveRecord::RecordNotFound: Couldn't find Hoge
少しややこしいのが、find_by
とwhere
です。find_by
は必ず1件のデータのみ返却しますので1件を保証される場合はこちらを使います。
また、find_by!
にすることで1件も見つからない場合は例外を発生させることもあります。
join
やgroup
、order
なども出来ますので、こちらはまた別の記事でまとめてみたいと思います。
【20191007追記】
より詳細な情報・書き方まとめてみました!
オブジェクトの保存・更新
オブジェクトの保存・更新は種類がいっぱいあります。
- save
- save!
- update
- update!
- update_attribute
- update_attributes
- update_attributes!
- update_column
- update_columns
- update_all
全てデータの保存や更新する処理になりますが、違いのポイントとしては以下があります。
- 戻り値の違い「true/false」 or 「例外」
- コールバックアクション(before_action/after_action)の有無
- バリデーションの有無
!
がついた場合は例外を返します。
update_column
はコールバックやバリデーションを無視しますので、ajaxやrakeタスクなど非同期で処理している時に使うことが多いと思います。が、基本的にはupdate_attributes
を使います。
【20191007追記】
より詳細な情報・書き方まとめてみました!
オブジェクトの削除
削除する処理はdestroy
とdelete
の二つになります。
- destroy
- delete
- destroy_all
- delete_all
大きな違いは、destroy
の場合は関連するデータも削除しますがdelete
の場合はそのデータのみを削除します。
【20191003追記】
より詳細な情報・書き方まとめてみました!
合わせて一本!
探して作って、探して保存してを実現する便利なメソッドもあるのでそれもご紹介です!
- find_or_created_by!
- find_or_initialize_by
# findして、あればデータを無ければnew > Hoge.find_or_initialize_by(name: 'opiyo') Hoge Load (0.5ms) SELECT "hoges".* FROM "hoges" WHERE "hoges"."name" = $1 LIMIT $2 [["name", "opiyo"], ["LIMIT", 1]] => #<Hoge:0x00007fe9d4d57418 id: 1, name: "opiyo", age: 30, gender: nil, birthday: Fri, 04 Aug 1989, email: nil, created_at: Mon, 30 Sep 2019 20:47:43 JST +09:00, updated_at: Mon, 30 Sep 2019 20:47:43 JST +09:00> > Hoge.find_or_initialize_by(name: 'opiy') Hoge Load (0.5ms) SELECT "hoges".* FROM "hoges" WHERE "hoges"."name" = $1 LIMIT $2 [["name", "opiy"], ["LIMIT", 1]] => #<Hoge:0x00007fe9d4c692b8 id: nil, name: "opiy", age: nil, gender: nil, birthday: nil, email: nil, created_at: nil, updated_at: nil> # findして、あればデータを無ければcreate > Hoge.find_or_create_by!(name: 'opiyo', age: 30) Hoge Load (0.3ms) SELECT "hoges".* FROM "hoges" WHERE "hoges"."name" = $1 AND "hoges"."age" = $2 LIMIT $3 [["name", "opiyo"], ["age", 30], ["LIMIT", 1]] => #<Hoge:0x00007fe9b6190788 id: 1, name: "opiyo", age: 30, gender: nil, birthday: Fri, 04 Aug 1989, email: nil, created_at: Mon, 30 Sep 2019 20:47:43 JST +09:00, updated_at: Mon, 30 Sep 2019 20:47:43 JST +09:00> > Hoge.find_or_create_by!(name: 'opiy', age: 40) Hoge Load (0.4ms) SELECT "hoges".* FROM "hoges" WHERE "hoges"."name" = $1 AND "hoges"."age" = $2 LIMIT $3 [["name", "opiy"], ["age", 40], ["LIMIT", 1]] (0.2ms) BEGIN Hoge Create (0.3ms) INSERT INTO "hoges" ("name", "age", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id" [["name", "opiy"], ["age", 40], ["created_at", "2019-10-01 13:27:21.287452"], ["updated_at", "2019-10-01 13:27:21.287452"]] (4.0ms) COMMIT => #<Hoge:0x00007fe9d17bc358 id: 3, name: "opiy", age: 40, gender: nil, birthday: nil, email: nil, created_at: Tue, 01 Oct 2019 22:27:21 JST +09:00, updated_at: Tue, 01 Oct 2019 22:27:21 JST +09:00>
これ以外にも基本的に上記で紹介した処理は繋げて書くことが可能です。
> Hoge.find_by(name: 'opiyo') Hoge Load (0.5ms) SELECT "hoges".* FROM "hoges" WHERE "hoges"."name" = $1 LIMIT $2 [["name", "opiyo"], ["LIMIT", 1]] => #<Hoge:0x00007fe9d4811508 id: 1, name: "opiyo", age: 30, gender: nil, birthday: Fri, 04 Aug 1989, email: nil, created_at: Mon, 30 Sep 2019 20:47:43 JST +09:00, updated_at: Mon, 30 Sep 2019 20:47:43 JST +09:00> > Hoge.find_by(name: 'opiyo').update_attributes(gender: '男性') Hoge Load (0.4ms) SELECT "hoges".* FROM "hoges" WHERE "hoges"."name" = $1 LIMIT $2 [["name", "opiyo"], ["LIMIT", 1]] (0.2ms) BEGIN Hoge Update (0.5ms) UPDATE "hoges" SET "gender" = $1, "updated_at" = $2 WHERE "hoges"."id" = $3 [["gender", "\xE7\x94\xB7\xE6\x80\xA7"], ["updated_at", "2019-10-01 13:29:00.694154"], ["id", 1]] (0.7ms) COMMIT => true > Hoge.find_by(name: 'opiyo') Hoge Load (0.6ms) SELECT "hoges".* FROM "hoges" WHERE "hoges"."name" = $1 LIMIT $2 [["name", "opiyo"], ["LIMIT", 1]] => #<Hoge:0x00007fe9b8121070 id: 1, name: "opiyo", age: 30, gender: "男性", birthday: Fri, 04 Aug 1989, email: nil, created_at: Mon, 30 Sep 2019 20:47:43 JST +09:00, updated_at: Tue, 01 Oct 2019 22:29:00 JST +09:00>
ざっとよく使われるものを紹介してきましたが、どんな場面でどのように使うのか。こんな時はどうするのかなど一杯あると思いますのでまた別の機会で、 深掘りした内容もまとめていきたいと思います。
【Rails】モデルの変更前後の値や変更されたのかをチェックする
Ruby on Railsで保存や変更した時の値を取得したり、値が変更されたのかどうかをチェックする方法を紹介します。
変更のチェック(changed?)
モデル全体で変更があるかどうかをチェックするにはchanged?
を使います。
> hoge = Hoge.last Hoge Load (0.3ms) SELECT "hoges".* FROM "hoges" ORDER BY "hoges"."id" DESC LIMIT $1 [["LIMIT", 1]] => #<Hoge:0x00007fd646062b78 id: 1, name: "opiyo", age: 30, gender: nil, birthday: Fri, 04 Aug 1989, email: nil, created_at: Mon, 30 Sep 2019 20:47:43 JST +09:00, updated_at: Mon, 30 Sep 2019 20:47:43 JST +09:00> > hoge.name = 'opiyoopiyo' => "opiyoopiyo" > hoge.changed? => true
特定のカラムの変更をチェック(column_changed?)
特定のカラムのみが変更されたかどうかをチェックするにはchanged?の前にカラム名を付けてカラム名_changed?
にします。
> hoge = Hoge.last => #<Hoge:0x00007fd646062b78 id: 1, name: "opiyo", age: 30, gender: nil, birthday: Fri, 04 Aug 1989, email: nil, created_at: Mon, 30 Sep 2019 20:47:43 JST +09:00, updated_at: Mon, 30 Sep 2019 20:47:43 JST +09:00> > hoge.name = 'opiyoopiyo' => "opiyoopiyo" > hoge.name_changed? => true # ageは変更してないので`false`になる > hoge.age_changed? => false
変更前の値を取得する(column_was)
変更前の値についてはカラム名の後に_was
を付けます。
> hoge.name => "opiyo" > hoge.name = 'opiyoopiyo' => "opiyoopiyo" > hoge.name_was => "opiyo"
使い方と注意点
利用する場面としてはbefore_save
が多いでしょうか。
# hoge.rb class Hoge < ApplicationRecord before_save :adult?, if: Proc.new { self.birthday_changed? } def adult? self.age > 20 end end
最大の注意点としてはafter_save
などのafter
処理で利用すると正しく取得できないので、必ずbefore
処理で利用するようにしましょう!