【Rails】技術面接対策の記事の質問を多少深ぼる記事⑧
はじめに
こんにちは!大ちゃんの駆け出し技術ブログです。
この記事は前回の記事の続きものです。
(前回の記事)
本記事では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 は設定ファイルの読み込みなどに使うのが典型的な用途です。
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からです!
次回ラストになります!