大ちゃんの駆け出し技術ブログ

RUNTEQ受講生のかわいいといわれるアウトプットブログ

【Rails】技術面接対策の記事の質問を多少深ぼる記事⑧

はじめに

こんにちは!大ちゃんの駆け出し技術ブログです。

この記事は前回の記事の続きものです。

(前回の記事)

sakitadaiki.hatenablog.com

本記事ではQ43 ~ Q48を多少深掘りします。

Q43: includeとextendの違いを説明してください

回答:

includeとextendはどちらもミックスインであり、別のモジュールのコードを注入するのに使われます。 ただしincludeではそのコードにインスタンスメソッドとしてアクセスできますが、extendではそのコードにクラスメソッドとしてアクセスできる点が異なります。

今までの質問ではmoduleについて説明する機会がなかったのでまずはモジュールについて説明します。moduleは「部品の集まり」や「区分」という意味です。定義の方法は非常にクラスと似ています。

module Hoge
    def foo
    p 'foo'
  end
end

moduleもオブジェクトですので親クラスがあり、最終的にはBasicObjectのインスタンスであることがわかります。

Hoge.class
=> Module
Hoge.class.superclass
=> Object
Hoge.class.superclass.superclass
=> BasicObject

クラスとの違いですが、moduleはインスタンス化を行えません。

Hoge.new # NoMethodError (undefined method `new' for Hoge:Module)

module内で定義したメソッドは他のクラスに取り込まれることで使用することができます。そしてその方法がincludeとextendになります。

includeを使用してクラスにmoduleを取り込んだ場合、module内のメソッドはインスタンスメソッドで使用することができます。

class Animal
    include Hoge
end

animal = Animal.new
animal.foo # => 'foo'

extendで取り込んだ場合、module内のメソッドはクラスメソッドで使用することができます。

class Animal
    extend Hoge
end

Animal.foo # => 'foo'
animal = Animal.new
animal.class.foo # => 'foo' インスタンスからクラスメソッドを呼び出す

Q44: loadとrequireの違いを説明してください

回答:

load 別のファイルを読み込む(既にメモリ上に読み込まれている場合でも実行する) require 別のファイルを1度だけ実行する(何度requireしても同じ)

どちらもファイルを読み込むメソッドです。loadの公式リファレンスページで2つの違いが書かれていました。

require は同じファイルは一度だけしかロードしませんが、load は無条件にロードします。また、require は拡張子.rb や .so を自動的に補完しますが、 load は行いません。 require はライブラリのロード、load は設定ファイルの読み込みなどに使うのが典型的な用途です。

docs.ruby-lang.org

2つを例で比較するために下記2つのファイルがあるとします。fuga.rbはpメソッドのみ記述されているファイルで、hoge.rbはクラスのファイルです。

# a/fuga.rb
p 'fuga'
# a/hoge.rb
class Hoge
  def hoge
    p 'hoge'
  end
end

上記のファイルを何も記載がない下記ファイル内で読み込む処理を記述します。

# b/example.rb

load

特徴としては下記のとおりです。

- 同じファイルを何度もロードできる
- 拡張子.rb や .so を自動的に補完しない

同じファイルを何度もロードできるため、ロードするたびに処理を走らせることができます。

# b/example.rb
10.times.each do
  load './a/fuga.rb'
end

# => 'fuga'が10回出力される

また、拡張子.rb や .so を自動的に補完しないので、拡張子は必ず明示する必要があります。

load './a/fuga' # エラーになる

require

特徴としては下記のとおりです。

- 同じファイルは一度だけしかロードしない
- 拡張子.rb や .so を自動的に補完

requireもファイルを読み込むためのメソッドですが、拡張子.rb や .so を自動的に補完するため、拡張子を省略することができます。

# b/example.rb
require './b/hoge'

Hoge.new.hoge # => 'hoge'

同じファイルは一度だけしかロードしないため、複数回読み込んでも意味がありません。

# b/example.rb
10.times.each do
  require './a/fuga'
end

# => 'fuga'が1回出力される

このような違いから、「require はライブラリのロード、load は設定ファイルの読み込みなどに使うのが典型的な用途」ということになります。

Q45: クラスとモジュールの違いを説明してください

回答:

クラスには属性とメソッドが1つ以上ある。クラスはインスタンスを作成できる。 モジュールは単なるメソッドと定数のコレクションであり、他のモジュールやクラスにミックスインできる。

これは「Q43: includeとextendの違いを説明してください」でほぼ説明しました。

クラスはインスタンスを作成できることがモジュールとは異なる点です。

class Hoge
    def hoge
        p 'hoge'
    end
end

Hoge.new.hoge

モジュールは単なるメソッドと定数のコレクションですのでインスタンス化する機能がありません。

module Fuga
    BAR = 'bar'
    def fuga
        p 'fuga'
    end

    def bar
        p BAR
    end
end

上記モジュールファイルをクラスにミックスインすることでモジュール内のメソッドや定数を使うことができます。

class Hoge
    include Fuga
    def hoge
        p 'hoge'
    end
end

Hoge.new.fuga # => 'fuga'
Hoge.new.bar # => 'bar'

Q46: Active Recordのスコープについて説明してください

回答:

Active Recordのスコープは、Active Recordモデル内で定義され、他のどこからでも呼び出せるクエリロジックです。 アプリ内のさまざまな場所で同じロジックを複製するよりも、スコープを定義する方が便利なことがあります。

# スコープの例
class Post
  scope :active_posts, -> { where(active:true) }
end

スコープについて理解するのに最初はかなり時間がかかった初学者は多いと思います。自分もその1人だったのでここで改めてしっかりと理解します!

「Q17:「ファットモデル、薄いコントローラ」の意味を説明してください」で少し説明しましたが、スコープはアプリ内で複数の箇所で呼び出すロジックをモデル内にまとめて定義しておくことために使われます。例えば、複数のコントローラで以下のように指定した文字列が含まれるNoteのインスタンスのみをインスタンス変数@notesに入れたいとします。

全てのNoteのインスタンスから指定した文字列が含まれるもの取り出す

class HogeController < ApplicationController
    def index
        @notes = Note.where("LOWER(message) LIKE ?", "%#{term.downcase}%")
    end

ログイン中のユーザが持つNoteのインスタンスから指定した文字列が含まれるもの取り出す

class FugaController < ApplicationController
    def index
        @current_user_notes = current_user.notes.where("LOWER(message) LIKE ?", "%#{term.downcase}%")
    end

ですが見た目から分かるとおり、以下のクエリロジックはかなり複雑で複数の箇所で呼び出すのはあまり好ましくないです。

where("LOWER(message) LIKE ?", "%#{term.downcase}%")

そこでそのクエリロジックをActive Recordのモデル内に切り出して冗長な記述を減らすようにします。その方法としてスコープを使用します。スコープの定義の方法は以下のとおりです。

scope :メソッド名, ->(引数(必要であれば)) { 切り出したクエリロジック }

よって指定した文字列が含まれるインスタンスを取り出すためのscopeは以下のように定義できます。

class Note < ApplicationRecord
  scope :search, ->(term) {
    where("LOWER(message) LIKE ?", "%#{term.downcase}%")
  }
end

これで先ほど直接記述していたクエリロジックをコントローラに記述せずに、スコープで定義したメソッド名を使用して検索をすることができます。

全てのNoteのインスタンスから指定した文字列が含まれるもの取り出す

class HogeController < ApplicationController
    def index
        @notes = Note.search("hogehoge")
    end

ログイン中のユーザが持つNoteのインスタンスから指定した文字列が含まれるもの取り出す

class FugaController < ApplicationController
    def index
        @current_user_notes = current_user.notes.search("hogehoge")
    end

これが回答例にある「アプリ内のさまざまな場所で同じロジックを複製するよりも、スコープを定義する」の部分にあたります。

Q47: クラス変数とインスタンス変数の違いを説明してください

回答:

インスタンス変数は@で表記され、クラスのインスタンスに関連付けられます。あるインスタンスで属性の値を変更しても、他のインスタンスにある同じインスタンス変数には影響しません。 クラス変数は@@で表記されますが、インスタンス変数よりも直感に反します。クラス変数は、クラスのあらゆるインスタンスで共有されるので、あるインスタンスでクラス変数を変更すると、すべてのインスタンスのクラス変数に影響します。

class Coffee
  @@likes = 0
  def like
    @@likes += 1
  end
  def likes
    puts @@likes
  end
end
coffee_one = Coffee.new
coffee_two = Coffee.new
coffee_one.like
coffee_two.like
coffee_one.likes
=> 2

インスタンス変数の特徴は下記の2つです。

  • 変数名の先頭に @ をつける
  • インスタンスの範囲内でのみ参照することが可能で、同じクラスの別のインスタンスでからは参照ができない

インスタンス変数であることを明示するために必ず先頭に@を付けます。

@name = name
@users = User.all

後半の説明ですが、例えば下記のようなクラスがあるとします。

class Hoge
  def hoge(name)
    @name = name
  end

  def display_hoge_name
    p @name
  end
end

そのクラスのインスタンスを作成しインスタンス変数@nameを出力します。

instance = Hoge.new
instance.hoge("松本さん")
instance.display_hoge_name # => "松本さん"

@nameはそのインスタンスの中でした参照することができず、同じ名前のインスタンス変数を変更しても他のインスタンスインスタンス変数に影響はありません。

another_instance = Hoge.new
another_instance.hoge("松岡さん")
another_instance.display_hoge_name # => "松岡さん"
instance.display_hoge_name # => "松本さん"

したがってインスタンス変数は各インスタンスの中でのみ参照できる共通の値となります。

クラス変数の特徴は下記の2つです。

  • 変数名の先頭に @@ をつける
  • クラスとその全てのインスタンスで参照することができるため、変数の値が共通の値となる。

クラス変数であることを明示するために必ず先頭に@@を付けます。

@@name
@@users

後半の説明部分は同じクラスを例にして説明します。ただし、インスタンス変数だった部分はクラス変数の記述に変更しています。

class Hoge
  def hoge(name)
    @@name = name
  end

  def display_hoge_name
    p @@name
  end
end

そのクラスのインスタンスを作成しクラス変数@@nameを出力します。

instance = Hoge.new
instance.hoge("松本さん")
instance.display_hoge_name # => "松本さん"

@@nameは他のインスタンスからも参照することができるので、別インスタンスから値を変更することができます。

another_instance = Hoge.new
another_instance.hoge("松岡さん")
another_instance.display_hoge_name # => "松岡さん"
instance.display_hoge_name # => "松岡さん"

このようにクラスとインスタンス間で共通の値を参照する場合にクラス変数は使用されます。

Q48: Active Recordのfind、find_by、whereの違いを説明してください

回答:

find 引数を1つ取り、その引数とマッチする主キーを持つレコードを探索します。 find_by キーと値を引数に取り、マッチする最初のレコードを返します。 where キーと値を引数に取り、マッチするレコードのコレクションを返します。マッチしない場合は空のコレクションを返します。

find

基本的にはRailsではIDで検索をする際に使われます。

@user = User.find(1)

しかし、findは主キーレコードを探索というのがポイントです。基本的にfindはIDを引数と覚えている人が多いかなと思いますが、IDで検索できるのは主キーがIDの時のみです。主キーが変更されていれば、ID検索ではなくなるので注意が必要です。例えば、Userテーブルのnameが主キーの場合、下記のように検索できます。

@user = User.find('松本')

検索に引っかからなかった場合、ActiveRecord::RecordNotFoundエラーを返します。

@user = User.find(100)
# => Couldn't find User with 'id'=100 (ActiveRecord::RecordNotFound)

find_by

findは主キー検索ですが、キーを指定して検索する場合にはfind_byが便利です。例えば、nameカラムが主キーではない場合は下記のようになります。

@user = User.find_by(name: '松本')

マッチしない場合はnilを返します。

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

where

nameカラムに「松本」という苗字は複数人いる可能性があります。ですので、find_byでは最初に検索に引っかかった松本さんしか取得できません。その場合はコレクションとして苗字「松本」を取得した方が良さそうです。その時に使えるのがwhereメソッドです。find_by同様キーと値を引数にとりますが、返却値は配列になっています。

@users = User.where(name: '松本')

マッチしない場合は空のコレクションを返します。

@users = User.where(name: 'kfjjakhfkjajhrkagkjark')
# => [] 

終わりに

今回はここまで!!!!

次回はQ49からです!

次回ラストになります!