【Rails】技術面接対策の記事の質問を多少深ぼる記事④
はじめに
こんにちは!大ちゃんの駆け出し技術ブログです。
この記事は前回の記事の続きものです。
(前回の記事)
本記事では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については下記記事で説明されていました。
前置き
記事の例では、ハイキングコースを表示するアプリケーションを例に説明しています。このアプリケーションの開発途中にユーザーがハイキングコースの検索結果に天気予報の表示を加えることになり、天気予報の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
Sidekiq
Sucker Punch
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からです!