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

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

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

はじめに

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

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

(前回の記事)

sakitadaiki.hatenablog.com

本記事ではQ19 ~ Q24を深掘りします。 Q23についてはこの記事では治らない内容でしたのでいつか別記事で書きたいと思います。

Q19: クラスメソッドとインスタンスメソッドの違いを説明してください

回答:

クラスメソッドはクラス上で利用でき、インスタンスメソッドはインスタンス上で利用できます(当たり前ですが)。両者の利用目的は異なるのが普通です。 Articleというクラスで考えましょう。インスタンスメソッドは、特定の記事1件の本文に含まれるワード数をカウントするのに利用できます。クラスメソッドは、すべての記事のうち特定の著者が書いた記事の件数をカウントするのに利用できます(スコープが違うことにお気づきでしょうか?)。 クラスメソッドはdef self.メソッド名のように定義します。

class Greeting
  def self.hello_from_class
    puts 'class: hello'
  end
  def hello_from_instance
    puts 'instance: hello '
  end
end
# class method
Greeting.hello_from_class # => 'class: hello'
# instance method
g = Greeting.new
g.hello_from_instance # => instance: hello

上記のコードの例はすごくわかりやすいです。クラス.クラスメソッドでクラスメソッドの呼び出しができ、クラス.new.インスタンスメソッドインスタンスメソッドの呼び出しができます。

# class method
Greeting.hello_from_class # => 'class: hello'
# instance method
Greeting.new.hello_from_instance # => instance: hello

そしてクラスメソッドはdef self.メソッド名で定義ができます。

class Hoge
  def self.hoge
    puts 'hoge'
  end
end

しかし、回答の中間の文章がイメージがつきづらいのではないでしょうか?文章に従って解説していきます。

Articleというクラスで考えましょう。インスタンスメソッドは、特定の記事1件の本文に含まれるワード数をカウントするのに利用できます。クラスメソッドは、すべての記事のうち特定の著者が書いた記事の件数をカウントするのに利用できます(スコープが違うことにお気づきでしょうか?)。

Articleというクラスで考えましょう。

はい。

class Article

end

インスタンスメソッドは、特定の記事1件の本文に含まれるワード数をカウントするのに利用できます。

特定の記事1件の本文に含まれるワード数をカウントするメソッドを考えます。

Articleクラスに本文(body)があるとしましょう。

class Article
   attr_accessor :body

  def initialize(body)
    @body = body
  end

    def count_words
        body[:body].length
    end
end

これで以下のように文字数をカウントすることができます。

Article.new(body: 'hogehoge').count_words
=> 8

このように特定の記事に属性があってその属性を参照する必要がある場合にインスタンスメソッドを使います。

クラスメソッドは、すべての記事のうち特定の著者が書いた記事の件数をカウントするのに利用できます(スコープが違うことにお気づきでしょうか?)。

上述の特定の著者が書いた記事の件数をカウントするメソッドとしては以下のようになると思われます。

class Article
   attr_accessor :body, :author

  def initialize(body, author)
    @body = body
        @author = author
  end

    def count_words
        body[:body].length
    end

    def self.count_author_articles(author)
        where(author: author).length
    end
end

Article.count_author_articles('hogehoge')と使用することで特定の著者の記事数を取り出すことができます。クラスメソッドは全てのインスタンスに対して検索をかけるなど、特定のインスタンスではなく全てのインスタンスに対して何かしらの処理を行う時に使います。「スコープが違うことにお気づきでしょうか?」は特定のインスタンスと全体のインスタンスという範囲(スコープ)が違うということでしょう。

Q20: POROについて説明してください

回答:

POROは「Plain Old Ruby Object」の略です。 Rubyではほぼあらゆるものがオブジェクトですが、Active Recordでは複雑なオブジェクトが多数使われがちです。一般にPOROという用語は、ビジネスロジックをサポートするシンプルな小さいオブジェクトを強調するのに使われます。

少しわかりづらいですがPOROについては下記記事で説明されていました。

www.learnhowtoprogram.com

前置き

記事の例では、ハイキングコースを表示するアプリケーションを例に説明しています。このアプリケーションの開発途中にユーザーがハイキングコースの検索結果に天気予報の表示を加えることになり、天気予報のAPIを呼び出すことになったとします。問題となってくるのは「このAPIをどこで呼び出すのか」、です。

まず、コントローラーでAPIの処理を行う方法です。

# hike_controller.rb
    class HikeController < ApplicationController
    
  def index
        # 天気APIを呼び出す処理
        # 結果をviewに反映する処理
    end

end

しかし、これは「ファットモデル、薄いコントローラ」に反します。コントローラーにはできるだけロジックを記述しないようにするのが理想です。

よって、コントローラーがモデルから天気予報データを取得できるように、ハイキングモデル(hike.rb)にその処理を記述する方法が良さそうです。

class Hike < ApplicationRecord
    def check_weather
        # 天気APIを呼び出す処理
    end
end

しかし、これはあまり理想的なコードではありません。理由は天気は hike.rb モデルとは関係ないからです。天気とハイキングモデルの関連性がないのに、天気を取り出すロジックをhike.rbに記述するのは後々メンテナンスに関わってくるのでよくないということですね。

よって、weather.rbという新しいモデルを作って、そこにコードを入れれば良さそうにも見えます。

class Whether < ApplicationRecord
    def check_weather
        # 天気APIを呼び出す処理
    end
end

しかし実はそれもダメなのです。理由は、実際にデータベース内のweatherテーブルにアクセスする必要がないからです。今回の実装ではユーザーがハイキングコースを入力し、天気予報データが表示されるだけで、その天気情報をデータベースに保存する必要はないのです。モデルを作成してしまうと、ActiveRecordを経由することになるので、データを保存する必要のないのにSQLを発行してしまいます。

ここまでをまとめると以下になります。

- 天気予報を表示するAPIメソッドの記述がどこかに必要
- コントローラーには「ファットモデル、薄いコントローラ」に反するので✖︎
- hike.rbには天気の情報との関連性がないので✖︎
- 天気予報のデータを保存する必要がないのでwhether.rbを新規に作るのは✖︎

PORO

前置きが長くなりましたがここで登場するのがPOROになります!ただしこれは難しい技術とかではなく、ただ新しいクラスを定義するだけの方法です。原点に変えるということですね。

class Weather

  def initialize(zip)
    @zip = zip
  end

  def get_humidity
    response = HTTParty.get('http://api.openweathermap.org/data/2.5/weather?zip=' + @zip + ',us&appid=[YOUR API KEY HERE]')
    response["main"]["humidity"]
  end
end

上述したクラスはただのクラスです。モデルの場合はApplicationRecordを継承していますが、その継承がなく単一のクラスとして定義します。これで天気予報の情報がindexアクションに反映することができます。

class HikeController < ApplicationController
    def index
      weather_object = Weather.new("97210")
      @humidity = weather_object.get_humidity()
        ・
        ・
        ・
    end
end

コントローラで新しいWeatherオブジェクトをインスタンス化し、それに対してget_humidity()を呼び出しています。そして@humidityをインスタンス変数にして、index.html.erbビューで利用できるようにしました。シンプルですが、コードのメンテナンス性が1番高そうな解決方法です。このクラスが回答にある「ビジネスロジックをサポートするシンプルな小さいオブジェクト」ということですね。

Q21: Rubyで多重継承は使えますか?

回答:

Rubyでは複数の親クラスからの多重継承は許されていません。その代わり、includeやextendによるモジュールのミックスインが利用できます。

多重継承とは文字通り複数のクラスから継承をすることです。詳しくは説明しませんが、JavaでClassAとClassBを多重継承したClassABを定義する場合、下記のコードになるらしいです。

class ClassAB extends ClassA implements InterfaceB {
    private ClassB instanceB;

    public ClassAB(int a, int b) {
        super(a);

        this.instanceB = new ClassB(b);
    }

    public int getB() {
        return this.instanceB.getB();
    }
}

Rubyの場合は単一のクラスのみ継承できます。

class ClassA < ClassB
end

もしmoduleCに定義されているインスタンスメソッドを使用したい場合、includeを使用すれば使えるようになります。

module moduleC
    def puts_c
        puts "c"
    end
end
class ClassA < ClassB
    include moduleC
end

ClassA.new.puts_c # => c

もしmoduleCに定義されているインスタンスメソッドをクラスメソッドとして使用したい場合、extendを使用すれば使えるようになります。

class ClassA < ClassB
    extend moduleC
end

ClassA.puts_c # => c

だいたいincludeを使うことになるのかなと思います。

Q22: Rubyは「強い型付け」「弱い型付け」のどちらですか?

回答:

Rubyは「強い型付け」の言語です。"hello" + 3を計算するとエラーになります。 対照的にJavaScriptは「弱い型付け」言語であり、"hello" + 3の結果は"hello3"になります。

深掘りする必要なさそですね。

Ruby

p "hello" + 3
=> `+': no implicit conversion of Integer into String (TypeError)

JS

console.log("hello" + 3)
=> "hello3"

Q23: バックグラウンドジョブにどんなフレームワークを使ったことがありますか?

回答:

Delayed::Job・・・使いやすく、セットアップも簡単です。キューは1つのデータベーステーブルに保存されます。Delayed::Jobとproductionで同じデータベースが使われている場合、ジョブが増えすぎるとデータベースがボトルネックになる可能性があります。 Sidekiq・・・Redisを用いてジョブをキューイングします。Redisはインメモリデータストアなので高速です。Sidekiqを使うにはRedisの追加が必要なので、インフラがその分複雑になります。 Sucker Punch・・・Rubyの1つのプロセスとして実行され、すべてのジョブをメモリ上に配置します。プロセスがクラッシュするとジョブが失われるので、クリティカルなタスクには不向きです。

これに関しては使ってみないとわからないので割愛します。いつか別のブログで紹介したいと多います。

Sidekiqのスター数がダントツで多いですね。

  • Delayed::Job

github.com

  • Sidekiq

github.com

  • Sucker Punch

github.com

Q24: Rubyのクラスでコンストラクタを宣言する方法を説明してください

回答:

コンストラクタはinitializeメソッドで定義します。このメソッドはクラスの新しいインスタンスが初期化されたときに呼び出されます。initializeメソッドの定義は必須ではなく、新しいインスタンスに属性値を提供するときによく利用されます。

class Thing
  attr_reader :name
  def initialize(name)
    @name = name
  end
end
t = Thing.new('dog')
puts t.name # => 'dog

コンストラクタはinitializeメソッドで定義します。このメソッドはクラスの新しいインスタンスが初期化されたときに呼び出されます。

コンストラクタとはそもそも何かというと、「インスタンスが作成された時に自動的に実行されるメソッド」です。それがRubyではinitializeメソッドということになります。

class ClassA
  def initialize
    # 何かしらの処理
  end
end

ClassA.new # initializeメソッドが実行される

initializeメソッドの定義は必須ではなく、新しいインスタンスに属性値を提供するときによく利用されます。

これは「Q4: Rubyのゲッターとセッターについて説明してください」でも少し触れたかもしれません。

そのインスタンスに対して属性値を提供する場合、インスタンス変数(@hogehoge)を使用します。

class Thing
  def initialize(name) # name属性を付与
    @name = name
  end
end

以上!!

簡単でしたね、、、

終わりに

今回はここまで!!!!

次回はQ19からです!