おぴよの気まぐれ日記

おぴよの気まぐれ日記

岡山やプログラミング、ファッションのこと、子育てや人生、生き方についての備忘録。

【Rails】Active Recordでオブジェクトを作ろう!(new/build)

Ruby on Railsでデータベースを操作するのに使うActive Record

今回は、newbuildを使って実現するオブジェクトの作り方を紹介します。

基本的なこと

newbuildは基本的に出来ることは一緒です。

厳密には違うのかもしれませんが、「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", .....>

実際の処理で使う場面としては、newcreateアクションで使うと思います。

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>

実際の処理で使う場面としては、newcreateアクションで使うのは同じですがログインが前提の場合や親になるモデルが取得できているような場面で利用します。

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_テーブル名

では。