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

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

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

はじめに

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

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

(前回の記事)

sakitadaiki.hatenablog.com

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

Q49: selectとmapとcollectの違いを説明してください

回答:

3つともブロックをひとつ引数として受け取ります。 select コレクションのサブセットを取得するのに使います。!付きのselect!を呼ぶと、元のコレクションが改変されます。

i = [1,2,3,4,5]
i.select {|x| x % 2 == 0}
# => [2, 4]

map コレクションの各要素に対して操作を実行し、更新されたコレクションを出力します。!付きのmap!を呼ぶと、本のコレクションが改変されます。

i = [1,2,3,4,5]
i.map {|x| x+1}
# => [2,3,4,5,6]

collect mapのエイリアスなので動作は同じです。

上記のブロックを引数に取るメソッドですが、個人的にはあまり使ったことがないのですが、現場だとどうなのでしょうか、、。

selectメソッドはコレクションのサブセットを取得するとありますが、要は配列の中から条件にマッチするものだけを取り出すということですね。ですので、ブロックの中身は条件式になります。

i = [1,2,3,4,5,6,7,8,9]
i.select {|x| x % 3 == 0}
# => [3, 6, 9] # 3の倍数を取得

上記のメソッドにはないのですが、filterメソッドというselectのエイリアスメソッドもあります。フィルタリングという意味でこちらの方が直感的に何をするのかがわかりますね。

i = [1,2,3,4,5,6,7,8,9]
i.filter {|x| x % 3 == 0}
# => [3, 6, 9]

mapメソッドは配列の中身を更新して取得する時に便利ですね。

i = [1,2,3,4,5]
i.map {|x| x * 2}
# => [2,4,6,8,10]

ちなみに返り値がない場合、その位置にある要素はnilになります。例えば、ブロックの中に条件式を含ませると返り値がnilになります。

i = [1,2,3,4,5]
i.map {|x| x * 2 if x % 2 == 0}
# => [nil,4,nil,8,nil] 2の倍数だけ2倍になり、その他はnilとなって返却される

しかし上記のようにnilが返ってしまうのは好ましくない場合が多そうですね。なにせ条件に合わない要素だけその要素の位置にnilが返ってきてしまうので。ですので、要素の評価をして条件にマッチしたものだけを加工して返却する処理をしてくれるメソッドも用意されています。それはfilter_mapメソッドです。

i = [1,2,3,4,5]
i.filter_map {|x| x * 2 if x % 2 == 0}
# => [4,8] 条件に合わなかった要素はnilとして返却されない

collectはmapのエイリアスでmapと同じ挙動となります。

i = [1,2,3,4,5]
i.collect {|x| x * 2}
# => [2,4,6,8,10]

また、ブロックを引数に取るメソッドとして有名なeachがあります。上記のメソッド群とは違い、戻り値は元の配列と同じになります。元の配列を加工する必要がない処理などではeachを使った方が良さそうですね。

i = [1,2,3,4,5]
i.each {|x| x * 2}
# => [1,2,3,4,5]

Q50: RailsCRUD verbと、それに対応するアクションを述べてください

回答:

verb アクション GET index GET new POST create GET show GET edit PATCH/PUT update DELETE destroy

CRUDのルーティングをよしなにやってくれるresourcesをroutes.rbに記載してから、ルーティングを確認するとよくわかると思います。

Rails.application.routes.draw do
  resources :posts
end
$ bundle exec rails routes
        Prefix Verb   URI Pattern
         posts GET    /posts(.:format)             posts#index
           POST   /posts(.:format)             posts#create
  new_post GET    /posts/new(.:format)         posts#new
 edit_post GET    /posts/:id/edit(.:format)    posts#edit
      post GET    /posts/:id(.:format)         posts#show
           PATCH  /posts/:id(.:format)         posts#update
           PUT    /posts/:id(.:format)         posts#update
           DELETE /posts/:id(.:format)         posts#destroy

サーバから情報を取得してくる時に使用するGETのアクションとしては以下の4つです。基本的にはページを表示するため、ページ表示のためのヘルパーメソッドが生成されます。

  • index (posts_path)
  • new (new_post_path)
  • edit (edit_post_path)
  • show (post_path(@post))

indexアクションの用途は基本的に一覧表示です。

def index
    @posts = Post.all
end

newアクションは新規作成モデル作成のためにからのインスタンスをビューに渡します。

def new
    @post = Post.new
end

editアクションとshowアクションはそれぞれ既に作成されたモデルの詳細ページ及び編集ページですので、既に作成されたそのモデルを表示します。

def show
    @post = Post.find(params[:id])
end
def edit
    @post = Post.find(params[:id])
end

サーバへ情報を登録する時に使用するPOSTに対応するのはcreateアクションです。新規データを作成してそれをサーバに保存します。基本的にはsaveメソッドを使用して保存します。

def create
  @post = current_user.posts.new(post_params)
  if @post.save
    redirect_to post_path(@post), success: 'ポストを作成しました'
  else
    flash.now[:danger] = 'ポストを作成できませんでした'
    render :new
  end
end

サーバに保存されたデータを更新するPUT、PATCHはupdateアクションです。

def update
  @post = current_user.posts.find(params[:id])
  if @post.update(post_params)
    redirect_to post_path(@post), success: 'ポストを更新しました'
  else
    flash.now[:danger] = 'ポストを更新できませんでした'
    render :edit
  end
end

PUTとPATCHが両方とも更新ですので、updateアクションは2つ表示されていますが、これらの違いについては下記記事を参照ください。

qiita.com

保存されたデータを削除するDELETEはdestroyアクションに対応しています。

def destroy
  @post = current_user.posts.find(params[:id])
  @post.destroy!
  redirect_to posts_path, success: 'ポストを削除しました'
end

このCRUDRailsでは当たり前に出てきますのでしっかりと説明できるようにしておきます。

Q51: createアクションへのルーティングを、resourcesを使わないで定義してください

回答:

resourcesありの場合 resources :photos resourcesなしの場合 post '/photos', to: 'photos#create', as: :create_photo

Q50で使用した例をそのまま流用します。

Rails.application.routes.draw do
  resources :posts
end

上述しましたがresourcesを使えばRailsがよしなにCRUDに対応したルーティングを定義してくれます。

$ bundle exec rails routes
        Prefix Verb   URI Pattern
           POST   /posts(.:format)             posts#create

これと同じように定義するには以下のように定義します。

post '/posts', to: 'posts#create'

ルーティングも同じように定義されています。

$ bundle exec rails routes
        Prefix Verb   URI Pattern
           POST   /posts(.:format)             posts#create

ちなみに回答例にあるasオプションは名前付きヘルパーを指定します。resourcesではcreateアクションは名前付きヘルパーが指定されていませんが、asを使うことで指定できます。

post '/posts', to: 'posts#create', as: :create_post
$ bundle exec rails routes
     Prefix Verb   URI Pattern
create_post POST   /posts(.:format)             posts#create

Q52: Rubyの3段階のアクセス制御を述べてください

回答:

public このメソッドは任意のオブジェクトから呼び出せる protected このメソッドは、そのメソッドが定義されているクラスと、そのクラスのサブクラスからしか呼び出せない private このメソッドは、そのオブジェクト自身しか呼び出せない

public はデフォルトのメソッドのアクセス権ですので、普段定義しているメソッドと変わりありません。クラスの外でも呼び出しが可能です。

Class Hoge
    # デフォルトでpublic
    def hoge
        p 'hoge'
    end
end

Hoge.new.hoge # => 'hoge'

順番が変わりますが、privateはクラス外からの呼び出しができなくなります。privateと明示するとその下で定義されたメソッドはprivateメソッドになります。

Class Hoge
    private
    def hoge
        p 'hoge'
    end
end

Hoge.new.hoge # => エラーになる

よってそのインスタンスやクラスの中でのみ使用することができます。Railsですとストロングパラメータのメソッドで使われるのが一般的です。

def create
    @user = User.new(user_params)
    if @user.save
      redirect_to root_path
    else
      render :new
    end
  end

  private

  def user_params
    params.require(:user).permit(:name, :email, :image, :password, :password_confirmation)
  end

メソッドはクラスの中でのみ使用されているのでエラーになりません。

そして最も使ったことがないprotectedですが、基本的にはprivateと同様の挙動となり、クラスの外からメソッドを呼び出すとエラーになります。

Class Hoge
    protected
    def hoge
        p 'hoge'
    end
end

Hoge.new.hoge # => エラーになる

異なる点としてはselfをレシーバーとすると、privateではエラーになりますが、protectedではエラーにはなりません。

Class Hoge
    def hoge_public
        self.hoge_protected # => 'protected!!'
        self.hoge_private # エラーになる
    end
    private
    def hoge_private
        p 'private!!'
    end
    protected
    def hoge_protected
        p 'protected!!'
    end
end

Hoge.new.hoge_private # => エラーになる
Hoge.new.hoge_protected # => エラーになる

他の記事をいくつか読みましたが、protectedを使う機会はあまりないようです。。。

Q53: Rubyのシングルトンの使いみちを説明してください

回答:

シングルトンは、クラスにインスタンスを1つしか持たせないデザインパターンです。シングルトンはRuby界隈では嫌われがちですが、Rubyにはシングルトン用のモジュールも付属しています。

require 'singleton'
class Thing
  include Singleton
end
puts Thing.instance
# => #<Thing:0x00007fdd492cf488>

シングルトンについては解説は回答とほぼ内容は同じですが下記説明の方がしっくりきました。

シングルトンとは、オブジェクト指向プログラミングにおけるクラスのデザインパターンの一つで、実行時にそのクラスのインスタンスが必ず単一になるよう設計すること。

シングルトンとは - IT用語辞典

そのクラスのインスタンスがかなるらず単一となるようにとあるように、そのクラスの元のインスタンスは複数存在することができず、ただ一つのみしか存在できないということです。

別記事での例を見てみます。

# Singletonは、Mix-inしたクラスのinstanceは同一のインスタンスを返すようになる
require 'singleton'

# シングルトン
class SingletonObject
  # instanceメソッドが定義され、newメソッドがprivateに設定される
  include Singleton
  attr_accessor :counter

  def initialize
    @counter = 0
  end
end

morizyun.github.io

シングルトンのクラスを定義するためには下記二つの記載が必須のようです。

# Singletonは、Mix-inしたクラスのinstanceは同一のインスタンスを返すようになる
require 'singleton'

singletonファイルをシングルトンを定義するファイル内に読み込むことでシングルトンを定義することができるようです。

# instanceメソッドが定義され、newメソッドがprivateに設定される
include Singleton

また、Singletonモジュールを読み込むことでそのファイル内にinstanceメソッドが定義され、newメソッドがprivateに設定されます。つまり、インスタンスを作成するnewメソッドの外部呼び出しができないため、物理的にインスタンス作成を断ち切っています。

obj3 = SingletonObject.new
# private method `new' called for SingletonObject:Class (NoMethodError)
# ↑ newでのインスタンスの作成に失敗

代わりに単一インスタンスを作成する方法としてinstanceメソッドが定義されています。しかし、単一のインスタンスしか持たないため、別の変数にinstanceメソッドを使用してインスタンスを格納しても、それは全て同じインスタンスを参照しているため、クラスで定義されている値も同じ値を参照することになります。

obj1 = SingletonObject.instance
obj1.counter += 1
puts(obj1.counter)
# 1

obj2 = SingletonObject.instance
obj2.counter += 1
puts(obj2.counter)
# 2
# ↑ 前回の+1が引き継がれている #

終わりに

今回はここまで!!!!

そしてこのシリーズも今回で最後になります!長かったですが大変勉強になりました!

改めて自分はGem頼りエンジニアをしていたのだなと痛感したのでこれからも基本を振り返って行きたいと思います。

以上!引き続き本ブログをよろしくお願いします!